processProviderFile static method
Implementation
static Future<void> processProviderFile(
String contents,
File file, {
bool log = false,
}) async {
final sw = Stopwatch()..start();
try {
var effectiveContents = contents;
if (_fileContentCache[file.path] == effectiveContents) {
return; // Content hasn't changed
}
final fileName = file.uri.pathSegments.last;
final partName = fileName.replaceAll('.dart', '.craft.dart');
final generatedFilePath =
'${file.parent.path.endsWith('/') ? file.parent.path : '${file.parent.path}/'}$partName';
final generatedFile = File(generatedFilePath);
// Fast path: if file clearly has no plugin annotations, avoid analyzer parse.
// The check is driven by the union of all registered plugin annotation names.
final stripped = effectiveContents
// Remove block comments
.replaceAll(RegExp(r'/\*.*?\*/', dotAll: true), '')
// Remove line comments
.replaceAll(RegExp(r'//.*'), '');
final allAnnotations = _pluginRunner.allAnnotations;
final maybeAnnotated = allAnnotations.any(
(name) => stripped.contains('@$name'),
);
if (!maybeAnnotated) {
final hasPartLine =
effectiveContents.contains("part '$partName';") ||
effectiveContents.contains('part "$partName";');
final generatedExists = await generatedFile.exists();
if (hasPartLine || generatedExists) {
await _cleanupPartLineSimple(file, partName);
if (generatedExists) {
await generatedFile.delete();
}
_fileContentCache[file.path] = await file.readAsString();
} else {
_fileContentCache[file.path] = effectiveContents;
}
return;
}
final parsedResult = parseString(content: effectiveContents);
final parsedUnit = parsedResult.unit;
// Run the plugin pipeline
final generatedContent = _pluginRunner.run(parsedResult);
if (generatedContent == null || generatedContent.isEmpty) {
await _cleanupPartAndGenerated(file, parsedUnit);
_fileContentCache[file.path] = await file.readAsString();
return;
}
// Check if file has paged providers
final hasPaged = generatedContent.contains('PagedDataNotifier');
final pagedPreamble = hasPaged ? _buildPagedPreamble() : '';
// Inline the global error mapper function when a generated notifier
// routes its errors through it.
final usesErrorMapper = generatedContent.contains(
'${CraftConfig.errorMapperInlineName}(',
);
final errorMapperPreamble = usesErrorMapper
? _buildErrorMapperPreamble()
: '';
effectiveContents = await _ensurePartDirective(
file,
effectiveContents,
parsedUnit,
);
_fileContentCache[file.path] = effectiveContents;
final fullContent = "// GENERATED CODE - DO NOT MODIFY BY HAND\n// ignore_for_file: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member\npart of '$fileName';\n$pagedPreamble$errorMapperPreamble\n$generatedContent";
final formatter = DartFormatter(
languageVersion: DartFormatter.latestLanguageVersion,
);
final formatted = formatter.format(fullContent);
// Only write when the generated part actually changed; a no-op rewrite
// would needlessly re-trigger the IDE analyzer (and any file watcher).
final genFile = File(generatedFilePath);
if (!await genFile.exists() || await genFile.readAsString() != formatted) {
await genFile.writeAsString(formatted);
if (log) {
print(
' 🔧 ${p.relative(generatedFilePath, from: Directory.current.path)}'
' · ${sw.elapsedMilliseconds}ms',
);
}
}
} on FileSystemException catch (e) {
print('FileSystemException: ${e.message}, path = ${e.path}');
} catch (e) {
print('Error processing file "${file.path}": $e');
}
}