Skip to content

Commit

Permalink
@ F r integrated Richard's solution (force)
Browse files Browse the repository at this point in the history
  • Loading branch information
yelmuratoff committed Jun 29, 2024
1 parent aaead54 commit 30c1e1a
Show file tree
Hide file tree
Showing 12 changed files with 1,343 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ generated_*

# Other files
.old.dart
.build

.DS_Store
17 changes: 17 additions & 0 deletions lib/src/widget/src/common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'dart:io';

const topBar = '▶▶▶▶';
const bottomBar = '◀◀◀◀';

/// [String] extension
extension StringApprovedExtension on String {
/// git diff complains when file doesn't end in newline. This getter ensures a string does.
String get endWithNewline => endsWith('\n') ? this : '$this\n';
}

extension DirectoryApprovedExtension on Directory {
Set<File> filesWithExtension(String extension) {
final fileSystemEntities = listSync().where((entity) => entity is File && entity.path.endsWith(extension));
return fileSystemEntities.whereType<File>().toSet();
}
}
166 changes: 166 additions & 0 deletions lib/src/widget/src/get_widget_names.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:analyzer/dart/analysis/context_builder.dart';
import 'package:analyzer/dart/analysis/context_locator.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:approval_tests/src/widget/src/common.dart';

final _widgetNamesDir = Directory('./test/approved');
final _widgetNamesPath = '${_widgetNamesDir.path}/class_names.txt';

Future<Set<String>> getWidgetNames() async {
final resultCompleter = Completer<Set<String>>();

await isWidgetNamesFileFresh().then((isFileFresh) {
if (isFileFresh) {
readWidgetsFile(_widgetNamesPath).then((widgetNames) {
resultCompleter.complete(widgetNames);
});
} else {
final libPath = '${Directory.current.absolute.path}/lib';
stdout.write("package:approved: searching for class names in $libPath...");
extractWidgetNames(libPath).then((widgetsList) {
if (!_widgetNamesDir.existsSync()) {
_widgetNamesDir.createSync();
}
_widgetNamesDir.createSync();
final widgetsFile = File(_widgetNamesPath);
widgetsFile.createSync();
const header = '''
# This file was autogenerated by package:approved. Please do not edit.
# Below is a list of class found in the project /lib folder.''';
final widgetNamesString = widgetsList.join('\n').endWithNewline;
widgetsFile.writeAsStringSync('$header\n$widgetNamesString');
stdout.write('done\n');
resultCompleter.complete(widgetsList);
});
}
});

return resultCompleter.future;
}

String loadWidgetNames() {
late String result;
final widgetNamesFile = File(_widgetNamesPath);
if (widgetNamesFile.existsSync()) {
result = widgetNamesFile.readAsStringSync();
}
return result;
}

/// Crawls the project and extracts widget names.
Future<Set<String>> extractWidgetNames(String libPath) async {
final completer = Completer<Set<String>>();

final contextLocator = ContextLocator();
final contextRoots = contextLocator.locateRoots(includedPaths: [libPath]);

final contextBuilder = ContextBuilder();
await getFlutterSdkPath().then((path) {
final dartStr = '$path/bin/cache/dart-sdk';
final analysisContext = contextBuilder.createContext(
contextRoot: contextRoots.first,
sdkPath: dartStr,
);

final classNames = <String>{};

// Traverse all files in the lib folder
final libDirectory = Directory(libPath);
final dartFiles = libDirectory.listSync(recursive: true).where(
(file) =>
file.path.endsWith('.dart') && !file.path.contains('.g.dart') && !file.path.contains('.freezed.dart'),
);

for (final file in dartFiles) {
final analysisSession = analysisContext.currentSession;
final parsedResult = analysisSession.getParsedUnit(file.path) as ParsedUnitResult;

for (final compilationUnitMember in parsedResult.unit.declarations) {
if (compilationUnitMember is ClassDeclaration) {
final String name = compilationUnitMember.name.value().toString();
if (!name.startsWith('_')) {
classNames.add(name);
}
}
}
}

completer.complete(classNames);
});

return completer.future;
}

/// Get the path to the Flutter SDK
Future<String> getFlutterSdkPath() async {
final completer = Completer<String>();

await Process.run('flutter', ['--version', '--machine']).then((result) {
if (result.exitCode != 0) {
throw Exception('Failed to run flutter command: ${result.stderr}');
}

final jsonData = jsonDecode(result.stdout.toString()) as Map<String, dynamic>;

completer.complete(jsonData['flutterRoot'].toString());
});

return completer.future;
}

Future<Set<String>> readWidgetsFile(String filePath) async {
final completer = Completer<Set<String>>();
final File file = File(filePath);

await file.readAsString().then((text) {
// Split by lines
final linesList = text.split('\n');
// Remove empty lines and comments
final linesSet = linesList.where((line) => line.isNotEmpty && !line.startsWith('#')).toSet();
completer.complete(linesSet);
});

return completer.future;
}

Future<bool> isWidgetNamesFileFresh() async {
final resultCompleter = Completer<bool>();

final libDirectory = Directory('lib');

await findNewestDartFileTimestamp(libDirectory).then((dateTime) {
final widgetNamesFile = File(_widgetNamesPath);
if (dateTime != null && widgetNamesFile.existsSync() && widgetNamesFile.lastModifiedSync().isAfter(dateTime)) {
resultCompleter.complete(true);
} else {
resultCompleter.complete(false);
}
});

return resultCompleter.future;
}

Future<DateTime?> findNewestDartFileTimestamp(Directory dir) async {
DateTime? newestTimestamp;

if (!await dir.exists()) {
return null;
}

await for (final FileSystemEntity entity in dir.list(recursive: true)) {
if (entity is File && entity.path.endsWith('.dart')) {
final DateTime lastModified = await entity.lastModified();

if (newestTimestamp == null || lastModified.isAfter(newestTimestamp)) {
newestTimestamp = lastModified;
}
}
}

return newestTimestamp;
}
36 changes: 36 additions & 0 deletions lib/src/widget/src/git_diffs.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// ignore_for_file: avoid_print

import 'dart:io';

import 'package:approval_tests/src/widget/src/common.dart';

void printGitDiffs(String testDescription, String differences) {
print(topBar);
print("Results of git diff during approvalTest('$testDescription'):");
print(differences.trim());
print(bottomBar);
}

/// return the diff of two files
String gitDiffFiles(File path0, FileSystemEntity path1) {
final processResult = Process.runSync('git', ['diff', '--no-index', path0.path, path1.path]);

final stdoutString = processResult.stdout as String;
final stderrString = processResult.stderr as String;

final processString = stdoutString.isNotEmpty || stderrString.isNotEmpty ? stdoutString : '';

return _stripGitDiff(processString);
}

/// Format the git --diff if superfluous text
String _stripGitDiff(String multiLineString) {
bool startsWithAny(String line, List<String> prefixes) => prefixes.any((prefix) => line.startsWith(prefix));

final List<String> lines = multiLineString.split('\n');
final List<String> filteredLines = lines.where((line) => !startsWithAny(line, ['diff', 'index', '@@'])).toList();

final String result = filteredLines.join('\n');

return result;
}
Loading

0 comments on commit 30c1e1a

Please sign in to comment.