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

CPLAT-10490 - Have format_tool add whole directories 💼 #340

Closed
Closed
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
96 changes: 85 additions & 11 deletions lib/src/tools/format_tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class FormatTool extends DevTool {
/// By default, nothing is excluded.
List<Glob> exclude;

/// If true, pass whole directories with no exclusions to the formatter.
///
/// This is helpful for large projects that may cause the formatter command to have too many arguments
bool collapseDirectories = false;

/// The formatter to run, one of:
/// - `dartfmt` (provided by the SDK)
/// - `pub run dart_style:format` (provided by the `dart_style` package)
Expand Down Expand Up @@ -99,11 +104,14 @@ class FormatTool extends DevTool {
@override
FutureOr<int> run([DevToolExecutionContext context]) {
context ??= DevToolExecutionContext();
final execution = buildExecution(context,
configuredFormatterArgs: formatterArgs,
defaultMode: defaultMode,
exclude: exclude,
formatter: formatter);
final execution = buildExecution(
context,
configuredFormatterArgs: formatterArgs,
defaultMode: defaultMode,
exclude: exclude,
formatter: formatter,
collapseDirectories: collapseDirectories,
);
return execution.exitCode ??
runProcessAndEnsureExit(execution.process, log: _log);
}
Expand All @@ -119,8 +127,18 @@ class FormatTool extends DevTool {
///
/// By default these globs are assumed to be relative to the current working
/// directory, but that can be overridden via [root] for testing purposes.
static FormatterInputs getInputs(
{List<Glob> exclude, bool expandCwd, bool followLinks, String root}) {
///
/// If collapseDirectories is true, directories that contain no exclusions will wind up in the [FormatterInputs],
/// rather than each file in that tree. You may get unexpected results if this and followLinks are both true.
static FormatterInputs getInputs({
List<Glob> exclude,
bool expandCwd,
bool followLinks,
String root,
bool collapseDirectories = false,
}) {
_log.finest(
'getInputs exclude $exclude, expandCwd $expandCwd, followLinks $followLinks, root $root, collapseDirectories $collapseDirectories');
expandCwd ??= false;
followLinks ??= false;

Expand All @@ -137,16 +155,33 @@ class FormatTool extends DevTool {

final dir = Directory(root ?? '.');

String currentDirectory = '';
bool skipFilesInDirectory = false;
for (final entry
in dir.listSync(recursive: true, followLinks: followLinks)) {
final relative = p.relative(entry.path, from: dir.path);
_log.finest('\n== Processing relative $relative ==');

if (p.isWithin(currentDirectory, relative)) {
if (skipFilesInDirectory) {
_log.finest('skipping child $entry');
continue;
}
} else {
// the file/dir in not inside, cancel skipping.
skipFilesInDirectory = false;
}

if (entry is Link) {
_log.finest('skipping link $relative');
skippedLinks.add(relative);
continue;
}

if (entry is File && !entry.path.endsWith('.dart')) continue;
if (entry is File && !entry.path.endsWith('.dart')) {
_log.finest('skipping non-dart file $relative');
continue;
}

// If the path is in a subdirectory starting with ".", ignore it.
final parts = p.split(relative);
Expand All @@ -161,20 +196,49 @@ class FormatTool extends DevTool {
if (hiddenIndex != null) {
final hiddenDirectory = p.joinAll(parts.take(hiddenIndex + 1));
hiddenDirectories.add(hiddenDirectory);
_log.finest('skipping hidden dir $hiddenDirectory');
if (collapseDirectories) {
currentDirectory = relative;
skipFilesInDirectory = true;
}
continue;
}

if (exclude.any((glob) => glob.matches(relative))) {
_log.finest('excluding $relative');
excludedFiles.add(relative);
} else {
if (entry is File) includedFiles.add(relative);
if (collapseDirectories && entry is Directory) {
_log.finest('directory: $entry');
currentDirectory = relative;
// It seems we can rely on the order of files coming from listSync.
// If a directory does not match any of the globs, and does not contain any of the globs,
// we should be able to just add that directory and skip adding any of its children files or directories.
if (exclude.any((glob) => p.isWithin(
entry.path, p.relative(glob.toString(), from: dir.path)))) {
_log.finest('directory has excludes');
} else {
skipFilesInDirectory = true;
_log.finest(
"directory does not have excludes skipping children and adding $relative");
includedFiles.add(relative);
}
}

if (entry is File && !skipFilesInDirectory) {
_log.finest("adding $relative");
includedFiles.add(relative);
}
}
}

return FormatterInputs(includedFiles,
var formatterInputs = FormatterInputs(includedFiles,
excludedFiles: excludedFiles,
skippedLinks: skippedLinks,
hiddenDirectories: hiddenDirectories);

_log.finest("getInputs done $formatterInputs");
return formatterInputs;
}
}

Expand All @@ -189,6 +253,11 @@ class FormatterInputs {
final Set<String> includedFiles;

final Set<String> skippedLinks;

@override
String toString() {
return 'FormatterInputs{\nexcludedFiles:\n$excludedFiles,\nhiddenDirectories:\n$hiddenDirectories,\nincludedFiles:\n$includedFiles,\nskippedLinks:\n$skippedLinks,\n}';
}
}

/// A declarative representation of an execution of the [FormatTool].
Expand Down Expand Up @@ -311,6 +380,7 @@ FormatExecution buildExecution(
List<Glob> exclude,
Formatter formatter,
String path,
bool collapseDirectories,
}) {
FormatMode mode;
if (context.argResults != null) {
Expand All @@ -333,7 +403,11 @@ FormatExecution buildExecution(
'format tool to use "dartfmt" instead.'));
return FormatExecution.exitEarly(ExitCode.config.code);
}
final inputs = FormatTool.getInputs(exclude: exclude, root: path);
final inputs = FormatTool.getInputs(
exclude: exclude,
root: path,
collapseDirectories: collapseDirectories,
);

if (inputs.includedFiles.isEmpty) {
_log.severe('The formatter cannot run because no inputs could be found '
Expand Down
29 changes: 29 additions & 0 deletions test/tools/format_tool_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@ void main() {
unorderedEquals({'links/lib-link', 'links/link.dart'}));
});

test('custom excludes with collapseDirectories', () {
FormatterInputs formatterInputs = FormatTool.getInputs(
exclude: [Glob('*_exclude.dart')],
root: root,
collapseDirectories: true,
);

expect(
formatterInputs.includedFiles,
unorderedEquals({
'file.dart',
'lib',
'linked.dart',
'other',
'links',
}),
);

expect(
formatterInputs.excludedFiles,
unorderedEquals({'should_exclude.dart'}),
);
expect(
formatterInputs.hiddenDirectories,
unorderedEquals({'.dart_tool_test'}),
);
expect(formatterInputs.skippedLinks, isEmpty);
});

test('empty inputs due to excludes config', () async {
expect(
FormatTool.getInputs(exclude: [Glob('**')], root: root)
Expand Down