Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions doc/tools/format-tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ dartfmt -w --fix .
----------------------------
```

### Configuring the formatter language version

For formatters that support `--language-version`, you can configure the value
used by `dart_dev`.

```dart
// tool/dart_dev/config.dart
import 'package:dart_dev/dart_dev.dart';

final config = {
'format': FormatTool()
..languageVersion = '3.0'
};
```

If `languageVersion` is not configured, `dart_dev` will use `latest` only in
the cases where it decides the formatter needs an explicit language version.

### Excluding files from formatting

```dart
Expand Down
8 changes: 4 additions & 4 deletions lib/src/core_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ library dart_dev.src.core_config;
import 'package:dart_dev/dart_dev.dart';

Map<String, DevTool> get coreConfig => {
'analyze': AnalyzeTool(),
'format': FormatTool(),
'test': TestTool(),
};
'analyze': AnalyzeTool(),
'format': FormatTool(),
'test': TestTool(),
};
23 changes: 16 additions & 7 deletions lib/src/dart_dev_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'utils/version.dart';

class DartDevRunner extends CommandRunner<int> {
DartDevRunner(Map<String, DevTool> commands)
: super('dart_dev', 'Dart tool runner.') {
: super('dart_dev', 'Dart tool runner.') {
// For backwards-compatibility, only add the `clean` command if it doesn't
// conflict with any configured command.
if (!commands.containsKey('clean')) {
Expand All @@ -32,10 +32,17 @@ class DartDevRunner extends CommandRunner<int> {
});

argParser
..addFlag('verbose',
abbr: 'v', negatable: false, help: 'Enables verbose logging.')
..addFlag('version',
negatable: false, help: 'Prints the dart_dev version.');
..addFlag(
'verbose',
abbr: 'v',
negatable: false,
help: 'Enables verbose logging.',
)
..addFlag(
'version',
negatable: false,
help: 'Prints the dart_dev version.',
);
}

@override
Expand All @@ -60,7 +67,8 @@ class DartDevRunner extends CommandRunner<int> {
final exitCode = (await super.run(args)) ?? 0;
stopwatch.stop();
await events.commandComplete(
events.CommandResult(args.toList(), exitCode, stopwatch.elapsed));
events.CommandResult(args.toList(), exitCode, stopwatch.elapsed),
);
return exitCode;
}
}
Expand All @@ -71,6 +79,7 @@ class CommandNameMismatch implements Exception {
CommandNameMismatch(this.actual, this.expected);

@override
String toString() => 'CommandNameMismatch: '
String toString() =>
'CommandNameMismatch: '
'Expected a "$expected" command but got one named "$actual".';
}
48 changes: 27 additions & 21 deletions lib/src/dart_dev_tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ abstract class DevTool {
DevTool();

factory DevTool.fromFunction(
FutureOr<int?> Function(DevToolExecutionContext context) function,
{ArgParser? argParser}) =>
FunctionTool(function, argParser: argParser);

factory DevTool.fromProcess(String executable, List<String> args,
{ProcessStartMode? mode, String? workingDirectory}) =>
ProcessTool(executable, args,
mode: mode, workingDirectory: workingDirectory);
FutureOr<int?> Function(DevToolExecutionContext context) function, {
ArgParser? argParser,
}) => FunctionTool(function, argParser: argParser);

factory DevTool.fromProcess(
String executable,
List<String> args, {
ProcessStartMode? mode,
String? workingDirectory,
}) => ProcessTool(
executable,
args,
mode: mode,
workingDirectory: workingDirectory,
);

/// The argument parser for this tool, if needed.
///
Expand Down Expand Up @@ -79,12 +86,12 @@ abstract class DevTool {
/// or not global verbose mode is enabled, and the [usageException] utility
/// function from [Command].
class DevToolExecutionContext {
DevToolExecutionContext(
{this.argResults,
this.commandName,
void Function(String message)? usageException,
this.verbose = false})
: _usageException = usageException;
DevToolExecutionContext({
this.argResults,
this.commandName,
void Function(String message)? usageException,
this.verbose = false,
}) : _usageException = usageException;

final void Function(String message)? _usageException;

Expand Down Expand Up @@ -113,13 +120,12 @@ class DevToolExecutionContext {
String? commandName,
void Function(String message)? usageException,
bool? verbose,
}) =>
DevToolExecutionContext(
argResults: argResults ?? this.argResults,
commandName: commandName ?? this.commandName,
usageException: usageException ?? this.usageException,
verbose: verbose ?? this.verbose,
);
}) => DevToolExecutionContext(
argResults: argResults ?? this.argResults,
commandName: commandName ?? this.commandName,
usageException: usageException ?? this.usageException,
verbose: verbose ?? this.verbose,
);

