Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: format built in command #657

Merged
merged 5 commits into from
Mar 12, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
- name: Install Tools
run: ./.github/workflows/scripts/install-tools.sh
- name: Check formatting
run: melos format:check
run: melos format --output none --set-exit-if-changed

test_linux:
runs-on: ubuntu-latest
Expand Down
72 changes: 72 additions & 0 deletions docs/commands/format.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: Format Command
description: Learn more about the `format` command in Melos.
---

# Format Command

<Info>Supports all [Melos filtering](/filters) flags.</Info>

The format command is used to format the code in your Melos workspace according
to Dart's formatting standards.

```bash
melos format
```

<Info>
To learn more, visit the [Dart format](https://dart.dev/tools/dart-format)
documentation.
</Info>


## --set-exit-if-changed
Return exit code 1 if there are any formatting changes. This flag is
particularly useful in CI/CD pipelines to automatically detect and reject
commits that do not adhere to the formatting standards, ensuring code quality.

```bash
melos format --set-exit-if-changed
```

<Info>
By default, dart format overwrites the Dart files.
</Info>

## --output
This option is useful when you want to review formatting changes without
directly overwriting your files.

```bash
melos format --output
# or
melos format --o
```

Outputs the formatted code to the console.

```bash
melos format -o show
```

Outputs the formatted code as a JSON object

```bash
melos format -o json
```

Lists the files that would be formatted, without showing the formatted content
or making changes.

```bash
melos format -o none
```

## concurrency (-c)
Defines the max concurrency value of how many packages will execute the command
in at any one time. Defaults to `1`.

```bash
# Set a 5 concurrency
melos format -c 5
```
8 changes: 0 additions & 8 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ ide:
intellij: true

scripts:
format:
description: Format Dart code.
run: dart format .

format:check:
description: Check formatting of Dart code.
run: dart format --output none --set-exit-if-changed .

test:
description: Run tests in a specific package.
run: dart test
Expand Down
1 change: 1 addition & 0 deletions packages/melos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ Available commands:
clean Clean this workspace and all packages. This deletes the temporary pub & ide files such
as ".packages" & ".flutter-plugins". Supports all package filtering options.
exec Execute an arbitrary command in each package. Supports all package filtering options.
format Idiomatically format Dart source code.
list List local packages in various output formats. Supports all package filtering options.
publish Publish any unpublished packages or package versions in your repository to pub.dev. Dry
run is on by default.
Expand Down
2 changes: 2 additions & 0 deletions packages/melos/lib/src/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import 'command_runner/analyze.dart';
import 'command_runner/bootstrap.dart';
import 'command_runner/clean.dart';
import 'command_runner/exec.dart';
import 'command_runner/format.dart';
import 'command_runner/list.dart';
import 'command_runner/publish.dart';
import 'command_runner/run.dart';
Expand Down Expand Up @@ -79,6 +80,7 @@ class MelosCommandRunner extends CommandRunner<void> {
addCommand(PublishCommand(config));
addCommand(VersionCommand(config));
addCommand(AnalyzeCommand(config));
addCommand(FormatCommand(config));

// Keep this last to exclude all built-in commands listed above
final script = ScriptCommand.fromConfig(config, exclude: commands.keys);
Expand Down
63 changes: 63 additions & 0 deletions packages/melos/lib/src/command_runner/format.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2016-present Invertase Limited & Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this library except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

spydon marked this conversation as resolved.
Show resolved Hide resolved
import '../commands/runner.dart';
import 'base.dart';

class FormatCommand extends MelosCommand {
FormatCommand(super.config) {
setupPackageFilterParser();
argParser.addOption('concurrency', defaultsTo: '1', abbr: 'c');
argParser.addFlag(
'set-exit-if-changed',
negatable: false,
help: 'Return exit code 1 if there are any formatting changes.',
);
argParser.addOption(
'output',
help: 'Set where to write formatted output.\n'
'[json] Print code and selection as JSON.\n'
'[none] Discard output.\n'
'[show] Print code to terminal.\n'
'[write] Overwrite formatted files on disk.\n',
abbr: 'o',
);
}

@override
final String name = 'format';

@override
final String description = 'Idiomatically format Dart source code.';

@override
Future<void> run() async {
final setExitIfChanged = argResults?['set-exit-if-changed'] as bool;
final output = argResults?['output'] as String?;
final concurrency = int.parse(argResults!['concurrency'] as String);

final melos = Melos(logger: logger, config: config);

return melos.format(
global: global,
packageFilters: parsePackageFilters(config.path),
concurrency: concurrency,
setExitIfChanged: setExitIfChanged,
output: output,
);
}
}
130 changes: 130 additions & 0 deletions packages/melos/lib/src/commands/format.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
part of 'runner.dart';

mixin _FormatMixin on _Melos {
Future<void> format({
GlobalOptions? global,
PackageFilters? packageFilters,
int concurrency = 1,
bool setExitIfChanged = false,
String? output,
}) async {
final workspace =
await createWorkspace(global: global, packageFilters: packageFilters);
final packages = workspace.filteredPackages.values;

await _formatForAllPackages(
workspace,
packages,
concurrency: concurrency,
setExitIfChanged: setExitIfChanged,
output: output,
);
}

Future<void> _formatForAllPackages(
MelosWorkspace workspace,
Iterable<Package> packages, {
required int concurrency,
required bool setExitIfChanged,
String? output,
}) async {
final failures = <String, int?>{};
final pool = Pool(concurrency);
final formatArgs = [
'dart',
'format',
if (setExitIfChanged) '--set-exit-if-changed',
if (output != null) '--output $output',
'.',
];
final formatArgsString = formatArgs.join(' ');
final prefixLogs = concurrency != 1 && packages.length != 1;

logger.command('melos format', withDollarSign: true);

logger
.child(targetStyle(formatArgsString))
.child('$runningLabel (in ${packages.length} packages)')
.newLine();
if (prefixLogs) {
logger.horizontalLine();
}

final packageResults = Map.fromEntries(
packages.map((package) => MapEntry(package.name, Completer<int?>())),
);

await pool.forEach<Package, void>(packages, (package) async {
if (!prefixLogs) {
logger
..horizontalLine()
..log(AnsiStyles.bgBlack.bold.italic('${package.name}:'));
}

final packageExitCode = await _formatForPackage(
workspace,
package,
formatArgs,
prefixLogs: prefixLogs,
);

packageResults[package.name]?.complete(packageExitCode);

if (packageExitCode > 0) {
failures[package.name] = packageExitCode;
} else if (!prefixLogs) {
logger.log(
AnsiStyles.bgBlack.bold.italic('${package.name}: ') +
AnsiStyles.bgBlack(successLabel),
);
}
}).drain<void>();

logger
..horizontalLine()
..newLine()
..command('melos format', withDollarSign: true);

final resultLogger = logger.child(targetStyle(formatArgsString));

if (failures.isNotEmpty) {
final failuresLogger =
resultLogger.child('$failedLabel (in ${failures.length} packages)');
for (final packageName in failures.keys) {
failuresLogger.child(
'${errorPackageNameStyle(packageName)} '
'${failures[packageName] == null ? '(dependency failed)' : '('
'with exit code ${failures[packageName]})'}',
);
}
exitCode = 1;
} else {
resultLogger.child(successLabel);
}
}

Future<int> _formatForPackage(
MelosWorkspace workspace,
Package package,
List<String> formatArgs, {
bool prefixLogs = true,
}) async {
final packagePrefix = '[${AnsiStyles.blue.bold(package.name)}]: ';

final environment = {
EnvironmentVariableKey.melosRootPath: config.path,
if (workspace.sdkPath != null)
EnvironmentVariableKey.melosSdkPath: workspace.sdkPath!,
if (workspace.childProcessPath != null)
EnvironmentVariableKey.path: workspace.childProcessPath!,
};

return startCommand(
formatArgs,
logger: logger,
environment: environment,
workingDirectory: package.path,
prefix: prefixLogs ? packagePrefix : null,
);
}
}
4 changes: 3 additions & 1 deletion packages/melos/lib/src/commands/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ part 'publish.dart';
part 'run.dart';
part 'version.dart';
part 'analyze.dart';
part 'format.dart';

enum CommandWithLifecycle {
bootstrap,
Expand All @@ -66,7 +67,8 @@ class Melos extends _Melos
_ExecMixin,
_VersionMixin,
_PublishMixin,
_AnalyzeMixin {
_AnalyzeMixin,
_FormatMixin {
Melos({
required this.config,
Logger? logger,
Expand Down
Loading
Loading