focus-flow/tool/check_docs.dart

111 lines
3.2 KiB
Dart

/// Runs the Check Docs workspace validation tool.
library;
import 'dart:io';
/// Generates Dart docs for each package with public libraries and fails on warnings.
Future<void> main(List<String> arguments) async {
final missingFileDocs = await _trackedDartFilesMissingFileDocs();
if (missingFileDocs.isNotEmpty) {
stderr.writeln('Dart files missing file-level Dartdoc library headers:');
for (final path in missingFileDocs) {
stderr.writeln('- $path');
}
}
final outputRoot = Directory(_join(_join('doc', 'api'), 'packages'));
if (await outputRoot.exists()) {
await outputRoot.delete(recursive: true);
}
await outputRoot.create(recursive: true);
final packages = Directory('packages')
.listSync()
.whereType<Directory>()
.where((directory) {
return File(_join(directory.path, 'pubspec.yaml')).existsSync() &&
Directory(_join(directory.path, 'lib')).existsSync();
}).toList(growable: false)
..sort((left, right) => left.path.compareTo(right.path));
var failed = missingFileDocs.isNotEmpty;
for (final package in packages) {
final outputPath = _join(outputRoot.absolute.path, _basename(package.path));
stdout.writeln('> dart doc --output $outputPath (${package.path})');
final result = await Process.run(
'dart',
<String>['doc', '--output', outputPath],
workingDirectory: package.path,
runInShell: Platform.isWindows,
);
final output = '${result.stdout}${result.stderr}';
stdout.write(output);
if (result.exitCode != 0 || _hasWarnings(output)) {
failed = true;
}
}
if (failed) {
stderr.writeln('Dart documentation gate failed.');
exitCode = 1;
}
}
Future<List<String>> _trackedDartFilesMissingFileDocs() async {
final result = await Process.run(
'git',
<String>['ls-files', '*.dart'],
runInShell: Platform.isWindows,
);
if (result.exitCode != 0) {
throw ProcessException(
'git',
<String>['ls-files', '*.dart'],
'Unable to list tracked Dart files.',
result.exitCode,
);
}
final paths = (result.stdout as String)
.split('\n')
.where((path) => path.trim().isNotEmpty)
.toList(growable: false)
..sort();
final missing = <String>[];
for (final path in paths) {
final file = File(path);
final contents = await file.readAsString();
if (_isGeneratedPart(path, contents)) {
continue;
}
if (!_hasFileLevelLibraryDocs(contents)) {
missing.add(path);
}
}
return missing;
}
bool _isGeneratedPart(String path, String contents) {
return path.endsWith('.g.dart') || contents.startsWith("part of '");
}
bool _hasFileLevelLibraryDocs(String contents) {
return RegExp(r'^\s*///[\s\S]*?\nlibrary;', multiLine: false)
.hasMatch(contents);
}
bool _hasWarnings(String output) {
return output.contains(' warning:') ||
RegExp(r'Found [1-9][0-9]* warnings').hasMatch(output);
}
String _join(String first, String second) {
final separator = Platform.pathSeparator;
return '$first$separator$second';
}
String _basename(String path) {
final normalized = path.replaceAll('\\', '/');
return normalized.substring(normalized.lastIndexOf('/') + 1);
}