processProviderFile static method

Future<void> processProviderFile(
  1. String contents,
  2. File file, {
  3. bool log = false,
})

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');
  }
}