Skip to content

Commit 992eefc

Browse files
committed
Add exclude patterns for files and directories
Add the `--exclude` option to the format command. This option allows users to exclude specific files and directories from being formatted. The option accepts a list of patterns that can be file paths, directory names, or glob patterns. - Added `excludePatterns` to `FormatterOptions`. - Modified `FormatCommand` to include `--exclude` option in `argParser`. - Updated `formatPaths` to check and exclude files based on provided patterns. - Added tests to verify the exclude functionality.
1 parent 34742d7 commit 992eefc

File tree

4 files changed

+127
-1
lines changed

4 files changed

+127
-1
lines changed

lib/src/cli/format_command.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ final class FormatCommand extends Command<int> {
154154
'See dart.dev/go/experiments.',
155155
hide: !verbose,
156156
);
157+
argParser.addMultiOption(
158+
'exclude',
159+
help:
160+
'Exclude files and directories matching the given patterns.\n'
161+
'Patterns can be file paths, directory names, or glob patterns.',
162+
hide: !verbose,
163+
);
157164

158165
if (verbose) argParser.addSeparator('Options when formatting from stdin:');
159166

@@ -312,6 +319,7 @@ final class FormatCommand extends Command<int> {
312319
var setExitIfChanged = argResults['set-exit-if-changed'] as bool;
313320

314321
var experimentFlags = argResults['enable-experiment'] as List<String>;
322+
var excludePatterns = argResults['exclude'] as List<String>;
315323

316324
// If stdin isn't connected to a pipe, then the user is not passing
317325
// anything to stdin, so let them know they made a mistake.
@@ -339,6 +347,7 @@ final class FormatCommand extends Command<int> {
339347
summary: summary,
340348
setExitIfChanged: setExitIfChanged,
341349
experimentFlags: experimentFlags,
350+
excludePatterns: excludePatterns,
342351
);
343352

344353
if (argResults.rest.isEmpty) {

lib/src/cli/formatter_options.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ final class FormatterOptions {
5454
/// See dart.dev/go/experiments for details.
5555
final List<String> experimentFlags;
5656

57+
/// Patterns for files and directories to exclude from formatting.
58+
final List<String> excludePatterns;
59+
5760
FormatterOptions({
5861
this.languageVersion,
5962
this.indent = 0,
@@ -65,7 +68,9 @@ final class FormatterOptions {
6568
this.summary = Summary.none,
6669
this.setExitIfChanged = false,
6770
List<String>? experimentFlags,
68-
}) : experimentFlags = [...?experimentFlags];
71+
List<String>? excludePatterns,
72+
}) : experimentFlags = [...?experimentFlags],
73+
excludePatterns = [...?excludePatterns];
6974

7075
/// Called when [file] is about to be formatted.
7176
///

lib/src/io.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,28 @@ Future<void> formatPaths(FormatterOptions options, List<String> paths) async {
129129
}
130130
}
131131

132+
/// Checks if the given [path] matches any of the [excludePatterns].
133+
bool _isExcluded(String path, List<String> excludePatterns) {
134+
var relativePath = p.relative(path);
135+
for (var pattern in excludePatterns) {
136+
if (relativePath.contains(pattern) || _matchesGlob(relativePath, pattern)) {
137+
return true;
138+
}
139+
}
140+
return false;
141+
}
142+
143+
/// Simple glob matching for patterns like *.dart or **/generated/**.
144+
bool _matchesGlob(String path, String pattern) {
145+
// For simplicity, support basic wildcards: * and **
146+
var regexPattern = pattern
147+
.replaceAll('.', '\\.')
148+
.replaceAll('*', '.*')
149+
.replaceAll('**', '.*');
150+
var regex = RegExp('^$regexPattern\$');
151+
return regex.hasMatch(path);
152+
}
153+
132154
/// Runs the formatter on every .dart file in [path] (and its subdirectories),
133155
/// and replaces them with their formatted output.
134156
///
@@ -156,6 +178,9 @@ Future<bool> _processDirectory(
156178
var parts = p.split(p.relative(entry.path, from: directory.path));
157179
if (parts.any((part) => part.startsWith('.'))) continue;
158180

181+
// Check if the file should be excluded.
182+
if (_isExcluded(entry.path, options.excludePatterns)) continue;
183+
159184
if (!await _processFile(
160185
cache,
161186
options,

test/cli/cli_test.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,91 @@ void main() {
247247
await process.shouldExit(70);
248248
});
249249
});
250+
251+
group('--exclude', () {
252+
test('excludes specific files', () async {
253+
await d.dir('code', [
254+
d.file('a.dart', unformattedSource),
255+
d.file('b.dart', unformattedSource),
256+
d.file('c.dart', unformattedSource),
257+
]).create();
258+
259+
var process = await runFormatter(['--exclude', 'b.dart', 'code']);
260+
await expectLater(
261+
process.stdout,
262+
emitsInOrder([
263+
'Formatted ${p.join('code', 'a.dart')}',
264+
'Formatted ${p.join('code', 'c.dart')}',
265+
]),
266+
);
267+
await expectLater(
268+
process.stdout,
269+
emits(startsWith('Formatted 2 files (2 changed)')),
270+
);
271+
await process.shouldExit(0);
272+
273+
// b.dart should remain unformatted.
274+
await d.dir('code', [
275+
d.file('a.dart', formattedSource),
276+
d.file('b.dart', unformattedSource),
277+
d.file('c.dart', formattedSource),
278+
]).validate();
279+
});
280+
281+
test('excludes directories', () async {
282+
await d.dir('code', [
283+
d.dir('subdir', [d.file('a.dart', unformattedSource)]),
284+
d.file('b.dart', unformattedSource),
285+
]).create();
286+
287+
var process = await runFormatter(['--exclude', 'subdir', 'code']);
288+
await expectLater(
289+
process.stdout,
290+
emitsInOrder(['Formatted ${p.join('code', 'b.dart')}']),
291+
);
292+
await expectLater(
293+
process.stdout,
294+
emits(startsWith('Formatted 1 file (1 changed)')),
295+
);
296+
await process.shouldExit(0);
297+
298+
// subdir/a.dart should remain unformatted.
299+
await d.dir('code', [
300+
d.dir('subdir', [d.file('a.dart', unformattedSource)]),
301+
d.file('b.dart', formattedSource),
302+
]).validate();
303+
});
304+
305+
test('supports multiple exclude patterns', () async {
306+
await d.dir('code', [
307+
d.file('a.dart', unformattedSource),
308+
d.file('b.dart', unformattedSource),
309+
d.file('c.dart', unformattedSource),
310+
]).create();
311+
312+
var process = await runFormatter([
313+
'--exclude',
314+
'a.dart',
315+
'--exclude',
316+
'c.dart',
317+
'code',
318+
]);
319+
await expectLater(
320+
process.stdout,
321+
emitsInOrder(['Formatted ${p.join('code', 'b.dart')}']),
322+
);
323+
await expectLater(
324+
process.stdout,
325+
emits(startsWith('Formatted 1 file (1 changed)')),
326+
);
327+
await process.shouldExit(0);
328+
329+
// a.dart and c.dart should remain unformatted.
330+
await d.dir('code', [
331+
d.file('a.dart', unformattedSource),
332+
d.file('b.dart', formattedSource),
333+
d.file('c.dart', unformattedSource),
334+
]).validate();
335+
});
336+
});
250337
}

0 commit comments

Comments
 (0)