blob: d87af512a1abe7e7bd2158b770d4f07862a0bdca [file] [edit]
// 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'back_end/worker_pool.dart';
import 'cli/formatter_options.dart';
import 'config_cache.dart';
import 'dart_formatter.dart';
import 'exceptions.dart';
import 'source_code.dart';
/// Reads and formats input from stdin until closed.
Future<void> formatStdin(
FormatterOptions options,
List<int>? selection,
String? path,
) async {
var selectionStart = 0;
var selectionLength = 0;
if (selection != null) {
selectionStart = selection[0];
selectionLength = selection[1];
}
var cache = ConfigCache();
var languageVersion = options.languageVersion;
if (languageVersion == null && path != null) {
// We have a stdin-name, so look for a surrounding package config.
languageVersion = await cache.findLanguageVersion(File(path), path);
}
// If they didn't specify a version or a path, or couldn't find a package
// surrounding the path, then default to the latest version.
languageVersion ??= DartFormatter.latestLanguageVersion;
// Determine the page width.
var pageWidth = options.pageWidth;
if (pageWidth == null && path != null) {
// We have a stdin-name, so look for a surrounding analyisis_options.yaml.
pageWidth = await cache.findPageWidth(File(path));
}
var trailingCommas = options.trailingCommas;
if (trailingCommas == null && path != null) {
// We have a stdin-name, so look for a surrounding analyisis_options.yaml.
trailingCommas = await cache.findTrailingCommas(File(path));
}
// Use a default page width if we don't have a specified one and couldn't
// find a configured one.
pageWidth ??= DartFormatter.defaultPageWidth;
var name = path ?? 'stdin';
var completer = Completer<void>();
var input = StringBuffer();
void onDone() {
var formatter = DartFormatter(
languageVersion: languageVersion!,
indent: options.indent,
pageWidth: pageWidth,
trailingCommas: trailingCommas,
experimentFlags: options.experimentFlags,
);
try {
options.beforeFile(null, name);
var source = SourceCode(
input.toString(),
uri: path,
selectionStart: selectionStart,
selectionLength: selectionLength,
);
var output = formatter.formatSource(source);
options.afterFile(
null,
name,
output,
changed: source.text != output.text,
);
} 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
}
completer.complete();
}
stdin.transform(const Utf8Decoder()).listen(input.write, onDone: onDone);
return completer.future;
}
/// Formats all of the files and directories given by [paths].
Future<void> formatPaths(FormatterOptions options, List<String> paths) async {
if (!await _processFileStream(options, _expandPaths(paths, options))) {
exitCode = 65;
}
}
/// Expands [paths] into a stream of files to format.
Stream<File> _expandPaths(List<String> paths, FormatterOptions options) async* {
var seen = <String>{};
for (var path in paths) {
var directory = Directory(path);
if (directory.existsSync()) {
await for (var entry in directory.list(
recursive: true,
followLinks: options.followLinks,
)) {
if (entry is Link) continue;
if (entry is! File || !entry.path.endsWith('.dart')) continue;
// Ignore paths in hidden directories.
var parts = p.split(p.relative(entry.path, from: directory.path));
if (parts.any((part) => part.startsWith('.'))) continue;
if (seen.add(p.canonicalize(entry.path))) {
yield entry;
}
}
continue;
}
var file = File(path);
if (file.existsSync()) {
if (seen.add(p.canonicalize(file.path))) {
yield file;
}
} else {
stderr.writeln('No file or directory found at "$path".');
}
}
}
/// Runs the formatter on a stream of files using a worker pool.
Future<bool> _processFileStream(
FormatterOptions options,
Stream<File> files,
) async {
var cache = ConfigCache();
var success = true;
var pool = WorkerPool();
await for (var file in files) {
var displayPath = p.normalize(file.path);
// Determine configuration in main isolate (leveraging cache).
var languageVersion =
options.languageVersion ??
await cache.findLanguageVersion(file, displayPath);
languageVersion ??= DartFormatter.latestLanguageVersion;
var pageWidth = options.pageWidth ?? await cache.findPageWidth(file);
var trailingCommas =
options.trailingCommas ?? await cache.findTrailingCommas(file);
pageWidth ??= DartFormatter.defaultPageWidth;
options.beforeFile(file, displayPath);
await pool.add(
uri: file.path,
languageVersion: languageVersion,
indent: options.indent,
pageWidth: pageWidth,
trailingCommas: trailingCommas,
experimentFlags: options.experimentFlags,
onResult: (response) {
if (response.error != null) {
if (response.isFormatterException) {
stderr.writeln(response.error);
} else {
stderr.writeln(
'Hit a bug in the formatter when formatting $displayPath.\n'
'${response.error}\n'
'${response.stackTrace}',
);
}
success = false;
return;
}
var output = SourceCode(
response.text!,
uri: file.path,
selectionStart: response.selectionStart,
selectionLength: response.selectionLength,
);
options.afterFile(file, displayPath, output, changed: response.changed);
},
);
}
var telemetry = await pool.close();
options.summary.addTelemetry(telemetry);
await pool.close();
return success;
}