Skip to content

Instantly share code, notes, and snippets.

@munificent
Last active April 2, 2019 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save munificent/1233df76e793cdba4c56ded4d8d4cf1d to your computer and use it in GitHub Desktop.
Save munificent/1233df76e793cdba4c56ded4d8d4cf1d to your computer and use it in GitHub Desktop.
Here is the relevant part of the log, the error seems to occur when trying to format the directory 'dart_style':
[1528/1570] ACTION //utils/dartfmt:dartfmt(//build/toolchain/linux:clang_x64)
FAILED: gen/dartfmt.dart.snapshot
python ../../build/gn_run_binary.py compiled_action dart --deterministic --packages=/dart-sdk/sdk/.packages --snapshot=gen/dartfmt.dart.snapshot --snapshot-depfile=/dart-sdk/sdk/out/ReleaseX64/gen/dartfmt.dart.snapshot.d --snapshot-kind=app-jit /dart-sdk/sdk/third_party/pkg_tested/dart_style/bin/format.dart ../../third_party/pkg_tested/dart_style
Command failed: ./dart --deterministic --packages=/dart-sdk/sdk/.packages --snapshot=gen/dartfmt.dart.snapshot --snapshot-depfile=/dart-sdk/sdk/out/ReleaseX64/gen/dartfmt.dart.snapshot.d --snapshot-kind=app-jit /dart-sdk/sdk/third_party/pkg_tested/dart_style/bin/format.dart ../../third_party/pkg_tested/dart_style
output: Formatting directory ../../third_party/pkg_tested/dart_style:
After which it prints the file it could not format/ Full listing:
// Copyright (c) 2015, 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.example.format;
import 'dart:io';
import 'dart:mirrors';
import 'package:path/path.dart' as p;
import 'package:dart_style/dart_style.dart';
import 'package:dart_style/src/debug.dart' as debug;
void main(List<String> args) {
// Enable debugging so you can see some of the formatter's internal state.
// Normal users do not do this.
debug.traceChunkBuilder = true;
debug.traceLineWriter = true;
debug.traceSplitter = true;
debug.useAnsiColors = true;
runTest("regression/0000/0068.stmt", 14);
formatStmt("hello(world);");
}
void formatStmt(String source, [int pageWidth = 80]) {
runFormatter(source, pageWidth, isCompilationUnit: false);
}
void formatUnit(String source, [int pageWidth = 80]) {
runFormatter(source, pageWidth, isCompilationUnit: true);
}
void runFormatter(String source, int pageWidth, {bool isCompilationUnit}) {
try {
var formatter = new DartFormatter(pageWidth: pageWidth);
var result;
if (isCompilationUnit) {
result = formatter.format(source);
} else {
result = formatter.formatStatement(source);
}
drawRuler("before", pageWidth);
print(source);
drawRuler("after", pageWidth);
print(result);
} on FormatterException catch (error) {
print(error.message());
}
}
void drawRuler(String label, int width) {
var padding = " " * (width - label.length - 1);
print("$label:$padding|");
}
/// Runs the formatter test starting on [line] at [path] inside the "test"
/// directory.
void runTest(String path, int line) {
var indentPattern = new RegExp(r"^\(indent (\d+)\)\s*");
// Locate the "test" directory. Use mirrors so that this works with the test
// package, which loads this suite into an isolate.
var testDir = p.join(
p.dirname(currentMirrorSystem()
.findLibrary(#dart_style.example.format)
.uri
.path),
"../test");
var lines = new File(p.join(testDir, path)).readAsLinesSync();
// The first line may have a "|" to indicate the page width.
var pageWidth = 80;
if (lines[0].endsWith("|")) {
pageWidth = lines[0].indexOf("|");
lines = lines.skip(1).toList();
}
var i = 0;
while (i < lines.length) {
var description = lines[i++].replaceAll(">>>", "").trim();
// Let the test specify a leading indentation. This is handy for
// regression tests which often come from a chunk of nested code.
var leadingIndent = 0;
var indentMatch = indentPattern.firstMatch(description);
if (indentMatch != null) {
leadingIndent = int.parse(indentMatch[1]);
description = description.substring(indentMatch.end);
}
if (description == "") {
description = "line ${i + 1}";
} else {
description = "line ${i + 1}: $description";
}
var startLine = i + 1;
var input = "";
while (!lines[i].startsWith("<<<")) {
input += lines[i++] + "\n";
}
var expectedOutput = "";
while (++i < lines.length && !lines[i].startsWith(">>>")) {
expectedOutput += lines[i] + "\n";
}
if (line != startLine) continue;
var isCompilationUnit = p.extension(path) == ".unit";
var inputCode =
_extractSelection(input, isCompilationUnit: isCompilationUnit);
var expected =
_extractSelection(expectedOutput, isCompilationUnit: isCompilationUnit);
var formatter =
new DartFormatter(pageWidth: pageWidth, indent: leadingIndent);
var actual = formatter.formatSource(inputCode);
// The test files always put a newline at the end of the expectation.
// Statements from the formatter (correctly) don't have that, so add
// one to line up with the expected result.
var actualText = actual.text;
if (!isCompilationUnit) actualText += "\n";
print("$path $description");
drawRuler("before", pageWidth);
print(input);
if (actualText == expected.text) {
drawRuler("result", pageWidth);
print(actualText);
} else {
print("FAIL");
drawRuler("expected", pageWidth);
print(expected.text);
drawRuler("actual", pageWidth);
print(actualText);
}
}
}
/// Given a source string that contains ‹ and › to indicate a selection, returns
/// a [SourceCode] with the text (with the selection markers removed) and the
/// correct selection range.
SourceCode _extractSelection(String source, {bool isCompilationUnit: false}) {
var start = source.indexOf("‹");
source = source.replaceAll("‹", "");
var end = source.indexOf("›");
source = source.replaceAll("›", "");
return new SourceCode(source,
isCompilationUnit: isCompilationUnit,
selectionStart: start == -1 ? null : start,
selectionLength: end == -1 ? null : end - start);
}
// 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.benchmark.benchmark;
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:dart_style/dart_style.dart';
const NUM_TRIALS = 100;
const FORMATS_PER_TRIAL = 30;
/// Note, these files use ".txt" because while they can be *parsed* correctly,
/// they don't resolve without error. That's OK because the formatter doesn't
/// care about that.
final source = loadFile("before.dart.txt");
final expected = loadFile("after.dart.txt");
void main(List<String> args) {
var best = 99999999.0;
// Run the benchmark several times. This ensures the VM is warmed up and lets
// us see how much variance there is.
for (var i = 0; i <= NUM_TRIALS; i++) {
var start = new DateTime.now();
// For a single benchmark, format the source multiple times.
var result;
for (var j = 0; j < FORMATS_PER_TRIAL; j++) {
result = formatSource();
}
var elapsed =
new DateTime.now().difference(start).inMilliseconds / FORMATS_PER_TRIAL;
// Keep track of the best run so far.
if (elapsed >= best) continue;
best = elapsed;
// Sanity check to make sure the output is what we expect and to make sure
// the VM doesn't optimize "dead" code away.
if (result != expected) {
print("Incorrect output:\n$result");
exit(1);
}
// Don't print the first run. It's always terrible since the VM hasn't
// warmed up yet.
if (i == 0) continue;
printResult("Run ${padLeft('#$i', 3)}", elapsed);
}
printResult("Best ", best);
}
String loadFile(String name) {
var path = p.join(p.dirname(p.fromUri(Platform.script)), name);
return new File(path).readAsStringSync();
}
void printResult(String label, double time) {
print("$label: ${padLeft(time.toStringAsFixed(2), 4)}ms "
"${'=' * ((time * 5).toInt())}");
}
String padLeft(input, int length) {
var result = input.toString();
if (result.length < length) {
result = " " * (length - result.length) + result;
}
return result;
}
String formatSource() {
var formatter = new DartFormatter();
return formatter.format(source);
}
// Copyright (c) 2015, 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 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:dart_style/src/dart_formatter.dart';
import 'package:dart_style/src/exceptions.dart';
import 'package:dart_style/src/formatter_options.dart';
import 'package:dart_style/src/io.dart';
import 'package:dart_style/src/source_code.dart';
import 'package:dart_style/src/style_fix.dart';
// Note: The following line of code is modified by tool/grind.dart.
const version = "1.2.4";
void main(List<String> args) {
var parser = new ArgParser(allowTrailingOptions: true);
parser.addSeparator("Common options:");
parser.addFlag("help",
abbr: "h", negatable: false, help: "Shows usage information.");
parser.addFlag("version",
negatable: false, help: "Shows version information.");
parser.addOption("line-length",
abbr: "l", help: "Wrap lines longer than this.", defaultsTo: "80");
parser.addFlag("overwrite",
abbr: "w",
negatable: false,
help: "Overwrite input files with formatted output.");
parser.addFlag("dry-run",
abbr: "n",
negatable: false,
help: "Show which files would be modified but make no changes.");
parser.addSeparator("Non-whitespace fixes (off by default):");
parser.addFlag("fix", negatable: false, help: "Apply all style fixes.");
for (var fix in StyleFix.all) {
// TODO(rnystrom): Allow negating this if used in concert with "--fix"?
parser.addFlag("fix-${fix.name}", negatable: false, help: fix.description);
}
parser.addSeparator("Other options:");
parser.addOption("indent",
abbr: "i", help: "Spaces of leading indentation.", defaultsTo: "0");
parser.addFlag("machine",
abbr: "m",
negatable: false,
help: "Produce machine-readable JSON output.");
parser.addFlag("set-exit-if-changed",
negatable: false,
help: "Return exit code 1 if there are any formatting changes.");
parser.addFlag("follow-links",
negatable: false,
help: "Follow links to files and directories.\n"
"If unset, links will be ignored.");
parser.addOption("preserve",
help: 'Selection to preserve, formatted as "start:length".');
parser.addOption("stdin-name",
help: "The path name to show when an error occurs in source read from "
"stdin.",
defaultsTo: "<stdin>");
parser.addFlag("profile", negatable: false, hide: true);
parser.addFlag("transform", abbr: "t", negatable: false, hide: true);
ArgResults argResults;
try {
argResults = parser.parse(args);
} on FormatException catch (err) {
usageError(parser, err.message);
}
if (argResults["help"]) {
printUsage(parser);
return;
}
if (argResults["version"]) {
print(version);
return;
}
// Can only preserve a selection when parsing from stdin.
List<int> selection;
if (argResults["preserve"] != null && argResults.rest.isNotEmpty) {
usageError(parser, "Can only use --preserve when reading from stdin.");
}
try {
selection = parseSelection(argResults["preserve"]);
} on FormatException catch (_) {
usageError(
parser,
'--preserve must be a colon-separated pair of integers, was '
'"${argResults['preserve']}".');
}
if (argResults["dry-run"] && argResults["overwrite"]) {
usageError(
parser, "Cannot use --dry-run and --overwrite at the same time.");
}
checkForReporterCollision(String chosen, String other) {
if (!argResults[other]) return;
usageError(parser, "Cannot use --$chosen and --$other at the same time.");
}
var reporter = OutputReporter.print;
if (argResults["dry-run"]) {
checkForReporterCollision("dry-run", "overwrite");
checkForReporterCollision("dry-run", "machine");
reporter = OutputReporter.dryRun;
} else if (argResults["overwrite"]) {
checkForReporterCollision("overwrite", "machine");
if (argResults.rest.isEmpty) {
usageError(parser,
"Cannot use --overwrite without providing any paths to format.");
}
reporter = OutputReporter.overwrite;
} else if (argResults["machine"]) {
reporter = OutputReporter.printJson;
}
if (argResults["profile"]) {
reporter = new ProfileReporter(reporter);
}
if (argResults["set-exit-if-changed"]) {
reporter = new SetExitReporter(reporter);
}
int pageWidth;
try {
pageWidth = int.parse(argResults["line-length"]);
} on FormatException catch (_) {
usageError(
parser,
'--line-length must be an integer, was '
'"${argResults['line-length']}".');
}
int indent;
try {
indent = int.parse(argResults["indent"]);
if (indent < 0 || indent.toInt() != indent) throw new FormatException();
} on FormatException catch (_) {
usageError(
parser,
'--indent must be a non-negative integer, was '
'"${argResults['indent']}".');
}
var followLinks = argResults["follow-links"];
var fixes = <StyleFix>[];
if (argResults["fix"]) fixes.addAll(StyleFix.all);
for (var fix in StyleFix.all) {
if (argResults["fix-${fix.name}"]) {
if (argResults["fix"]) {
usageError(parser, "--fix-${fix.name} is redundant with --fix.");
}
fixes.add(fix);
}
}
if (argResults.wasParsed("stdin-name") && !argResults.rest.isEmpty) {
usageError(parser, "Cannot pass --stdin-name when not reading from stdin.");
}
var options = new FormatterOptions(reporter,
indent: indent,
pageWidth: pageWidth,
followLinks: followLinks,
fixes: fixes);
if (argResults.rest.isEmpty) {
formatStdin(options, selection, argResults["stdin-name"] as String);
} else {
formatPaths(options, argResults.rest);
}
if (argResults["profile"]) {
(reporter as ProfileReporter).showProfile();
}
}
List<int> parseSelection(String selection) {
if (selection == null) return null;
var coordinates = selection.split(":");
if (coordinates.length != 2) {
throw new FormatException(
'Selection should be a colon-separated pair of integers, "123:45".');
}
return coordinates.map((coord) => coord.trim()).map(int.parse).toList();
}
/// Reads input from stdin until it's closed, and the formats it.
void formatStdin(FormatterOptions options, List<int> selection, String name) {
var selectionStart = 0;
var selectionLength = 0;
if (selection != null) {
selectionStart = selection[0];
selectionLength = selection[1];
}
var input = new StringBuffer();
stdin.transform(new Utf8Decoder()).listen(input.write, onDone: () {
var formatter = new DartFormatter(
indent: options.indent,
pageWidth: options.pageWidth,
fixes: options.fixes);
try {
options.reporter.beforeFile(null, name);
var source = new SourceCode(input.toString(),
uri: name,
selectionStart: selectionStart,
selectionLength: selectionLength);
var output = formatter.formatSource(source);
options.reporter
.afterFile(null, name, output, changed: source.text != output.text);
return;
} on FormatterException catch (err) {
stderr.writeln(err.message());
exitCode = 65; // sysexits.h: EX_DATAERR
} catch (err, stack) {
stderr.writeln('''Hit a bug in the formatter when formatting stdin.
Please report at: github.com/dart-lang/dart_style/issues
$err
$stack''');
exitCode = 70; // sysexits.h: EX_SOFTWARE
}
});
}
/// Formats all of the files and directories given by [paths].
void formatPaths(FormatterOptions options, List<String> paths) {
for (var path in paths) {
var directory = new Directory(path);
if (directory.existsSync()) {
if (!processDirectory(options, directory)) {
exitCode = 65;
}
continue;
}
var file = new File(path);
if (file.existsSync()) {
if (!processFile(options, file)) {
exitCode = 65;
}
} else {
stderr.writeln('No file or directory found at "$path".');
}
}
}
/// Prints [error] and usage help then exits with exit code 64.
void usageError(ArgParser parser, String error) {
printUsage(parser, error);
exit(64);
}
void printUsage(ArgParser parser, [String error]) {
var output = stdout;
var message = "Idiomatically formats Dart source code.";
if (error != null) {
message = error;
output = stdout;
}
output.write("""$message
Usage: dartfmt [options...] [files or directories...]
Example: dartfmt -w .
Reformats every Dart file in the current directory tree.
${parser.usage}
""");
}
// Copyright (c) 2015, 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 'dart:convert';
import 'dart:io';
import 'package:grinder/grinder.dart';
import "package:node_preamble/preamble.dart" as preamble;
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart' as yaml;
/// Matches the version line in dart_style's pubspec.
final _versionPattern = new RegExp(r"^version: .*$", multiLine: true);
main(List<String> args) => grind(args);
@DefaultTask()
@Task()
validate() async {
// Test it.
await new TestRunner().testAsync();
// Make sure it's warning clean.
Analyzer.analyze("bin/format.dart", fatalWarnings: true);
// Format it.
Dart.run("bin/format.dart", arguments: ["-w", "."]);
}
@Task('Publish to npm')
npm() {
var out = 'dist';
var pubspec = yaml.loadYaml(getFile("pubspec.yaml").readAsStringSync());
var homepage = pubspec["homepage"];
var fileName = 'index.js';
// Generate modified dart2js output suitable to run on node.
var tempFile = new File('${Directory.systemTemp.path}/temp.js');
Dart2js.compile(new File('tool/node_format_service.dart'),
outFile: tempFile, categories: 'all');
var dart2jsOutput = tempFile.readAsStringSync();
new File('$out/$fileName').writeAsStringSync('''${preamble.getPreamble()}
self.exports = exports; // Temporary hack for Dart-JS Interop under node.
$dart2jsOutput''');
new File('$out/package.json')
.writeAsStringSync(const JsonEncoder.withIndent(' ').convert({
"name": "dart-style",
"version": pubspec["version"],
"description": pubspec["description"],
"main": fileName,
"typings": "dart-style.d.ts",
"scripts": {"test": "echo \"Error: no test specified\" && exit 1"},
"repository": {"type": "git", "url": "git+$homepage"},
"author": pubspec["author"],
"license": "BSD",
"bugs": {"url": "$homepage/issues"},
"homepage": homepage
}));
run('npm', arguments: ['publish', out]);
}
/// Gets ready to publish a new version of the package.
///
/// To publish a version, you need to:
///
/// 1. Make sure the version in the pubspec is a "-dev" number. This should
/// already be the case since you've already landed patches that change
/// the formatter and bumped to that as a consequence.
///
/// 2. Run this task:
///
/// pub run grinder bump
///
/// 3. Commit the change to a branch.
///
/// 4. Send it out for review:
///
/// git cl upload
///
/// 5. After the review is complete, land it:
///
/// git cl land
///
/// 6. Tag the commit:
///
/// git tag -a "<version>" -m "<version>"
/// git push origin <version>
///
/// 7. Publish the package:
///
/// pub lish
@Task()
@Depends(validate)
bump() async {
// Read the version from the pubspec.
var pubspecFile = getFile("pubspec.yaml");
var pubspec = pubspecFile.readAsStringSync();
var version = new Version.parse(yaml.loadYaml(pubspec)["version"]);
// Require a "-dev" version since we don't otherwise know what to bump it to.
if (!version.isPreRelease) throw "Cannot publish non-dev version $version.";
// Don't allow versions like "1.2.3-dev+4" because it's not clear if the
// user intended the "+4" to be discarded or not.
if (version.build.isNotEmpty) throw "Cannot publish build version $version.";
var bumped = new Version(version.major, version.minor, version.patch);
// Update the version in the pubspec.
pubspec = pubspec.replaceAll(_versionPattern, "version: $bumped");
pubspecFile.writeAsStringSync(pubspec);
// Update the version constant in bin/format.dart.
var binFormatFile = getFile("bin/format.dart");
var binFormat = binFormatFile.readAsStringSync().replaceAll(
new RegExp(r'const version = "[^"]+";'), 'const version = "$bumped";');
binFormatFile.writeAsStringSync(binFormat);
// Update the version in the CHANGELOG.
var changelogFile = getFile("CHANGELOG.md");
var changelog = changelogFile
.readAsStringSync()
.replaceAll(version.toString(), bumped.toString());
changelogFile.writeAsStringSync(changelog);
log("Updated version to '$bumped'.");
}
// Copyright (c) 2016, 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 'dart:math' as math;
import 'package:js/js.dart';
import 'package:dart_style/dart_style.dart';
@JS()
@anonymous
class FormatResult {
external factory FormatResult({String code, String error});
external String get code;
external String get error;
}
@JS('exports.formatCode')
external set formatCode(Function formatter);
void main() {
formatCode = allowInterop((String source) {
var formatter = new DartFormatter();
var exception;
try {
return new FormatResult(code: new DartFormatter().format(source));
} on FormatterException catch (err) {
// Couldn't parse it as a compilation unit.
exception = err;
}
// Maybe it's a statement.
try {
return new FormatResult(code: formatter.formatStatement(source));
} on FormatterException catch (err) {
// There is an error when parsing it both as a compilation unit and a
// statement, so we aren't sure which one the user intended. As a
// heuristic, we'll choose that whichever one we managed to parse more of
// before hitting an error is probably the right one.
if (_firstOffset(exception) < _firstOffset(err)) {
exception = err;
}
}
// If we get here, it couldn't be parsed at all.
return new FormatResult(code: source, error: "$exception");
});
}
/// Returns the offset of the error nearest the beginning of the file out of
/// all the errors in [exception].
int _firstOffset(FormatterException exception) =>
exception.errors.map((error) => error.offset).reduce(math.min);
library dart_style.src.string_compare;
/// Returns `true` if [c] represents a whitespace code unit allowed in Dart
/// source code.
bool _isWhitespace(int c) => (c <= 0x000D && c >= 0x0009) || c == 0x0020;
/// Returns the index of the next non-whitespace character.
///
/// Returns `true` if current contains a non-whitespace character.
/// Returns `false` if no characters are left.
int _moveNextNonWhitespace(String str, int len, int i) {
while (i < len && _isWhitespace(str.codeUnitAt(i))) {
i++;
}
return i;
}
/// Returns `true` if the strings are equal ignoring whitespace characters.
bool equalIgnoringWhitespace(String str1, String str2) {
// Benchmarks showed about a 20% regression in formatter performance when
// when we use the simpler to implement solution of stripping all
// whitespace characters and checking string equality. This solution is
// faster due to lower memory usage and poor performance concatting strings
// together one rune at a time.
var len1 = str1.length;
var len2 = str2.length;
var i1 = 0;
var i2 = 0;
while (true) {
i1 = _moveNextNonWhitespace(str1, len1, i1);
i2 = _moveNextNonWhitespace(str2, len2, i2);
if (i1 >= len1 || i2 >= len2) {
return (i1 >= len1) == (i2 >= len2);
}
if (str1[i1] != str2[i2]) return false;
i1++;
i2++;
}
}
// 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.argument_list_visitor;
import 'dart:math' as math;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'chunk.dart';
import 'rule/argument.dart';
import 'rule/rule.dart';
import 'source_visitor.dart';
/// Helper class for [SourceVisitor] that handles visiting and writing an
/// [ArgumentList], including all of the special code needed to handle
/// block-formatted arguments.
class ArgumentListVisitor {
final SourceVisitor _visitor;
/// The "(" before the argument list.
final Token _leftParenthesis;
/// The ")" after the argument list.
final Token _rightParenthesis;
/// All of the arguments, positional, named, and functions, in the argument
/// list.
final List<Expression> _allArguments;
/// The normal arguments preceding any block function arguments.
final ArgumentSublist _arguments;
/// The contiguous list of block function arguments, if any.
///
/// Otherwise, this is `null`.
final List<Expression> _functions;
/// If there are block function arguments, this is the arguments after them.
///
/// Otherwise, this is `null`.
final ArgumentSublist _argumentsAfterFunctions;
/// Returns `true` if there is only a single positional argument.
bool get _isSingle =>
_allArguments.length == 1 && _allArguments.single is! NamedExpression;
/// Whether this argument list has any arguments that should be formatted as
/// blocks.
// TODO(rnystrom): Returning true based on collections is non-optimal. It
// forces a method chain to break into two but the result collection may not
// actually split which can lead to a method chain that's allowed to break
// where it shouldn't.
bool get hasBlockArguments =>
_arguments._blocks.isNotEmpty || _functions != null;
factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) {
return new ArgumentListVisitor.forArguments(
visitor, node.leftParenthesis, node.rightParenthesis, node.arguments);
}
factory ArgumentListVisitor.forArguments(
SourceVisitor visitor,
Token leftParenthesis,
Token rightParenthesis,
List<Expression> arguments) {
// Look for a single contiguous range of block function arguments.
var functionsStart;
var functionsEnd;
for (var i = 0; i < arguments.length; i++) {
var argument = arguments[i];
if (_isBlockFunction(argument)) {
if (functionsStart == null) functionsStart = i;
// The functions must be one contiguous section.
if (functionsEnd != null && functionsEnd != i) {
functionsStart = null;
functionsEnd = null;
break;
}
functionsEnd = i + 1;
}
}
// Edge case: If all of the arguments are named, but they aren't all
// functions, then don't handle the functions specially. A function with a
// bunch of named arguments tends to look best when they are all lined up,
// even the function ones (unless they are all functions).
//
// Prefers:
//
// function(
// named: () {
// something();
// },
// another: argument);
//
// Over:
//
// function(named: () {
// something();
// },
// another: argument);
if (functionsStart != null &&
arguments[0] is NamedExpression &&
(functionsStart > 0 || functionsEnd < arguments.length)) {
functionsStart = null;
}
// Edge case: If all of the function arguments are named and there are
// other named arguments that are "=>" functions, then don't treat the
// block-bodied functions specially. In a mixture of the two function
// styles, it looks cleaner to treat them all like normal expressions so
// that the named arguments line up.
if (functionsStart != null &&
arguments[functionsStart] is NamedExpression) {
bool isArrow(NamedExpression named) {
var expression = named.expression;
if (expression is FunctionExpression) {
return expression.body is ExpressionFunctionBody;
}
return false;
}
for (var i = 0; i < functionsStart; i++) {
if (arguments[i] is! NamedExpression) continue;
if (isArrow(arguments[i])) {
functionsStart = null;
break;
}
}
for (var i = functionsEnd; i < arguments.length; i++) {
if (isArrow(arguments[i])) {
functionsStart = null;
break;
}
}
}
if (functionsStart == null) {
// No functions, so there is just a single argument list.
return new ArgumentListVisitor._(
visitor,
leftParenthesis,
rightParenthesis,
arguments,
new ArgumentSublist(arguments, arguments),
null,
null);
}
// Split the arguments into two independent argument lists with the
// functions in the middle.
var argumentsBefore = arguments.take(functionsStart).toList();
var functions = arguments.sublist(functionsStart, functionsEnd);
var argumentsAfter = arguments.skip(functionsEnd).toList();
return new ArgumentListVisitor._(
visitor,
leftParenthesis,
rightParenthesis,
arguments,
new ArgumentSublist(arguments, argumentsBefore),
functions,
new ArgumentSublist(arguments, argumentsAfter));
}
ArgumentListVisitor._(
this._visitor,
this._leftParenthesis,
this._rightParenthesis,
this._allArguments,
this._arguments,
this._functions,
this._argumentsAfterFunctions);
/// Builds chunks for the argument list.
void visit() {
// If there is just one positional argument, it tends to look weird to
// split before it, so try not to.
if (_isSingle) _visitor.builder.startSpan();
_visitor.builder.startSpan();
_visitor.token(_leftParenthesis);
_arguments.visit(_visitor);
_visitor.builder.endSpan();
if (_functions != null) {
// TODO(rnystrom): It might look better to treat the parameter list of the
// first function as if it were an argument in the preceding argument list
// instead of just having this little solo split here. That would try to
// keep the parameter list with other arguments when possible, and, I
// think, generally look nicer.
if (_functions.first == _allArguments.first) {
_visitor.soloZeroSplit();
} else {
_visitor.soloSplit();
}
for (var argument in _functions) {
if (argument != _functions.first) _visitor.space();
_visitor.visit(argument);
// Write the following comma.
if (argument.endToken.next.type == TokenType.COMMA) {
_visitor.token(argument.endToken.next);
}
}
_visitor.builder.startSpan();
_argumentsAfterFunctions.visit(_visitor);
_visitor.builder.endSpan();
}
_visitor.token(_rightParenthesis);
if (_isSingle) _visitor.builder.endSpan();
}
/// Returns `true` if [expression] is a [FunctionExpression] with a non-empty
/// block body.
static bool _isBlockFunction(Expression expression) {
if (expression is NamedExpression) {
expression = (expression as NamedExpression).expression;
}
// Allow functions wrapped in dotted method calls like "a.b.c(() { ... })".
if (expression is MethodInvocation) {
if (!_isValidWrappingTarget(expression.target)) return false;
if (expression.argumentList.arguments.length != 1) return false;
return _isBlockFunction(expression.argumentList.arguments.single);
}
if (expression is InstanceCreationExpression) {
if (expression.argumentList.arguments.length != 1) return false;
return _isBlockFunction(expression.argumentList.arguments.single);
}
// Allow immediately-invoked functions like "() { ... }()".
if (expression is FunctionExpressionInvocation) {
var invocation = expression as FunctionExpressionInvocation;
if (invocation.argumentList.arguments.isNotEmpty) return false;
expression = invocation.function;
}
// Unwrap parenthesized expressions.
while (expression is ParenthesizedExpression) {
expression = (expression as ParenthesizedExpression).expression;
}
// Must be a function.
if (expression is! FunctionExpression) return false;
// With a curly body.
var function = expression as FunctionExpression;
if (function.body is! BlockFunctionBody) return false;
// That isn't empty.
var body = function.body as BlockFunctionBody;
return body.block.statements.isNotEmpty ||
body.block.rightBracket.precedingComments != null;
}
/// Returns `true` if [expression] is a valid method invocation target for
/// an invocation that wraps a function literal argument.
static bool _isValidWrappingTarget(Expression expression) {
// Allow bare function calls.
if (expression == null) return true;
// Allow property accesses.
while (expression is PropertyAccess) {
expression = (expression as PropertyAccess).target;
}
if (expression is PrefixedIdentifier) return true;
if (expression is SimpleIdentifier) return true;
return false;
}
}
/// A range of arguments from a complete argument list.
///
/// One of these typically covers all of the arguments in an invocation. But,
/// when an argument list has block functions in the middle, the arguments
/// before and after the functions are treated as separate independent lists.
/// In that case, there will be two of these.
class ArgumentSublist {
/// The full argument list from the AST.
final List<Expression> _allArguments;
/// The positional arguments, in order.
final List<Expression> _positional;
/// The named arguments, in order.
final List<Expression> _named;
/// Maps each block argument, excluding functions, to the first token for that
/// argument.
final Map<Expression, Token> _blocks;
/// The number of leading block arguments, excluding functions.
///
/// If all arguments are blocks, this counts them.
final int _leadingBlocks;
/// The number of trailing blocks arguments.
///
/// If all arguments are blocks, this is zero.
final int _trailingBlocks;
/// The rule used to split the bodies of all block arguments.
Rule get blockRule => _blockRule;
Rule _blockRule;
/// The most recent chunk that split before an argument.
Chunk get previousSplit => _previousSplit;
Chunk _previousSplit;
factory ArgumentSublist(
List<Expression> allArguments, List<Expression> arguments) {
// Assumes named arguments follow all positional ones.
var positional =
arguments.takeWhile((arg) => arg is! NamedExpression).toList();
var named = arguments.skip(positional.length).toList();
var blocks = <Expression, Token>{};
for (var argument in arguments) {
var bracket = _blockToken(argument);
if (bracket != null) blocks[argument] = bracket;
}
// Count the leading arguments that are blocks.
var leadingBlocks = 0;
for (var argument in arguments) {
if (!blocks.containsKey(argument)) break;
leadingBlocks++;
}
// Count the trailing arguments that are blocks.
var trailingBlocks = 0;
if (leadingBlocks != arguments.length) {
for (var argument in arguments.reversed) {
if (!blocks.containsKey(argument)) break;
trailingBlocks++;
}
}
// Blocks must all be a prefix or suffix of the argument list (and not
// both).
if (leadingBlocks != blocks.length) leadingBlocks = 0;
if (trailingBlocks != blocks.length) trailingBlocks = 0;
// Ignore any blocks in the middle of the argument list.
if (leadingBlocks == 0 && trailingBlocks == 0) {
blocks.clear();
}
return new ArgumentSublist._(
allArguments, positional, named, blocks, leadingBlocks, trailingBlocks);
}
ArgumentSublist._(this._allArguments, this._positional, this._named,
this._blocks, this._leadingBlocks, this._trailingBlocks);
void visit(SourceVisitor visitor) {
if (_blocks.isNotEmpty) {
_blockRule = new Rule(Cost.splitBlocks);
}
var rule = _visitPositional(visitor);
_visitNamed(visitor, rule);
}
/// Writes the positional arguments, if any.
PositionalRule _visitPositional(SourceVisitor visitor) {
if (_positional.isEmpty) return null;
// Allow splitting after "(".
// Only count the blocks in the positional rule.
var leadingBlocks = math.min(_leadingBlocks, _positional.length);
var trailingBlocks = math.max(_trailingBlocks - _named.length, 0);
var rule = new PositionalRule(_blockRule, leadingBlocks, trailingBlocks);
_visitArguments(visitor, _positional, rule);
return rule;
}
/// Writes the named arguments, if any.
void _visitNamed(SourceVisitor visitor, PositionalRule positionalRule) {
if (_named.isEmpty) return;
// Only count the blocks in the named rule.
var leadingBlocks = math.max(_leadingBlocks - _positional.length, 0);
var trailingBlocks = math.min(_trailingBlocks, _named.length);
var namedRule = new NamedRule(_blockRule, leadingBlocks, trailingBlocks);
// Let the positional args force the named ones to split.
if (positionalRule != null) {
positionalRule.setNamedArgsRule(namedRule);
}
_visitArguments(visitor, _named, namedRule);
}
void _visitArguments(
SourceVisitor visitor, List<Expression> arguments, ArgumentRule rule) {
visitor.builder.startRule(rule);
// Split before the first argument.
_previousSplit =
visitor.builder.split(space: arguments.first != _allArguments.first);
rule.beforeArgument(_previousSplit);
// Try to not split the positional arguments.
if (arguments == _positional) {
visitor.builder.startSpan(Cost.positionalArguments);
}
for (var argument in arguments) {
_visitArgument(visitor, rule, argument);
// Write the split.
if (argument != arguments.last) {
_previousSplit = visitor.split();
rule.beforeArgument(_previousSplit);
}
}
if (arguments == _positional) visitor.builder.endSpan();
visitor.builder.endRule();
}
void _visitArgument(
SourceVisitor visitor, ArgumentRule rule, Expression argument) {
// If we're about to write a block argument, handle it specially.
if (_blocks.containsKey(argument)) {
rule.disableSplitOnInnerRules();
// Tell it to use the rule we've already created.
visitor.beforeBlock(_blocks[argument], this);
} else if (_allArguments.length > 1) {
// Edge case: Only bump the nesting if there are multiple arguments. This
// lets us avoid spurious indentation in cases like:
//
// function(function(() {
// body;
// }));
visitor.builder.startBlockArgumentNesting();
} else if (argument is! NamedExpression) {
// Edge case: Likewise, don't force the argument to split if there is
// only a single positional one, like:
//
// outer(inner(
// longArgument));
rule.disableSplitOnInnerRules();
}
if (argument is NamedExpression) {
visitor.visitNamedArgument(argument, rule as NamedRule);
} else {
visitor.visit(argument);
}
if (_blocks.containsKey(argument)) {
rule.enableSplitOnInnerRules();
} else if (_allArguments.length > 1) {
visitor.builder.endBlockArgumentNesting();
} else if (argument is! NamedExpression) {
rule.enableSplitOnInnerRules();
}
// Write the following comma.
if (argument.endToken.next.type == TokenType.COMMA) {
visitor.token(argument.endToken.next);
}
}
/// If [expression] can be formatted as a block, returns the token that opens
/// the block, such as a collection's bracket.
///
/// Block-formatted arguments can get special indentation to make them look
/// more statement-like.
static Token _blockToken(Expression expression) {
if (expression is NamedExpression) {
expression = (expression as NamedExpression).expression;
}
// TODO(rnystrom): Should we step into parenthesized expressions?
if (expression is ListLiteral) return expression.leftBracket;
if (expression is SetOrMapLiteral) return expression.leftBracket;
if (expression is SingleStringLiteral && expression.isMultiline) {
return expression.beginToken;
}
// Not a collection literal.
return null;
}
}
// Copyright (c) 2015, 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.rule.type_argument;
import '../chunk.dart';
import 'rule.dart';
/// Rule for splitting a list of type arguments or type parameters. Type
/// parameters split a little differently from normal value argument lists. In
/// particular, this tries harder to avoid splitting before the first type
/// argument since that looks stranger with `<...>` than it does with `(...)`.
///
/// The values for a rule for `n` arguments are:
///
/// * `0`: No splits at all.
/// * `1 ... n`: Split before one argument, starting from the last.
/// * `n + 1`: Split before all arguments.
///
/// If there is only one type argument, the last two cases collapse and there
/// are only two values.
class TypeArgumentRule extends Rule {
/// The chunks prior to each positional type argument.
final List<Chunk> _arguments = [];
int get cost => Cost.typeArgument;
int get numValues => _arguments.length == 1 ? 2 : _arguments.length + 2;
/// Remembers [chunk] as containing the split that occurs right before a type
/// argument in the list.
void beforeArgument(Chunk chunk) {
_arguments.add(chunk);
}
bool isSplit(int value, Chunk chunk) {
// Don't split at all.
if (value == Rule.unsplit) return false;
// Split before every argument.
if (value == numValues - 1) return true;
// Split before a single argument. Try later arguments before earlier ones
// to try to keep as much on the first line as possible.
return chunk == _arguments[_arguments.length - value];
}
String toString() => "TypeArg${super.toString()}";
}
// Copyright (c) 2015, 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.rule.combinator;
import '../chunk.dart';
import 'rule.dart';
/// Handles a list of "combinators".
///
/// A combinator is a keyword followed by a list of nodes used to modify some
/// declaration. It's used for actual hide and show combinators as well as
/// "with" and "implements" clauses in class declarations.
///
/// Combinators can be split in a few different ways:
///
/// // All on one line:
/// import 'animals.dart' show Ant hide Cat;
///
/// // Wrap before each keyword:
/// import 'animals.dart'
/// show Ant, Baboon
/// hide Cat;
///
/// // Wrap either or both of the name lists:
/// import 'animals.dart'
/// show
/// Ant,
/// Baboon
/// hide Cat;
///
/// These are not allowed:
///
/// // Wrap list but not keyword:
/// import 'animals.dart' show
/// Ant,
/// Baboon
/// hide Cat;
///
/// // Wrap one keyword but not both:
/// import 'animals.dart'
/// show Ant, Baboon hide Cat;
///
/// This ensures that when any wrapping occurs, the keywords are always at
/// the beginning of the line.
class CombinatorRule extends Rule {
/// The set of chunks before the combinators.
final Set<Chunk> _combinators = new Set();
/// A list of sets of chunks prior to each name in a combinator.
///
/// The outer list is a list of combinators (i.e. "hide", "show", etc.). Each
/// inner set is the set of names for that combinator.
final List<Set<Chunk>> _names = [];
int get numValues {
var count = 2; // No wrapping, or wrap just before each combinator.
if (_names.length == 2) {
count += 3; // Wrap first set of names, second, or both.
} else {
assert(_names.length == 1);
count++; // Wrap the names.
}
return count;
}
/// Adds a new combinator to the list of combinators.
///
/// This must be called before adding any names.
void addCombinator(Chunk chunk) {
_combinators.add(chunk);
_names.add(new Set());
}
/// Adds a chunk prior to a name to the current combinator.
void addName(Chunk chunk) {
_names.last.add(chunk);
}
bool isSplitAtValue(int value, Chunk chunk) {
switch (value) {
case 1:
// Just split at the combinators.
return _combinators.contains(chunk);
case 2:
// Split at the combinators and the first set of names.
return _isCombinatorSplit(0, chunk);
case 3:
// If there is two combinators, just split at the combinators and the
// second set of names.
if (_names.length == 2) {
// Two sets of combinators, so just split at the combinators and the
// second set of names.
return _isCombinatorSplit(1, chunk);
}
// Split everything.
return true;
default:
return true;
}
}
/// Returns `true` if [chunk] is for a combinator or a name in the
/// combinator at index [combinator].
bool _isCombinatorSplit(int combinator, Chunk chunk) {
return _combinators.contains(chunk) || _names[combinator].contains(chunk);
}
String toString() => "Comb${super.toString()}";
}
// Copyright (c) 2015, 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.rule.rule;
import '../chunk.dart';
import '../fast_hash.dart';
/// A constraint that determines the different ways a related set of chunks may
/// be split.
class Rule extends FastHash {
/// Rule value that splits no chunks.
///
/// Every rule is required to treat this value as fully unsplit.
static const unsplit = 0;
/// Rule constraint value that means "any value as long as something splits".
///
/// It disallows [unsplit] but allows any other value.
static const mustSplit = -1;
/// The number of different states this rule can be in.
///
/// Each state determines which set of chunks using this rule are split and
/// which aren't. Values range from zero to one minus this. Value zero
/// always means "no chunks are split" and increasing values by convention
/// mean increasingly undesirable splits.
///
/// By default, a rule has two values: fully unsplit and fully split.
int get numValues => 2;
/// The rule value that forces this rule into its maximally split state.
///
/// By convention, this is the highest of the range of allowed values.
int get fullySplitValue => numValues - 1;
int get cost => _cost;
final int _cost;
/// During line splitting [LineSplitter] sets this to the index of this
/// rule in its list of rules.
int index;
/// If `true`, the rule has been "hardened" meaning it's been placed into a
/// permanent "must fully split" state.
bool get isHardened => _isHardened;
bool _isHardened = false;
/// The other [Rule]s that are implied this one.
///
/// In many cases, if a split occurs inside an expression, surrounding rules
/// also want to split too. For example, a split in the middle of an argument
/// forces the entire argument list to also split.
///
/// This tracks those relationships. If this rule splits, (sets its value to
/// [fullySplitValue]) then all of the surrounding implied rules are also set
/// to their fully split value.
///
/// This contains all direct as well as transitive relationships. If A
/// contains B which contains C, C's outerRules contains both B and A.
final Set<Rule> _implied = new Set<Rule>();
/// Marks [other] as implied by this one.
///
/// That means that if this rule splits, then [other] is force to split too.
void imply(Rule other) {
_implied.add(other);
}
/// Whether this rule cares about rules that it contains.
///
/// If `true` then inner rules will constrain this one and force it to split
/// when they split. Otherwise, it can split independently of any contained
/// rules.
bool get splitsOnInnerRules => true;
Rule([int cost]) : _cost = cost ?? Cost.normal;
/// Creates a new rule that is already fully split.
Rule.hard() : _cost = 0 {
// Set the cost to zero since it will always be applied, so there's no
// point in penalizing it.
//
// Also, this avoids doubled counting in literal blocks where there is both
// a split in the outer chunk containing the block and the inner hard split
// between the elements or statements.
harden();
}
/// Fixes this rule into a "fully split" state.
void harden() {
_isHardened = true;
}
/// Returns `true` if [chunk] should split when this rule has [value].
bool isSplit(int value, Chunk chunk) {
if (_isHardened) return true;
if (value == Rule.unsplit) return false;
// Let the subclass decide.
return isSplitAtValue(value, chunk);
}
/// Subclasses can override this to determine which values split which chunks.
///
/// By default, this assumes every chunk splits.
bool isSplitAtValue(int value, Chunk chunk) => true;
/// Given that this rule has [value], determine if [other]'s value should be
/// constrained.
///
/// Allows relationships between rules like "if I split, then this should
/// split too". Returns a non-negative value to force [other] to take that
/// value. Returns -1 to allow [other] to take any non-zero value. Returns
/// null to not constrain other.
int constrain(int value, Rule other) {
// By default, any containing rule will be fully split if this one is split.
if (value == Rule.unsplit) return null;
if (_implied.contains(other)) return other.fullySplitValue;
return null;
}
/// A protected method for subclasses to add the rules that they constrain
/// to [rules].
///
/// Called by [Rule] the first time [constrainedRules] is accessed.
void addConstrainedRules(Set<Rule> rules) {}
/// Discards constraints on any rule that doesn't have an index.
///
/// This is called by [LineSplitter] after it has indexed all of the in-use
/// rules. A rule may end up with a constraint on a rule that's no longer
/// used by any chunk. This can happen if the rule gets hardened, or if it
/// simply never got used by a chunk. For example, a rule for splitting an
/// empty list of metadata annotations.
///
/// This removes all of those.
void forgetUnusedRules() {
_implied.retainWhere((rule) => rule.index != null);
// Clear the cached ones too.
_constrainedRules = null;
_allConstrainedRules = null;
}
/// The other [Rule]s that this rule places immediate constraints on.
Set<Rule> get constrainedRules {
// Lazy initialize this on first use. Note: Assumes this is only called
// after the chunks have been written and any constraints have been wired
// up.
if (_constrainedRules == null) {
_constrainedRules = _implied.toSet();
addConstrainedRules(_constrainedRules);
}
return _constrainedRules;
}
Set<Rule> _constrainedRules;
/// The transitive closure of all of the rules this rule places constraints
/// on, directly or indirectly, including itself.
Set<Rule> get allConstrainedRules {
if (_allConstrainedRules == null) {
visit(Rule rule) {
if (_allConstrainedRules.contains(rule)) return;
_allConstrainedRules.add(rule);
rule.constrainedRules.forEach(visit);
}
_allConstrainedRules = new Set();
visit(this);
}
return _allConstrainedRules;
}
Set<Rule> _allConstrainedRules;
String toString() => "$id";
}
// Copyright (c) 2015, 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.rule.metadata;
import 'argument.dart';
import 'rule.dart';
/// Rule for handling splits between parameter metadata annotations and the
/// following parameter.
///
/// Metadata annotations for parameters (and type parameters) get some special
/// handling. We use a single rule for all annotations in the parameter list.
/// If any of the annotations split, they all do.
///
/// Also, if the annotations split, we force the entire parameter list to fully
/// split, both named and positional.
class MetadataRule extends Rule {
Rule _positionalRule;
Rule _namedRule;
/// Remembers that [rule] is the [PositionalRule] used by the argument list
/// containing the parameter metadata using this rule.
void bindPositionalRule(PositionalRule rule) {
_positionalRule = rule;
}
/// Remembers that [rule] is the [NamedRule] used by the argument list
/// containing the parameter metadata using this rule.
void bindNamedRule(NamedRule rule) {
_namedRule = rule;
}
/// Constrains the surrounding argument list rules to fully split if the
/// metadata does.
int constrain(int value, Rule other) {
var constrained = super.constrain(value, other);
if (constrained != null) return constrained;
// If the metadata doesn't split, we don't care what the arguments do.
if (value == Rule.unsplit) return null;
// Otherwise, they have to split.
if (other == _positionalRule) return _positionalRule.fullySplitValue;
if (other == _namedRule) return _namedRule.fullySplitValue;
return null;
}
void addConstrainedRules(Set<Rule> rules) {
if (_positionalRule != null) rules.add(_positionalRule);
if (_namedRule != null) rules.add(_namedRule);
}
void forgetUnusedRules() {
super.forgetUnusedRules();
if (_positionalRule != null && _positionalRule.index == null) {
_positionalRule = null;
}
if (_namedRule != null && _namedRule.index == null) {
_namedRule = null;
}
}
}
// Copyright (c) 2015, 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.rule.argument;
import '../chunk.dart';
import 'rule.dart';
/// Base class for a rule that handles argument or parameter lists.
abstract class ArgumentRule extends Rule {
/// The chunks prior to each positional argument.
final List<Chunk> _arguments = [];
/// The rule used to split collections in the argument list, if any.
Rule _collectionRule;
/// The number of leading collection arguments.
///
/// This and [_trailingCollections] cannot both be positive. If every
/// argument is a collection, this will be [_arguments.length] and
/// [_trailingCollections] will be 0.
final int _leadingCollections;
/// The number of trailing collections.
///
/// This and [_leadingCollections] cannot both be positive.
final int _trailingCollections;
/// If true, then inner rules that are written will force this rule to split.
///
/// Temporarily disabled while writing collection arguments so that they can
/// be multi-line without forcing the whole argument list to split.
bool _trackInnerRules = true;
/// Don't split when an inner collection rule splits.
bool get splitsOnInnerRules => _trackInnerRules;
ArgumentRule(this._collectionRule, this._leadingCollections,
this._trailingCollections);
void addConstrainedRules(Set<Rule> rules) {
super.addConstrainedRules(rules);
if (_collectionRule != null) rules.add(_collectionRule);
}
void forgetUnusedRules() {
super.forgetUnusedRules();
if (_collectionRule != null && _collectionRule.index == null) {
_collectionRule = null;
}
}
/// Remembers [chunk] as containing the split that occurs right before an
/// argument in the list.
void beforeArgument(Chunk chunk) {
_arguments.add(chunk);
}
/// Disables tracking inner rules while a collection argument is written.
void disableSplitOnInnerRules() {
assert(_trackInnerRules == true);
_trackInnerRules = false;
}
/// Re-enables tracking inner rules.
void enableSplitOnInnerRules() {
assert(_trackInnerRules == false);
_trackInnerRules = true;
}
}
/// Rule for handling positional argument lists.
///
/// The number of values is based on the number of arguments and whether or not
/// there are bodies. The first two values are always:
///
/// * 0: Do not split at all.
/// * 1: Split only before the first argument.
///
/// Then there is a value for each argument, to split before that argument.
/// These values work back to front. So, for a two-argument list, value 2 splits
/// after the second argument and value 3 splits after the first.
///
/// Then there is a value that splits before every argument.
///
/// Finally, if there are collection arguments, there is another value that
/// splits before all of the non-collection arguments, but does not split
/// before the collections, so that they can split internally.
class PositionalRule extends ArgumentRule {
/// If there are named arguments following these positional ones, this will
/// be their rule.
Rule _namedArgsRule;
/// Creates a new rule for a positional argument list.
///
/// If [_collectionRule] is given, it is the rule used to split the collection
/// arguments in the list.
PositionalRule(
Rule collectionRule, int leadingCollections, int trailingCollections)
: super(collectionRule, leadingCollections, trailingCollections);
int get numValues {
// Can split before any one argument or none.
var result = _arguments.length + 1;
// If there are multiple arguments, can split before all of them.
if (_arguments.length > 1) result++;
// When there are collection arguments, there are two ways we can split on
// "all" arguments:
//
// - Split on just the non-collection arguments, and force the collection
// arguments to split internally.
// - Split on all of them including the collection arguments, and do not
// allow the collection arguments to split internally.
if (_leadingCollections > 0 || _trailingCollections > 0) result++;
return result;
}
void addConstrainedRules(Set<Rule> rules) {
super.addConstrainedRules(rules);
if (_namedArgsRule != null) rules.add(_namedArgsRule);
}
void forgetUnusedRules() {
super.forgetUnusedRules();
if (_namedArgsRule != null && _namedArgsRule.index == null) {
_namedArgsRule = null;
}
}
bool isSplitAtValue(int value, Chunk chunk) {
// Split only before the first argument. Keep the entire argument list
// together on the next line.
if (value == 1) return chunk == _arguments.first;
// Split before a single argument. Try later arguments before earlier ones
// to try to keep as much on the first line as possible.
if (value <= _arguments.length) {
var argument = _arguments.length - value + 1;
return chunk == _arguments[argument];
}
// Only split before the non-collection arguments.
if (value == _arguments.length + 1) {
for (var i = 0; i < _leadingCollections; i++) {
if (chunk == _arguments[i]) return false;
}
for (var i = _arguments.length - _trailingCollections;
i < _arguments.length;
i++) {
if (chunk == _arguments[i]) return false;
}
return true;
}
// Split before all of the arguments, even the collections.
return true;
}
/// Remembers that [rule] is the [Rule] immediately following this positional
/// positional argument list.
///
/// This is normally a [NamedRule] but [PositionalRule] is also used for the
/// property accesses at the beginning of a call chain, in which case this
/// is just a [SimpleRule].
void setNamedArgsRule(Rule rule) {
_namedArgsRule = rule;
}
/// Constrains the named argument list to at least move to the next line if
/// there are any splits in the positional arguments. Prevents things like:
///
/// function(
/// argument,
/// argument, named: argument);
int constrain(int value, Rule other) {
var constrained = super.constrain(value, other);
if (constrained != null) return constrained;
// Handle the relationship between the positional and named args.
if (other == _namedArgsRule) {
// If the positional args are one-per-line, the named args are too.
if (value == fullySplitValue) return _namedArgsRule.fullySplitValue;
// Otherwise, if there is any split in the positional arguments, don't
// allow the named arguments on the same line as them.
if (value != 0) return -1;
}
// Decide how to constrain the collection rule.
if (other != _collectionRule) return null;
// If all of the collections are in the named arguments, [_collectionRule]
// will not be null, but we don't have to handle it.
if (_leadingCollections == 0 && _trailingCollections == 0) return null;
// If we aren't splitting any args, we can split the collection.
if (value == Rule.unsplit) return null;
// Split only before the first argument.
if (value == 1) {
if (_leadingCollections > 0) {
// We are splitting before a collection, so don't let it split
// internally.
return Rule.unsplit;
} else {
// The split is outside of the collections so they can split or not.
return null;
}
}
// Split before a single argument. If it's in the middle of the collection
// arguments, don't allow them to split.
if (value <= _arguments.length) {
var argument = _arguments.length - value + 1;
if (argument < _leadingCollections ||
argument >= _arguments.length - _trailingCollections) {
return Rule.unsplit;
}
return null;
}
// Only split before the non-collection arguments. This case only comes into
// play when we do want to split the collection, so force that here.
if (value == _arguments.length + 1) return 1;
// Split before all of the arguments, even the collections. We'll allow
// them to split but indent their bodies if they do.
return null;
}
String toString() => "Pos${super.toString()}";
}
/// Splitting rule for a list of named arguments or parameters. Its values mean:
///
/// * Do not split at all.
/// * Split only before first argument.
/// * Split before all arguments.
class NamedRule extends ArgumentRule {
int get numValues => 3;
NamedRule(
Rule collectionRule, int leadingCollections, int trailingCollections)
: super(collectionRule, leadingCollections, trailingCollections);
bool isSplitAtValue(int value, Chunk chunk) {
// Move all arguments to the second line as a unit.
if (value == 1) return chunk == _arguments.first;
// Otherwise, split before all arguments.
return true;
}
int constrain(int value, Rule other) {
var constrained = super.constrain(value, other);
if (constrained != null) return constrained;
// Decide how to constrain the collection rule.
if (other != _collectionRule) return null;
// If all of the collections are in the named arguments, [_collectionRule]
// will not be null, but we don't have to handle it.
if (_leadingCollections == 0 && _trailingCollections == 0) return null;
// If we aren't splitting any args, we can split the collection.
if (value == Rule.unsplit) return null;
// Split only before the first argument. Don't allow the collections to
// split.
if (value == 1) return Rule.unsplit;
// Split before all of the arguments, even the collections. We'll allow
// them to split but indent their bodies if they do.
return null;
}
String toString() => "Named${super.toString()}";
}
// 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.chunk_builder;
import 'chunk.dart';
import 'dart_formatter.dart';
import 'debug.dart' as debug;
import 'line_writer.dart';
import 'nesting_builder.dart';
import 'nesting_level.dart';
import 'rule/rule.dart';
import 'source_code.dart';
import 'style_fix.dart';
import 'whitespace.dart';
/// Matches if the last character of a string is an identifier character.
final _trailingIdentifierChar = new RegExp(r"[a-zA-Z0-9_]$");
/// Matches a JavaDoc-style doc comment that starts with "/**" and ends with
/// "*/" or "**/".
final _javaDocComment = new RegExp(r"^/\*\*([^*/][\s\S]*?)\*?\*/$");
/// Matches the leading "*" in a line in the middle of a JavaDoc-style comment.
var _javaDocLine = new RegExp(r"^\s*\*(.*)");
/// Takes the incremental serialized output of [SourceVisitor]--the source text
/// along with any comments and preserved whitespace--and produces a coherent
/// tree of [Chunk]s which can then be split into physical lines.
///
/// Keeps track of leading indentation, expression nesting, and all of the hairy
/// code required to seamlessly integrate existing comments into the pure
/// output produced by [SourceVisitor].
class ChunkBuilder {
final DartFormatter _formatter;
/// The builder for the code surrounding the block that this writer is for, or
/// `null` if this is writing the top-level code.
final ChunkBuilder _parent;
final SourceCode _source;
final List<Chunk> _chunks;
/// The whitespace that should be written to [_chunks] before the next
/// non-whitespace token.
///
/// This ensures that changes to indentation and nesting also apply to the
/// most recent split, even if the visitor "creates" the split before changing
/// indentation or nesting.
Whitespace _pendingWhitespace = Whitespace.none;
/// The nested stack of rules that are currently in use.
///
/// New chunks are implicitly split by the innermost rule when the chunk is
/// ended.
final _rules = <Rule>[];
/// The set of rules known to contain hard splits that will in turn force
/// these rules to harden.
///
/// This is accumulated lazily while chunks are being built. Then, once they
/// are all done, the rules are all hardened. We do this later because some
/// rules may not have all of their constraints fully wired up until after
/// the hard split appears. For example, a hard split in a positional
/// argument list needs to force the named arguments to split too, but we
/// don't create that rule until after the positional arguments are done.
final _hardSplitRules = new Set<Rule>();
/// The list of rules that are waiting until the next whitespace has been
/// written before they start.
final _lazyRules = <Rule>[];
/// The nested stack of spans that are currently being written.
final _openSpans = <OpenSpan>[];
/// The current state.
final _nesting = new NestingBuilder();
/// The stack of nesting levels where block arguments may start.
///
/// A block argument's contents will nest at the last level in this stack.
final _blockArgumentNesting = <NestingLevel>[];
/// The index of the "current" chunk being written.
///
/// If the last chunk is still being appended to, this is its index.
/// Otherwise, it is the index of the next chunk which will be created.
int get _currentChunkIndex {
if (_chunks.isEmpty) return 0;
if (_chunks.last.canAddText) return _chunks.length - 1;
return _chunks.length;
}
/// Whether or not there was a leading comment that was flush left before any
/// other content was written.
///
/// This is used when writing child blocks to make the parent chunk have the
/// right flush left value when a comment appears immediately inside the
/// block.
bool _firstFlushLeft = false;
/// The number of calls to [preventSplit()] that have not been ended by a
/// call to [endPreventSplit()].
///
/// Splitting is completely disabled inside string interpolation. We do want
/// to fix the whitespace inside interpolation, though, so we still format
/// them. This tracks whether we're inside an interpolation. We can't use a
/// simple bool because interpolation can nest.
///
/// When this is non-zero, splits are ignored.
int _preventSplitNesting = 0;
/// Whether there is pending whitespace that depends on the number of
/// newlines in the source.
///
/// This is used to avoid calculating the newlines between tokens unless
/// actually needed since doing so is slow when done between every single
/// token pair.
bool get needsToPreserveNewlines =>
_pendingWhitespace == Whitespace.oneOrTwoNewlines ||
_pendingWhitespace == Whitespace.splitOrTwoNewlines ||
_pendingWhitespace == Whitespace.splitOrNewline;
/// The number of characters of code that can fit in a single line.
int get pageWidth => _formatter.pageWidth;
/// The current innermost rule.
Rule get rule => _rules.last;
ChunkBuilder(this._formatter, this._source)
: _parent = null,
_chunks = [] {
indent(_formatter.indent);
startBlockArgumentNesting();
}
ChunkBuilder._(this._parent, this._formatter, this._source, this._chunks) {
startBlockArgumentNesting();
}
/// Writes [string], the text for a single token, to the output.
///
/// By default, this also implicitly adds one level of nesting if we aren't
/// currently nested at all. We do this here so that if a comment appears
/// after any token within a statement or top-level form and that comment
/// leads to splitting, we correctly nest. Even pathological cases like:
///
///
/// import // comment
/// "this_gets_nested.dart";
///
/// If we didn't do this here, we'd have to call [nestExpression] after the
/// first token of practically every grammar production.
void write(String string) {
_emitPendingWhitespace();
_writeText(string);
_lazyRules.forEach(_activateRule);
_lazyRules.clear();
_nesting.commitNesting();
}
/// Writes a [WhitespaceChunk] of [type].
void writeWhitespace(Whitespace type) {
_pendingWhitespace = type;
}
/// Write a split owned by the current innermost rule.
///
/// If [flushLeft] is `true`, then forces the next line to start at column
/// one regardless of any indentation or nesting.
///
/// If [isDouble] is passed, forces the split to either be a single or double
/// newline. Otherwise, leaves it indeterminate.
///
/// If [nest] is `false`, ignores any current expression nesting. Otherwise,
/// uses the current nesting level. If unsplit, it expands to a space if
/// [space] is `true`.
Chunk split({bool flushLeft, bool isDouble, bool nest, bool space}) {
space ??= false;
// If we are not allowed to split at all, don't. Returning null for the
// chunk is safe since the rule that uses the chunk will itself get
// discarded because no chunk references it.
if (_preventSplitNesting > 0) {
if (space) _pendingWhitespace = Whitespace.space;
return null;
}
return _writeSplit(_rules.last,
flushLeft: flushLeft, isDouble: isDouble, nest: nest, space: space);
}
/// Outputs the series of [comments] and associated whitespace that appear
/// before [token] (which is not written by this).
///
/// The list contains each comment as it appeared in the source between the
/// last token written and the next one that's about to be written.
///
/// [linesBeforeToken] is the number of lines between the last comment (or
/// previous token if there are no comments) and the next token.
void writeComments(
List<SourceComment> comments, int linesBeforeToken, String token) {
// Edge case: if we require a blank line, but there exists one between
// some of the comments, or after the last one, then we don't need to
// enforce one before the first comment. Example:
//
// library foo;
// // comment
//
// class Bar {}
//
// Normally, a blank line is required after `library`, but since there is
// one after the comment, we don't need one before it. This is mainly so
// that commented out directives stick with their preceding group.
if (_pendingWhitespace == Whitespace.twoNewlines &&
comments.first.linesBefore < 2) {
if (linesBeforeToken > 1) {
_pendingWhitespace = Whitespace.newline;
} else {
for (var i = 1; i < comments.length; i++) {
if (comments[i].linesBefore > 1) {
_pendingWhitespace = Whitespace.newline;
break;
}
}
}
}
// Edge case: if the previous output was also from a call to
// [writeComments()] which ended with a line comment, force a newline.
// Normally, comments are strictly interleaved with tokens and you never
// get two sequences of comments in a row. However, when applying a fix
// that removes a token (like `new`), it's possible to get two sets of
// comments in a row, as in:
//
// // a
// new // b
// Foo();
//
// When that happens, we need to make sure the preserve the split at the
// end of the first sequence of comments if there is one.
if (_pendingWhitespace == null) {
comments.first.linesBefore = 1;
_pendingWhitespace = Whitespace.none;
}
// Edge case: if the comments are completely inline (i.e. just a series of
// block comments with no newlines before, after, or between them), then
// they will eat any pending newlines. Make sure that doesn't happen by
// putting the pending whitespace before the first comment and moving them
// to their own line. Turns this:
//
// library foo; /* a */ /* b */ import 'a.dart';
//
// into:
//
// library foo;
//
// /* a */ /* b */
// import 'a.dart';
if (linesBeforeToken == 0 &&
comments.every((comment) => comment.isInline) &&
_pendingWhitespace.minimumLines > 0) {
comments.first.linesBefore = _pendingWhitespace.minimumLines;
linesBeforeToken = 1;
}
// Write each comment and the whitespace between them.
for (var i = 0; i < comments.length; i++) {
var comment = comments[i];
preserveNewlines(comment.linesBefore);
// Don't emit a space because we'll handle it below. If we emit it here,
// we may get a trailing space if the comment needs a line before it.
if (_pendingWhitespace == Whitespace.space) {
_pendingWhitespace = Whitespace.none;
}
_emitPendingWhitespace();
if (comment.linesBefore == 0) {
// If we're sitting on a split, move the comment before it to adhere it
// to the preceding text.
if (_shouldMoveCommentBeforeSplit(comment.text)) {
_chunks.last.allowText();
}
// The comment follows other text, so we need to decide if it gets a
// space before it or not.
if (_needsSpaceBeforeComment(comment)) _writeText(" ");
} else {
// The comment starts a line, so make sure it stays on its own line.
_writeHardSplit(
flushLeft: comment.flushLeft,
isDouble: comment.linesBefore > 1,
nest: true);
}
_writeCommentText(comment);
if (comment.selectionStart != null) {
startSelectionFromEnd(comment.text.length - comment.selectionStart);
}
if (comment.selectionEnd != null) {
endSelectionFromEnd(comment.text.length - comment.selectionEnd);
}
// Make sure there is at least one newline after a line comment and allow
// one or two after a block comment that has nothing after it.
var linesAfter;
if (i < comments.length - 1) {
linesAfter = comments[i + 1].linesBefore;
} else {
linesAfter = linesBeforeToken;
// Always force a newline after multi-line block comments. Prevents
// mistakes like:
//
// /**
// * Some doc comment.
// */ someFunction() { ... }
if (linesAfter == 0 && comments.last.text.contains("\n")) {
linesAfter = 1;
}
}
if (linesAfter > 0) _writeHardSplit(isDouble: linesAfter > 1, nest: true);
}
// If the comment has text following it (aside from a grouping character),
// it needs a trailing space.
if (_needsSpaceAfterLastComment(comments, token)) {
_pendingWhitespace = Whitespace.space;
}
preserveNewlines(linesBeforeToken);
}
/// Writes the text of [comment].
///
/// If it's a JavaDoc comment that should be fixed to use `///`, fixes it.
void _writeCommentText(SourceComment comment) {
if (!_formatter.fixes.contains(StyleFix.docComments)) {
_writeText(comment.text);
return;
}
// See if it's a JavaDoc comment.
var match = _javaDocComment.firstMatch(comment.text);
if (match == null) {
_writeText(comment.text);
return;
}
// Remove a leading "*" from the middle lines.
var lines = match.group(1).split("\n").toList();
for (var i = 1; i < lines.length - 1; i++) {
var line = lines[i];
var match = _javaDocLine.firstMatch(line);
if (match != null) {
line = match.group(1);
} else {
// Note that this may remove deliberate leading whitespace. In tests on
// a large corpus, though, I couldn't find examples of that.
line = line.trimLeft();
}
lines[i] = line;
}
// Trim the first and last lines if empty.
if (lines.first.trim().isEmpty) lines.removeAt(0);
if (lines.isNotEmpty && lines.last.trim().isEmpty) lines.removeLast();
// Don't completely eliminate an empty block comment.
if (lines.isEmpty) lines.add("");
for (var line in lines) {
if (line.isNotEmpty && !line.startsWith(" ")) line = " $line";
_writeText("///${line.trimRight()}");
_pendingWhitespace = Whitespace.newline;
_emitPendingWhitespace();
}
}
/// If the current pending whitespace allows some source discretion, pins
/// that down given that the source contains [numLines] newlines at that
/// point.
void preserveNewlines(int numLines) {
// If we didn't know how many newlines the user authored between the last
// token and this one, now we do.
switch (_pendingWhitespace) {
case Whitespace.splitOrNewline:
if (numLines > 0) {
_pendingWhitespace = Whitespace.nestedNewline;
} else {
_pendingWhitespace = Whitespace.none;
split(space: true);
}
break;
case Whitespace.splitOrTwoNewlines:
if (numLines > 1) {
_pendingWhitespace = Whitespace.twoNewlines;
} else {
_pendingWhitespace = Whitespace.none;
split(space: true);
}
break;
case Whitespace.oneOrTwoNewlines:
if (numLines > 1) {
_pendingWhitespace = Whitespace.twoNewlines;
} else {
_pendingWhitespace = Whitespace.newline;
}
break;
}
}
/// Creates a new indentation level [spaces] deeper than the current one.
///
/// If omitted, [spaces] defaults to [Indent.block].
void indent([int spaces]) {
_nesting.indent(spaces);
}
/// Discards the most recent indentation level.
void unindent() {
_nesting.unindent();
}
/// Starts a new span with [cost].
///
/// Each call to this needs a later matching call to [endSpan].
void startSpan([int cost = Cost.normal]) {
_openSpans.add(new OpenSpan(_currentChunkIndex, cost));
}
/// Ends the innermost span.
void endSpan() {
var openSpan = _openSpans.removeLast();
// A span that just covers a single chunk can't be split anyway.
var end = _currentChunkIndex;
if (openSpan.start == end) return;
// Add the span to every chunk that can split it.
var span = new Span(openSpan.cost);
for (var i = openSpan.start; i < end; i++) {
var chunk = _chunks[i];
if (!chunk.rule.isHardened) chunk.spans.add(span);
}
}
/// Starts a new [Rule].
///
/// If omitted, defaults to a new [Rule].
void startRule([Rule rule]) {
if (rule == null) rule = new Rule();
// If there are any pending lazy rules, start them now so that the proper
// stack ordering of rules is maintained.
_lazyRules.forEach(_activateRule);
_lazyRules.clear();
_activateRule(rule);
}
void _activateRule(Rule rule) {
// See if any of the rules that contain this one care if it splits.
_rules.forEach((outer) {
if (!outer.splitsOnInnerRules) return;
rule.imply(outer);
});
_rules.add(rule);
}
/// Starts a new [Rule] that comes into play *after* the next whitespace
/// (including comments) is written.
///
/// This is used for operators who want to start a rule before the first
/// operand but not get forced to split if a comment appears before the
/// entire expression.
///
/// If [rule] is omitted, defaults to a new [Rule].
void startLazyRule([Rule rule]) {
if (rule == null) rule = new Rule();
_lazyRules.add(rule);
}
/// Ends the innermost rule.
void endRule() {
if (_lazyRules.isNotEmpty) {
_lazyRules.removeLast();
} else {
_rules.removeLast();
}
}
/// Pre-emptively forces all of the current rules to become hard splits.
///
/// This is called by [SourceVisitor] when it can determine that a rule will
/// will always be split. Turning it (and the surrounding rules) into hard
/// splits lets the writer break the output into smaller pieces for the line
/// splitter, which helps performance and avoids failing on very large input.
///
/// In particular, it's easy for the visitor to know that collections with a
/// large number of items must split. Doing that early avoids crashing the
/// splitter when it tries to recurse on huge collection literals.
void forceRules() => _handleHardSplit();
/// Begins a new expression nesting level [indent] spaces deeper than the
/// current one if it splits.
///
/// If [indent] is omitted, defaults to [Indent.expression]. If [now] is
/// `true`, commits the nesting change immediately instead of waiting until
/// after the next chunk of text is written.
void nestExpression({int indent, bool now}) {
if (now == null) now = false;
_nesting.nest(indent);
if (now) _nesting.commitNesting();
}
/// Discards the most recent level of expression nesting.
///
/// Expressions that are more nested will get increased indentation when split
/// if the previous line has a lower level of nesting.
///
/// If [now] is `false`, does not commit the nesting change until after the
/// next chunk of text is written.
void unnest({bool now}) {
if (now == null) now = true;
_nesting.unnest();
if (now) _nesting.commitNesting();
}
/// Marks the selection starting point as occurring [fromEnd] characters to
/// the left of the end of what's currently been written.
///
/// It counts backwards from the end because this is called *after* the chunk
/// of text containing the selection has been output.
void startSelectionFromEnd(int fromEnd) {
assert(_chunks.isNotEmpty);
_chunks.last.startSelectionFromEnd(fromEnd);
}
/// Marks the selection ending point as occurring [fromEnd] characters to the
/// left of the end of what's currently been written.
///
/// It counts backwards from the end because this is called *after* the chunk
/// of text containing the selection has been output.
void endSelectionFromEnd(int fromEnd) {
assert(_chunks.isNotEmpty);
_chunks.last.endSelectionFromEnd(fromEnd);
}
/// Captures the current nesting level as marking where subsequent block
/// arguments should start.
void startBlockArgumentNesting() {
_blockArgumentNesting.add(_nesting.currentNesting);
}
/// Releases the last nesting level captured by [startBlockArgumentNesting].
void endBlockArgumentNesting() {
_blockArgumentNesting.removeLast();
}
/// Starts a new block as a child of the current chunk.
///
/// Nested blocks are handled using their own independent [LineWriter].
ChunkBuilder startBlock(Chunk argumentChunk) {
var chunk = _chunks.last;
chunk.makeBlock(argumentChunk);
var builder =
new ChunkBuilder._(this, _formatter, _source, chunk.block.chunks);
// A block always starts off indented one level.
builder.indent();
return builder;
}
/// Ends this [ChunkBuilder], which must have been created by [startBlock()].
///
/// Forces the chunk that owns the block to split if it can tell that the
/// block contents will always split. It does that by looking for hard splits
/// in the block. If [ignoredSplit] is given, that rule will be ignored
/// when determining if a block contains a hard split. If [forceSplit] is
/// `true`, the block is considered to always split.
///
/// Returns the previous writer for the surrounding block.
ChunkBuilder endBlock(Rule ignoredSplit, {bool forceSplit}) {
_divideChunks();
// If we don't already know if the block is going to split, see if it
// contains any hard splits or is longer than a page.
if (!forceSplit) {
var length = 0;
for (var chunk in _chunks) {
length += chunk.length + chunk.unsplitBlockLength;
if (length > _formatter.pageWidth) {
forceSplit = true;
break;
}
if (chunk.rule != null &&
chunk.rule.isHardened &&
chunk.rule != ignoredSplit) {
forceSplit = true;
break;
}
}
}
_parent._endChildBlock(
firstFlushLeft: _firstFlushLeft, forceSplit: forceSplit);
return _parent;
}
/// Finishes off the last chunk in a child block of this parent.
void _endChildBlock({bool firstFlushLeft, bool forceSplit}) {
// If there is a hard newline within the block, force the surrounding rule
// for it so that we apply that constraint.
if (forceSplit) forceRules();
// Write the split for the block contents themselves.
var chunk = _chunks.last;
chunk.applySplit(rule, _nesting.indentation, _blockArgumentNesting.last,
flushLeft: firstFlushLeft);
if (chunk.rule.isHardened) _handleHardSplit();
}
/// Finishes writing and returns a [SourceCode] containing the final output
/// and updated selection, if any.
SourceCode end() {
_writeHardSplit();
_divideChunks();
if (debug.traceChunkBuilder) {
debug.log(debug.green("\nBuilt:"));
debug.dumpChunks(0, _chunks);
debug.log();
}
var writer = new LineWriter(_formatter, _chunks);
var result = writer.writeLines(_formatter.indent,
isCompilationUnit: _source.isCompilationUnit);
var selectionStart;
var selectionLength;
if (_source.selectionStart != null) {
selectionStart = result.selectionStart;
var selectionEnd = result.selectionEnd;
// If we haven't hit the beginning and/or end of the selection yet, they
// must be at the very end of the code.
if (selectionStart == null) selectionStart = writer.length;
if (selectionEnd == null) selectionEnd = writer.length;
selectionLength = selectionEnd - selectionStart;
}
return new SourceCode(result.text,
uri: _source.uri,
isCompilationUnit: _source.isCompilationUnit,
selectionStart: selectionStart,
selectionLength: selectionLength);
}
void preventSplit() {
_preventSplitNesting++;
}
void endPreventSplit() {
_preventSplitNesting--;
assert(_preventSplitNesting >= 0, "Mismatched calls.");
}
/// Writes the current pending [Whitespace] to the output, if any.
///
/// This should only be called after source lines have been preserved to turn
/// any ambiguous whitespace into a concrete choice.
void _emitPendingWhitespace() {
// Output any pending whitespace first now that we know it won't be
// trailing.
switch (_pendingWhitespace) {
case Whitespace.space:
_writeText(" ");
break;
case Whitespace.newline:
_writeHardSplit();
break;
case Whitespace.nestedNewline:
_writeHardSplit(nest: true);
break;
case Whitespace.newlineFlushLeft:
_writeHardSplit(flushLeft: true, nest: true);
break;
case Whitespace.twoNewlines:
_writeHardSplit(isDouble: true);
break;
case Whitespace.splitOrNewline:
case Whitespace.splitOrTwoNewlines:
case Whitespace.oneOrTwoNewlines:
// We should have pinned these down before getting here.
assert(false);
break;
}
_pendingWhitespace = Whitespace.none;
}
/// Returns `true` if the last chunk is a split that should be moved after the
/// comment that is about to be written.
bool _shouldMoveCommentBeforeSplit(String comment) {
// Not if there is nothing before it.
if (_chunks.isEmpty) return false;
// Multi-line comments are always pushed to the next line.
if (comment.contains("\n")) return false;
var text = _chunks.last.text;
// A block comment following a comma probably refers to the following item.
if (text.endsWith(",") && comment.startsWith("/*")) return false;
// If the text before the split is an open grouping character, it looks
// better to keep it with the elements than with the bracket itself.
return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{");
}
/// Returns `true` if [comment] appears to be a magic generic method comment.
///
/// Those get spaced a little differently to look more like real syntax:
///
/// int f/*<S, T>*/(int x) => 3;
bool _isGenericMethodComment(SourceComment comment) {
return comment.text.startsWith("/*<") || comment.text.startsWith("/*=");
}
/// Returns `true` if a space should be output between the end of the current
/// output and the subsequent comment which is about to be written.
///
/// This is only called if the comment is trailing text in the unformatted
/// source. In most cases, a space will be output to separate the comment
/// from what precedes it. This returns false if:
///
/// * This comment does begin the line in the output even if it didn't in
/// the source.
/// * The comment is a block comment immediately following a grouping
/// character (`(`, `[`, or `{`). This is to allow `foo(/* comment */)`,
/// et. al.
bool _needsSpaceBeforeComment(SourceComment comment) {
// Not at the start of the file.
if (_chunks.isEmpty) return false;
// Not at the start of a line.
if (!_chunks.last.canAddText) return false;
var text = _chunks.last.text;
if (text.endsWith("\n")) return false;
// Always put a space before line comments.
if (comment.isLineComment) return true;
// Magic generic method comments like "Foo/*<T>*/" don't get spaces.
if (_isGenericMethodComment(comment) &&
_trailingIdentifierChar.hasMatch(text)) {
return false;
}
// Block comments do not get a space if following a grouping character.
return !text.endsWith("(") && !text.endsWith("[") && !text.endsWith("{");
}
/// Returns `true` if a space should be output after the last comment which
/// was just written and the token that will be written.
bool _needsSpaceAfterLastComment(List<SourceComment> comments, String token) {
// Not if there are no comments.
if (comments.isEmpty) return false;
// Not at the beginning of a line.
if (!_chunks.last.canAddText) return false;
// Magic generic method comments like "Foo/*<T>*/" don't get spaces.
if (_isGenericMethodComment(comments.last) && token == "(") {
return false;
}
// Otherwise, it gets a space if the following token is not a delimiter or
// the empty string, for EOF.
return token != ")" &&
token != "]" &&
token != "}" &&
token != "," &&
token != ";" &&
token != "";
}
/// Appends a hard split with the current indentation and nesting (the latter
/// only if [nest] is `true`).
///
/// If [double] is `true` or `false`, forces a single or double line to be
/// output. Otherwise, it is left indeterminate.
///
/// If [flushLeft] is `true`, then the split will always cause the next line
/// to be at column zero. Otherwise, it uses the normal indentation and
/// nesting behavior.
void _writeHardSplit({bool isDouble, bool flushLeft, bool nest: false}) {
// A hard split overrides any other whitespace.
_pendingWhitespace = null;
_writeSplit(new Rule.hard(),
flushLeft: flushLeft, isDouble: isDouble, nest: nest);
}
/// Ends the current chunk (if any) with the given split information.
///
/// Returns the chunk.
Chunk _writeSplit(Rule rule,
{bool flushLeft, bool isDouble, bool nest, bool space}) {
nest ??= true;
space ??= false;
if (_chunks.isEmpty) {
if (flushLeft != null) _firstFlushLeft = flushLeft;
return null;
}
_chunks.last.applySplit(rule, _nesting.indentation,
nest ? _nesting.nesting : new NestingLevel(),
flushLeft: flushLeft, isDouble: isDouble, space: space);
if (_chunks.last.rule.isHardened) _handleHardSplit();
return _chunks.last;
}
/// Writes [text] to either the current chunk or a new one if the current
/// chunk is complete.
void _writeText(String text) {
if (_chunks.isNotEmpty && _chunks.last.canAddText) {
_chunks.last.appendText(text);
} else {
_chunks.add(new Chunk(text));
}
}
/// Returns true if we can divide the chunks at [index] and line split the
/// ones before and after that separately.
bool _canDivideAt(int i) {
// Don't divide after the last chunk.
if (i == _chunks.length - 1) return false;
var chunk = _chunks[i];
if (!chunk.rule.isHardened) return false;
if (chunk.nesting.isNested) return false;
if (chunk.isBlock) return false;
return true;
}
/// Pre-processes the chunks after they are done being written by the visitor
/// but before they are run through the line splitter.
///
/// Marks ranges of chunks that can be line split independently to keep the
/// batches we send to [LineSplitter] small.
void _divideChunks() {
// Harden all of the rules that we know get forced by containing hard
// splits, along with all of the other rules they constrain.
_hardenRules();
// Now that we know where all of the divided chunk sections are, mark the
// chunks.
for (var i = 0; i < _chunks.length; i++) {
_chunks[i].markDivide(_canDivideAt(i));
}
}
/// Hardens the active rules when a hard split occurs within them.
void _handleHardSplit() {
if (_rules.isEmpty) return;
// If the current rule doesn't care, it will "eat" the hard split and no
// others will care either.
if (!_rules.last.splitsOnInnerRules) return;
// Start with the innermost rule. This will traverse the other rules it
// constrains.
_hardSplitRules.add(_rules.last);
}
/// Replaces all of the previously hardened rules with hard splits, along
/// with every rule that those constrain to also split.
///
/// This should only be called after all chunks have been written.
void _hardenRules() {
if (_hardSplitRules.isEmpty) return;
walkConstraints(rule) {
rule.harden();
// Follow this rule's constraints, recursively.
for (var other in rule.constrainedRules) {
if (other == rule) continue;
if (!other.isHardened &&
rule.constrain(rule.fullySplitValue, other) ==
other.fullySplitValue) {
walkConstraints(other);
}
}
}
for (var rule in _hardSplitRules) {
walkConstraints(rule);
}
// Discard spans in hardened chunks since we know for certain they will
// split anyway.
for (var chunk in _chunks) {
if (chunk.rule != null && chunk.rule.isHardened) {
chunk.spans.clear();
}
}
}
}
// 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.io;
import 'dart:io';
import 'package:path/path.dart' as p;
import 'dart_formatter.dart';
import 'exceptions.dart';
import 'formatter_options.dart';
import 'source_code.dart';
/// Runs the formatter on every .dart file in [path] (and its subdirectories),
/// and replaces them with their formatted output.
///
/// Returns `true` if successful or `false` if an error occurred in any of the
/// files.
bool processDirectory(FormatterOptions options, Directory directory) {
options.reporter.showDirectory(directory.path);
var success = true;
var shownHiddenPaths = new Set<String>();
for (var entry in directory.listSync(
recursive: true, followLinks: options.followLinks)) {
var relative = p.relative(entry.path, from: directory.path);
if (entry is Link) {
options.reporter.showSkippedLink(relative);
continue;
}
if (entry is! File || !entry.path.endsWith(".dart")) continue;
// If the path is in a subdirectory starting with ".", ignore it.
var parts = p.split(relative);
var hiddenIndex;
for (var i = 0; i < parts.length; i++) {
if (parts[i].startsWith(".")) {
hiddenIndex = i;
break;
}
}
if (hiddenIndex != null) {
// Since we'll hide everything inside the directory starting with ".",
// show the directory name once instead of once for each file.
var hiddenPath = p.joinAll(parts.take(hiddenIndex + 1));
if (shownHiddenPaths.add(hiddenPath)) {
options.reporter.showHiddenPath(hiddenPath);
}
continue;
}
if (!processFile(options, entry, label: relative)) success = false;
}
return success;
}
/// Runs the formatter on [file].
///
/// Returns `true` if successful or `false` if an error occurred.
bool processFile(FormatterOptions options, File file, {String label}) {
if (label == null) label = file.path;
var formatter = new DartFormatter(
indent: options.indent,
pageWidth: options.pageWidth,
fixes: options.fixes);
try {
var source = new SourceCode(file.readAsStringSync(), uri: file.path);
options.reporter.beforeFile(file, label);
var output = formatter.formatSource(source);
options.reporter
.afterFile(file, label, output, changed: source.text != output.text);
return true;
} on FormatterException catch (err) {
var color = Platform.operatingSystem != "windows" &&
stdioType(stderr) == StdioType.terminal;
stderr.writeln(err.message(color: color));
} on UnexpectedOutputException catch (err) {
stderr.writeln('''Hit a bug in the formatter when formatting $label.
$err
Please report at github.com/dart-lang/dart_style/issues.''');
} catch (err, stack) {
stderr.writeln('''Hit a bug in the formatter when formatting $label.
Please report at github.com/dart-lang/dart_style/issues.
$err
$stack''');
}
return false;
}
[1532/1570] ACTION //utils/dartdevc:dartdevc(//build/toolchain/linux:clang_x64)
FAILED: gen/dartdevc.dart.snapshot
python ../../build/gn_run_binary.py compiled_action dart --deterministic --packages=/dart-sdk/sdk/.packages --snapshot=gen/dartdevc.dart.snapshot --snapshot-depfile=/dart-sdk/sdk/out/ReleaseX64/gen/dartdevc.dart.snapshot.d --snapshot-kind=app-jit /dart-sdk/sdk/pkg/dev_compiler/bin/dartdevc.dart --dart-sdk /dart-sdk/sdk/sdk --dart-sdk-summary /dart-sdk/sdk/out/ReleaseX64/gen/utils/dartdevc/ddc_sdk.sum -k -o dartdevc.js /dart-sdk/sdk/pkg/dev_compiler/bin/dartdevc.dart
Command failed: ./dart --deterministic --packages=/dart-sdk/sdk/.packages --snapshot=gen/dartdevc.dart.snapshot --snapshot-depfile=/dart-sdk/sdk/out/ReleaseX64/gen/dartdevc.dart.snapshot.d --snapshot-kind=app-jit /dart-sdk/sdk/pkg/dev_compiler/bin/dartdevc.dart --dart-sdk /dart-sdk/sdk/sdk --dart-sdk-summary /dart-sdk/sdk/out/ReleaseX64/gen/utils/dartdevc/ddc_sdk.sum -k -o dartdevc.js /dart-sdk/sdk/pkg/dev_compiler/bin/dartdevc.dart
output:
[1533/1570] ACTION //utils/compiler:dart2js(//build/toolchain/linux:clang_x64)
ninja: build stopped: subcommand failed.
BUILD FAILED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment