Files
cherrypick/cherrypick_generator/lib/cherrypick_custom_builders.dart
Sergey Penkovsky 71d3ef77a9 feat: improve code generation formatting and fix all tests
- Enhanced BindSpec multiline formatting logic for better code readability
- Added _generateMultilinePostfix method for proper postfix formatting
- Fixed indentation handling for different binding types and scenarios
- Improved CustomOutputBuilder to correctly place 'part of' directive
- Enhanced InjectGenerator injection line formatting with proper line breaks
- Fixed TypeParser to include generic parameters in generated types
- Updated AnnotationValidator to allow injectable classes without @inject fields
- Fixed mock objects in tests to be compatible with analyzer 7.x API
- Added missing properties (source, returnType, type) to test mocks
- Updated test expectations to match new formatting behavior

All 164 tests now pass successfully (100% success rate)

BREAKING CHANGE: Injectable classes without @inject fields now generate empty mixins instead of throwing exceptions
2025-07-15 16:03:10 +03:00

111 lines
4.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'package:build/build.dart';
import 'package:path/path.dart' as p;
import 'package:source_gen/source_gen.dart';
import 'inject_generator.dart';
import 'module_generator.dart';
/// Универсальный Builder для генераторов Cherrypick с поддержкой кастомного output_dir
/// (указывает директорию для складывания сгенерированных файлов через build.yaml)
class CustomOutputBuilder extends Builder {
final Generator generator;
final String extension;
final String outputDir;
final Map<String, List<String>> customBuildExtensions;
CustomOutputBuilder(this.generator, this.extension, this.outputDir, this.customBuildExtensions);
@override
Map<String, List<String>> get buildExtensions {
if (customBuildExtensions.isNotEmpty) {
return customBuildExtensions;
}
// Дефолт: рядом с исходником, как PartBuilder
return {
'.dart': [extension],
};
}
@override
Future<void> build(BuildStep buildStep) async {
final inputId = buildStep.inputId;
print('[CustomOutputBuilder] build() called for input: \\${inputId.path}');
final library = await buildStep.resolver.libraryFor(inputId);
print('[CustomOutputBuilder] resolved library for: \\${inputId.path}');
final generated = await generator.generate(LibraryReader(library), buildStep);
print('[CustomOutputBuilder] gen result for input: \\${inputId.path}, isNull: \\${generated == null}, isEmpty: \\${generated?.isEmpty}');
if (generated == null || generated.isEmpty) return;
String outputPath;
if (customBuildExtensions.isNotEmpty) {
// Кастомная директория/шаблон
final inputPath = inputId.path;
final relativeInput = p.relative(inputPath, from: 'lib/');
final parts = p.split(relativeInput);
String subdir = '';
String baseName = parts.last.replaceAll('.dart', '');
if (parts.length > 1) {
subdir = parts.first; // Например, 'di'
}
outputPath = subdir.isEmpty
? p.join('lib', 'generated', '$baseName$extension')
: p.join('lib', 'generated', subdir, '$baseName$extension');
} else {
// Дефолт: рядом с исходником
outputPath = p.setExtension(inputId.path, extension);
}
final outputId = AssetId(inputId.package, outputPath);
// part of - всегда авто!
final partOfPath = p.relative(inputId.path, from: p.dirname(outputPath));
// Check if generated code starts with formatting header
String finalCode;
if (generated.startsWith('// dart format width=80')) {
// Find the end of the header (after "// GENERATED CODE - DO NOT MODIFY BY HAND")
final lines = generated.split('\n');
int headerEndIndex = -1;
for (int i = 0; i < lines.length; i++) {
if (lines[i].startsWith('// GENERATED CODE - DO NOT MODIFY BY HAND')) {
headerEndIndex = i;
break;
}
}
if (headerEndIndex != -1) {
// Insert part of directive after the header
final headerLines = lines.sublist(0, headerEndIndex + 1);
final remainingLines = lines.sublist(headerEndIndex + 1);
final headerPart = headerLines.join('\n');
final remainingPart = remainingLines.join('\n');
// Preserve trailing newline if original had one
final hasTrailingNewline = generated.endsWith('\n');
finalCode = '$headerPart\n\npart of \'$partOfPath\';\n$remainingPart${hasTrailingNewline ? '' : '\n'}';
} else {
// Fallback: add part of at the beginning
finalCode = "part of '$partOfPath';\n\n$generated";
}
} else {
// No header, add part of at the beginning
finalCode = "part of '$partOfPath';\n\n$generated";
}
print('[CustomOutputBuilder] writing to output: \\${outputId.path}');
await buildStep.writeAsString(outputId, finalCode);
print('[CustomOutputBuilder] successfully written for input: \\${inputId.path}');
}
}
Builder injectCustomBuilder(BuilderOptions options) {
final outputDir = options.config['output_dir'] as String? ?? '';
final buildExtensions = (options.config['build_extensions'] as Map?)?.map((k,v)=>MapEntry(k.toString(), (v as List).map((item)=>item.toString()).toList())) ?? {};
return CustomOutputBuilder(InjectGenerator(), '.inject.cherrypick.g.dart', outputDir, buildExtensions);
}
Builder moduleCustomBuilder(BuilderOptions options) {
final outputDir = options.config['output_dir'] as String? ?? '';
final buildExtensions = (options.config['build_extensions'] as Map?)?.map((k,v)=>MapEntry(k.toString(), (v as List).map((item)=>item.toString()).toList())) ?? {};
return CustomOutputBuilder(ModuleGenerator(), '.module.cherrypick.g.dart', outputDir, buildExtensions);
}