/// Calling this will throw a [UsageException] with [message] that should be
/// caught by [CommandRunner] and used to set the exit code accordingly and
Expand Down
7 changes: 5 additions & 2 deletions lib/src/events.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'dart:async';

Future<void> commandComplete(CommandResult result) async {
await Future.wait(_commandCompleteListeners
.map((listener) => Future<void>.value(listener(result))));
await Future.wait(
_commandCompleteListeners.map(
(listener) => Future<void>.value(listener(result)),
),
);
}

void onCommandComplete(FutureOr<void> Function(CommandResult result) callback) {
Expand Down
104 changes: 62 additions & 42 deletions lib/src/executable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,19 @@ Future<void> run(List<String> args) async {
final oldDevDartExists = File(_paths.legacyConfig).existsSync();

if (!configExists) {
log.fine('No custom `${_paths.config}` file found; '
'using default config.');
log.fine(
'No custom `${_paths.config}` file found; '
'using default config.',
);
}
if (oldDevDartExists) {
log.warning(yellow.wrap(
log.warning(
yellow.wrap(
'dart_dev v3 now expects configuration to be at `${_paths.config}`,\n'
'but `${_paths.legacyConfig}` still exists. View the guide to see how to upgrade:\n'
'https://github.com/Workiva/dart_dev/blob/master/doc/v3-upgrade-guide.md'));
'https://github.com/Workiva/dart_dev/blob/master/doc/v3-upgrade-guide.md',
),
);
}

if (args.contains('hackFastFormat') && !oldDevDartExists) {
Expand All @@ -57,13 +62,10 @@ Future<void> run(List<String> args) async {
}

final processArgs = generateRunScript();
final process = await Process.start(
processArgs.first,
[
if (processArgs.length > 1) ...processArgs.sublist(1),
...args,
],
mode: ProcessStartMode.inheritStdio);
final process = await Process.start(processArgs.first, [
if (processArgs.length > 1) ...processArgs.sublist(1),
...args,
], mode: ProcessStartMode.inheritStdio);
ensureProcessExit(process);
exitCode = await process.exitCode;
}
Expand All @@ -75,17 +77,19 @@ Future<void> handleFastFormat(List<String> args) async {
final configFile = File(_paths.config);
if (configFile.existsSync()) {
final toolBuilder = FormatToolBuilder();
parseString(content: configFile.readAsStringSync())
.unit
.accept(toolBuilder);
parseString(
content: configFile.readAsStringSync(),
).unit.accept(toolBuilder);
formatTool = toolBuilder
.formatDevTool; // could be null if no custom `format` entry found

if (formatTool == null && toolBuilder.failedToDetectAKnownFormatter) {
exitCode = ExitCode.config.code;
log.severe('Failed to reconstruct the format tool\'s configuration.\n\n'
'This is likely because dart_dev expects either the FormatTool class or the\n'
'OverReactFormatTool class.');
log.severe(
'Failed to reconstruct the format tool\'s configuration.\n\n'
'This is likely because dart_dev expects either the FormatTool class or the\n'
'OverReactFormatTool class.',
);
return;
}
}
Expand Down Expand Up @@ -116,27 +120,31 @@ bool _allPackagesAreImportedImmutably(Iterable<String> packageNames) {
File pubspecLockFile = File('pubspec.lock');
if (!pubspecLockFile.existsSync()) return false;
final pubSpecLock = loadYamlDocument(pubspecLockFile.readAsStringSync());
return getDependencySources(pubSpecLock, packageNames)
.values
.every((source) => source == 'hosted' || source == 'git');
return getDependencySources(
pubSpecLock,
packageNames,
).values.every((source) => source == 'hosted' || source == 'git');
}

/// Return null iff it is not possible to account for all
/// recompilation-necessitating factors in the digest.
String? _computeRunScriptDigest() {
final currentPackageName =
Pubspec.parse(File('pubspec.yaml').readAsStringSync()).name;
final currentPackageName = Pubspec.parse(
File('pubspec.yaml').readAsStringSync(),
).name;
final configFile = File(_paths.config);
var configHasRelativeImports = false;
if (configFile.existsSync()) {
final configImports = parseImports(configFile.readAsStringSync());
configHasRelativeImports = configImports.any((i) => !i.contains(':'));
final configHasSamePackageImports =
configImports.any((i) => i.startsWith('package:$currentPackageName'));
final configHasSamePackageImports = configImports.any(
(i) => i.startsWith('package:$currentPackageName'),
);

if (configHasSamePackageImports) {
log.fine(
'Skipping compilation because ${_paths.config} imports from its own package.');
'Skipping compilation because ${_paths.config} imports from its own package.',
);

// If the config imports from its own source files, we don't have a way of
// efficiently tracking changes that would require recompilation of this
Expand All @@ -148,7 +156,8 @@ String? _computeRunScriptDigest() {
final packageNames = computePackageNamesFromImports(configImports);
if (!_allPackagesAreImportedImmutably(packageNames)) {
log.fine(
'Skipping compilation because ${_paths.config} imports non-hosted packages.');
'Skipping compilation because ${_paths.config} imports non-hosted packages.',
);
_deleteRunExecutableAndDigest();
return null;
}
Expand All @@ -162,11 +171,14 @@ String? _computeRunScriptDigest() {
if (packageConfig.existsSync()) ...packageConfig.readAsBytesSync(),
if (configFile.existsSync()) ...configFile.readAsBytesSync(),
if (configHasRelativeImports)
for (final file in Glob('tool/**.dart', recursive: true)
.listSync()
.whereType<File>()
.where((f) => p.canonicalize(f.path) != p.canonicalize(_paths.config))
.sortedBy((f) => f.path))
for (final file
in Glob('tool/**.dart', recursive: true)
.listSync()
.whereType<File>()
.where(
(f) => p.canonicalize(f.path) != p.canonicalize(_paths.config),
)
.sortedBy((f) => f.path))
...file.readAsBytesSync(),
]);
return base64.encode(digest.bytes);
Expand All @@ -190,9 +202,12 @@ List<String> generateRunScript() {
// Generate a digest of inputs to the run script. We use this to determine
// whether we need to recompile the executable.
String? encodedDigest;
logTimedSync(log, 'Computing run script digest',
() => encodedDigest = _computeRunScriptDigest(),
level: Level.FINE);
logTimedSync(
log,
'Computing run script digest',
() => encodedDigest = _computeRunScriptDigest(),
level: Level.FINE,
);

if (encodedDigest != null &&
(!runExecutableDigest.existsSync() ||
Expand All @@ -210,7 +225,7 @@ List<String> generateRunScript() {
'exe',
_paths.runScript,
'-o',
_paths.runExecutable
_paths.runExecutable,
];
final result = Process.runSync(Platform.executable, args);
if (result.exitCode == 0) {
Expand All @@ -220,7 +235,8 @@ List<String> generateRunScript() {
// Compilation failed. Dump some logs for debugging, but note to the
// user that dart_dev should still work.
log.warning(
'Could not compile run script; dart_dev will continue without precompilation.');
'Could not compile run script; dart_dev will continue without precompilation.',
);
log.fine('CMD: ${Platform.executable} ${args.join(" ")}');
log.fine('STDOUT:\n${result.stdout}');
log.fine('STDERR:\n${result.stderr}');
Expand Down Expand Up @@ -258,9 +274,10 @@ void main(List<String> args) async {
}

Future<void> runWithConfig(
// ignore: library_private_types_in_public_api
List<String> args,
_ConfigGetter configGetter) async {
// ignore: library_private_types_in_public_api
List<String> args,
_ConfigGetter configGetter,
) async {
attachLoggerToStdio(args);

try {
Expand All @@ -278,8 +295,10 @@ Future<void> runWithConfig(
stderr
..writeln('Invalid "${_paths.config}" in ${p.absolute(p.current)}')
..writeln()
..writeln('It should provide a `Map<String, DevTool> config;` getter,'
' but it either does not exist or threw unexpectedly:')
..writeln(
'It should provide a `Map<String, DevTool> config;` getter,'
' but it either does not exist or threw unexpectedly:',
)
..writeln(' $error')
..writeln()
..writeln('For more info: https://github.com/Workiva/dart_dev');
Expand Down Expand Up @@ -307,7 +326,8 @@ Future<void> runWithConfig(
DevTool chooseDefaultFormatTool({String? path}) {
final pubspec = cachedPubspec(path: path);
const orf = 'over_react_format';
final hasOverReactFormat = pubspec.dependencies.containsKey(orf) ||
final hasOverReactFormat =
pubspec.dependencies.containsKey(orf) ||
pubspec.devDependencies.containsKey(orf) ||
pubspec.dependencyOverrides.containsKey(orf);

Expand Down
Loading
Loading