1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:io';
6
7import 'package:args/args.dart';
8import 'package:path/path.dart' as path;
9import 'package:process_runner/process_runner.dart';
10
11Future<int> main(List<String> arguments) async {
12 final ArgParser parser = ArgParser();
13 parser.addFlag('help', help: 'Print help.', abbr: 'h');
14 parser.addFlag(
15 'fix',
16 abbr: 'f',
17 help: 'Instead of just checking for formatting errors, fix them in place.',
18 );
19 parser.addFlag(
20 'all-files',
21 abbr: 'a',
22 help:
23 'Instead of just checking for formatting errors in changed files, '
24 'check for them in all files.',
25 );
26
27 late final ArgResults options;
28 try {
29 options = parser.parse(arguments);
30 } on FormatException catch (e) {
31 stderr.writeln('ERROR: $e');
32 _usage(parser, exitCode: 0);
33 }
34
35 if (options['help'] as bool) {
36 _usage(parser, exitCode: 0);
37 }
38
39 final File script = File.fromUri(Platform.script).absolute;
40 final Directory flutterRoot = script.parent.parent.parent.parent;
41
42 final bool result =
43 (await DartFormatChecker(
44 flutterRoot: flutterRoot,
45 allFiles: options['all-files'] as bool,
46 ).check(fix: options['fix'] as bool)) ==
47 0;
48
49 exit(result ? 0 : 1);
50}
51
52void _usage(ArgParser parser, {int exitCode = 1}) {
53 stderr.writeln('format.dart [--help] [--fix] [--all-files]');
54 stderr.writeln(parser.usage);
55 exit(exitCode);
56}
57
58class DartFormatChecker {
59 DartFormatChecker({required this.flutterRoot, required this.allFiles})
60 : processRunner = ProcessRunner(defaultWorkingDirectory: flutterRoot);
61
62 final Directory flutterRoot;
63 final bool allFiles;
64 final ProcessRunner processRunner;
65
66 Future<int> check({required bool fix}) async {
67 final String baseGitRef = await _getDiffBaseRevision();
68 final List<String> filesToCheck = await _getFileList(
69 types: <String>['*.dart'],
70 allFiles: allFiles,
71 baseGitRef: baseGitRef,
72 );
73 return _checkFormat(filesToCheck: filesToCheck, fix: fix);
74 }
75
76 Future<String> _getDiffBaseRevision() async {
77 String upstream = 'upstream';
78 final String upstreamUrl = await _runGit(
79 <String>['remote', 'get-url', upstream],
80 processRunner,
81 failOk: true,
82 );
83 if (upstreamUrl.isEmpty) {
84 upstream = 'origin';
85 }
86 await _runGit(<String>['fetch', upstream, 'main'], processRunner);
87 String result = '';
88 try {
89 // This is the preferred command to use, but developer checkouts often do
90 // not have a clear fork point, so we fall back to just the regular
91 // merge-base in that case.
92 result = await _runGit(<String>[
93 'merge-base',
94 '--fork-point',
95 'FETCH_HEAD',
96 'HEAD',
97 ], processRunner);
98 } on ProcessRunnerException {
99 result = await _runGit(<String>['merge-base', 'FETCH_HEAD', 'HEAD'], processRunner);
100 }
101 return result.trim();
102 }
103
104 Future<String> _runGit(
105 List<String> args,
106 ProcessRunner processRunner, {
107 bool failOk = false,
108 }) async {
109 final ProcessRunnerResult result = await processRunner.runProcess(<String>[
110 'git',
111 ...args,
112 ], failOk: failOk);
113 return result.stdout;
114 }
115
116 Future<List<String>> _getFileList({
117 required List<String> types,
118 required bool allFiles,
119 required String baseGitRef,
120 }) async {
121 String output;
122 if (allFiles) {
123 output = await _runGit(<String>['ls-files', '--', ...types], processRunner);
124 } else {
125 output = await _runGit(<String>[
126 'diff',
127 '-U0',
128 '--no-color',
129 '--diff-filter=d',
130 '--name-only',
131 baseGitRef,
132 '--',
133 ...types,
134 ], processRunner);
135 }
136 return output
137 .split('\n')
138 .where((String line) => line.isNotEmpty && !line.startsWith('engine'))
139 .toList();
140 }
141
142 Future<int> _checkFormat({required List<String> filesToCheck, required bool fix}) async {
143 final List<String> cmd = <String>[
144 path.join(flutterRoot.path, 'bin', 'dart'),
145 'format',
146 '--set-exit-if-changed',
147 '--show=none',
148 if (!fix) '--output=show',
149 if (fix) '--output=write',
150 ];
151 final List<WorkerJob> jobs = <WorkerJob>[];
152 for (final String file in filesToCheck) {
153 jobs.add(WorkerJob(<String>[...cmd, file]));
154 }
155 final ProcessPool dartFmt = ProcessPool(
156 processRunner: processRunner,
157 printReport: _namedReport('dart format'),
158 );
159
160 Iterable<WorkerJob> incorrect;
161 final List<WorkerJob> errorJobs = <WorkerJob>[];
162 if (!fix) {
163 final Stream<WorkerJob> completedJobs = dartFmt.startWorkers(jobs);
164 final List<WorkerJob> diffJobs = <WorkerJob>[];
165 await for (final WorkerJob completedJob in completedJobs) {
166 if (completedJob.result.exitCode != 0 && completedJob.result.exitCode != 1) {
167 // The formatter had a problem formatting the file.
168 errorJobs.add(completedJob);
169 } else if (completedJob.result.exitCode == 1) {
170 diffJobs.add(
171 WorkerJob(<String>[
172 'git',
173 'diff',
174 '--no-index',
175 '--no-color',
176 '--ignore-cr-at-eol',
177 '--',
178 completedJob.command.last,
179 '-',
180 ], stdinRaw: _codeUnitsAsStream(completedJob.result.stdoutRaw)),
181 );
182 }
183 }
184 final ProcessPool diffPool = ProcessPool(
185 processRunner: processRunner,
186 printReport: _namedReport('diff'),
187 );
188 final List<WorkerJob> completedDiffs = await diffPool.runToCompletion(diffJobs);
189 incorrect = completedDiffs.where((WorkerJob job) => job.result.exitCode != 0);
190 } else {
191 final List<WorkerJob> completedJobs = await dartFmt.runToCompletion(jobs);
192 final List<WorkerJob> incorrectJobs = incorrect = <WorkerJob>[];
193 for (final WorkerJob job in completedJobs) {
194 if (job.result.exitCode != 0 && job.result.exitCode != 1) {
195 // The formatter had a problem formatting the file.
196 errorJobs.add(job);
197 } else if (job.result.exitCode == 1) {
198 incorrectJobs.add(job);
199 }
200 }
201 }
202
203 _clearOutput();
204
205 if (incorrect.isNotEmpty) {
206 final bool plural = incorrect.length > 1;
207 if (fix) {
208 stdout.writeln(
209 'Fixing ${incorrect.length} dart file${plural ? 's' : ''}'
210 ' which ${plural ? 'were' : 'was'} formatted incorrectly.',
211 );
212 } else {
213 stderr.writeln(
214 'Found ${incorrect.length} Dart file${plural ? 's' : ''}'
215 ' which ${plural ? 'were' : 'was'} formatted incorrectly.',
216 );
217 final String fileList = incorrect
218 .map((WorkerJob job) => job.command[job.command.length - 2])
219 .join(' ');
220 stdout.writeln();
221 stdout.writeln('To fix, run `dart format $fileList` or:');
222 stdout.writeln();
223 stdout.writeln('git apply <<DONE');
224 for (final WorkerJob job in incorrect) {
225 stdout.write(
226 job.result.stdout
227 .replaceFirst('b/-', 'b/${job.command[job.command.length - 2]}')
228 .replaceFirst('b/-', 'b/${job.command[job.command.length - 2]}')
229 .replaceFirst(
230 RegExp('\\+Formatted \\d+ files? \\(\\d+ changed\\) in \\d+.\\d+ seconds.\n'),
231 '',
232 ),
233 );
234 }
235 stdout.writeln('DONE');
236 stdout.writeln();
237 }
238 _printErrorJobs(errorJobs);
239 } else if (errorJobs.isNotEmpty) {
240 _printErrorJobs(errorJobs);
241 } else {
242 stdout.writeln('All dart files formatted correctly.');
243 }
244 return fix ? errorJobs.length : (incorrect.length + errorJobs.length);
245 }
246
247 void _printErrorJobs(List<WorkerJob> errorJobs) {
248 if (errorJobs.isNotEmpty) {
249 final bool plural = errorJobs.length > 1;
250 stderr.writeln(
251 'The formatter failed to run on ${errorJobs.length} Dart file${plural ? 's' : ''}.',
252 );
253 stdout.writeln();
254 for (final WorkerJob job in errorJobs) {
255 stdout.writeln('--> ${job.command.last} produced the following error:');
256 stdout.write(job.result.stderr);
257 stdout.writeln();
258 }
259 }
260 }
261}
262
263ProcessPoolProgressReporter _namedReport(String name) {
264 return (int total, int completed, int inProgress, int pending, int failed) {
265 final String percent = total == 0 ? '100' : ((100 * completed) ~/ total).toString().padLeft(3);
266 final String completedStr = completed.toString().padLeft(3);
267 final String totalStr = total.toString().padRight(3);
268 final String inProgressStr = inProgress.toString().padLeft(2);
269 final String pendingStr = pending.toString().padLeft(3);
270 final String failedStr = failed.toString().padLeft(3);
271
272 stdout.write(
273 '$name Jobs: $percent% done, '
274 '$completedStr/$totalStr completed, '
275 '$inProgressStr in progress, '
276 '$pendingStr pending, '
277 '$failedStr failed.${' ' * 20}\r',
278 );
279 };
280}
281
282void _clearOutput() {
283 stdout.write('\r${' ' * 100}\r');
284}
285
286Stream<List<int>> _codeUnitsAsStream(List<int>? input) async* {
287 if (input != null) {
288 yield input;
289 }
290}
291

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com