blob: fdca0f81fd9c120f5bd517177bd2d3973c869dda [file] [log] [blame]
// 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.3.3';
void main(List<String> args) {
var parser = ArgParser(allowTrailingOptions: true);
var verbose = args.contains('-v') || args.contains('--verbose');
var hide = !verbose;
parser.addSeparator('Common options:');
parser.addFlag('help',
abbr: 'h',
negatable: false,
help:
'Shows this usage information. Add --verbose to show hidden options.');
parser.addFlag('version',
negatable: false, help: 'Shows version information.');
parser.addFlag('verbose',
abbr: 'v', negatable: false, help: 'Verbose output.');
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);
}
if (verbose) {
parser.addSeparator('Other options:');
}
parser.addOption('indent',
abbr: 'i',
help: 'Spaces of leading indentation.',
defaultsTo: '0',
hide: hide);
parser.addFlag('machine',
abbr: 'm',
negatable: false,
help: 'Produce machine-readable JSON output.',
hide: hide);
parser.addFlag('set-exit-if-changed',
negatable: false,
help: 'Return exit code 1 if there are any formatting changes.',
hide: hide);
parser.addFlag('follow-links',
negatable: false,
help: 'Follow links to files and directories.\n'
'If unset, links will be ignored.',
hide: hide);
parser.addOption('preserve',
help: 'Selection to preserve, formatted as "start:length".', hide: hide);
parser.addOption('stdin-name',
help: 'The path name to show when an error occurs in source read from '
'stdin.',
defaultsTo: '<stdin>',
hide: hide);
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.');
}
void 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 = ProfileReporter(reporter);
}
if (argResults['set-exit-if-changed']) {
reporter = 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 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.isNotEmpty) {
usageError(parser, 'Cannot pass --stdin-name when not reading from stdin.');
}
var options = 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 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 = StringBuffer();
stdin.transform(Utf8Decoder()).listen(input.write, onDone: () {
var formatter = DartFormatter(
indent: options.indent,
pageWidth: options.pageWidth,
fixes: options.fixes);
try {
options.reporter.beforeFile(null, name);
var source = 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 = Directory(path);
if (directory.existsSync()) {
if (!processDirectory(options, directory)) {
exitCode = 65;
}
continue;
}
var file = 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}
''');
}