Last active
April 2, 2019 21:15
-
-
Save munificent/1233df76e793cdba4c56ded4d8d4cf1d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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