Skip to content

Commit c8f3ca7

Browse files
authored
fix: .value not correctly generated (#7)
1 parent 24c1b78 commit c8f3ca7

File tree

3 files changed

+164
-29
lines changed

3 files changed

+164
-29
lines changed

packages/solid_generator/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.0.2
2+
3+
- **FIX**: Generator not transpiling code correctly in some cases.
4+
15
## 1.0.1
26

37
- **FIX**: Remove Flutter SDK.

packages/solid_generator/lib/src/solid_builder.dart

Lines changed: 159 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,47 @@ class SolidBuilder extends Builder {
2323
@override
2424
FutureOr<void> build(BuildStep buildStep) async {
2525
final inputId = buildStep.inputId;
26-
print('DEBUG: SolidBuilder.build() called for: ${inputId.path}');
27-
2826
// Skip if not a source file we should process
2927
if (!_shouldProcess(inputId)) {
30-
print('DEBUG: Skipping ${inputId.path} - not a processable file');
3128
return;
3229
}
3330

34-
print('DEBUG: Processing ${inputId.path}');
35-
3631
try {
37-
// Read the input file
32+
// Read the input file content (still needed for formatting preservation)
3833
final content = await buildStep.readAsString(inputId);
3934

40-
// Parse the Dart code
41-
final parseResult = parseString(
42-
content: content,
43-
featureSet: FeatureSet.latestLanguageVersion(),
44-
);
35+
CompilationUnit compilationUnit;
36+
37+
try {
38+
// PERFORMANCE OPTIMIZATION: Use build_runner's optimized AST parsing via resolver
39+
// Benefits over manual parseString():
40+
// 1. Cached parsing - build_runner reuses already-parsed ASTs
41+
// 2. Resolved type information - no need to manually resolve imports
42+
// 3. Incremental compilation - only re-parse changed files
43+
// 4. Better error handling with resolved context
44+
// 5. Faster subsequent builds due to build system caching
45+
final resolver = buildStep.resolver;
46+
compilationUnit = await resolver.compilationUnitFor(inputId);
47+
// Using optimized resolver path
48+
} catch (resolverError) {
49+
// FALLBACK: Use manual parsing if resolver is not available (e.g., in test environments)
50+
final parseResult = parseString(
51+
content: content,
52+
featureSet: FeatureSet.latestLanguageVersion(),
53+
);
4554

46-
if (parseResult.errors.isNotEmpty) {
47-
log.warning('Parse errors in ${inputId.path}: ${parseResult.errors}');
48-
return;
55+
if (parseResult.errors.isNotEmpty) {
56+
log.warning('Parse errors in ${inputId.path}: ${parseResult.errors}');
57+
return;
58+
}
59+
60+
compilationUnit = parseResult.unit;
61+
// Using fallback parseString path
4962
}
5063

51-
// Transform the AST
64+
// Transform the AST using either the optimized or fallback compilation unit
5265
final transformedCode = await _transformAst(
53-
parseResult.unit,
66+
compilationUnit,
5467
inputId.path,
5568
content,
5669
);
@@ -90,6 +103,35 @@ class SolidBuilder extends Builder {
90103
return transformAstForTesting(unit, filePath, originalContent);
91104
}
92105

106+
/// Enhanced version that can leverage resolver for type information
107+
/// This provides access to resolved types for even more advanced optimizations
108+
///
109+
/// The resolver provides:
110+
/// - resolver.typeProvider: Access to common types (String, int, etc.)
111+
/// - resolver.libraryFor(element): Get library information
112+
/// - element.staticType: Resolved types for AST nodes
113+
/// - Dependency graph information for better optimization decisions
114+
///
115+
/// Performance benefits over manual parsing:
116+
/// - Cached AST parsing (build_runner reuses parsed trees)
117+
/// - Resolved type information (no need to manually resolve imports/types)
118+
/// - Better error handling and incremental compilation support
119+
/// - Faster subsequent builds due to build_runner's caching
120+
Future<String> transformAstWithResolver(
121+
CompilationUnit unit,
122+
String filePath,
123+
String originalContent,
124+
Resolver resolver,
125+
) async {
126+
// For now, delegate to the existing method
127+
// In the future, we can leverage:
128+
// - resolver.typeProvider for type checking
129+
// - resolver.libraryFor() for dependency analysis
130+
// - Static type information for more intelligent transformations
131+
// - Better error messages with resolved context
132+
return transformAstForTesting(unit, filePath, originalContent);
133+
}
134+
93135
/// Public method for testing the transformation logic
94136
Future<String> transformAstForTesting(
95137
CompilationUnit unit,
@@ -864,6 +906,65 @@ class SolidBuilder extends Builder {
864906

865907
if (buildMethod == null) return content; // No build method found
866908

909+
// Apply reactive field transformation to the build method
910+
911+
// Extract field names, distinguishing between Signal fields and Resource/Effect methods
912+
final signalFieldNames = <String>{}; // @SolidState fields that need .value
913+
final resourceMethodNames = <String>{}; // @SolidQuery/@SolidEffect methods that don't need .value
914+
915+
for (final member in classDeclaration.members) {
916+
if (member is FieldDeclaration) {
917+
// Check for @SolidState annotation on fields
918+
final annotations = member.metadata;
919+
final hasSolidState = annotations.any(
920+
(annotation) => annotation.name.name == 'SolidState',
921+
);
922+
if (hasSolidState) {
923+
for (final variable in member.fields.variables) {
924+
signalFieldNames.add(variable.name.lexeme);
925+
}
926+
}
927+
} else if (member is MethodDeclaration) {
928+
// Check for reactive annotations on methods/getters
929+
final annotations = member.metadata;
930+
final hasSolidQuery = annotations.any(
931+
(annotation) => annotation.name.name == 'SolidQuery',
932+
);
933+
final hasSolidEffect = annotations.any(
934+
(annotation) => annotation.name.name == 'SolidEffect',
935+
);
936+
937+
if (hasSolidQuery || hasSolidEffect) {
938+
// Resource/Effect methods don't need .value transformation
939+
resourceMethodNames.add(member.name.lexeme);
940+
}
941+
}
942+
}
943+
944+
// Transform reactive field access in the build method
945+
// Only apply .value transformation to Signal fields, not Resource/Effect methods
946+
if (signalFieldNames.isNotEmpty) {
947+
for (final fieldName in signalFieldNames) {
948+
// Transform string interpolation: $fieldName to ${fieldName.value}
949+
final interpolationPattern = RegExp(
950+
r'\$' + RegExp.escape(fieldName) + r'(?!\.value)(?!\w)',
951+
);
952+
buildMethod = buildMethod!.replaceAll(
953+
interpolationPattern,
954+
'\${$fieldName.value}',
955+
);
956+
957+
// Transform direct access: fieldName to fieldName.value
958+
final directAccessPattern = RegExp(
959+
r'\b' + RegExp.escape(fieldName) + r'\b(?!\.value)(?!\()',
960+
);
961+
buildMethod = buildMethod.replaceAll(
962+
directAccessPattern,
963+
'$fieldName.value',
964+
);
965+
}
966+
}
967+
867968
// Check if there's already an initState method in the original class
868969
MethodDeclaration? existingInitState;
869970
for (final member in classDeclaration.members) {
@@ -1320,7 +1421,8 @@ class SolidBuilder extends Builder {
13201421

13211422
// Look for lines that contain widget constructor calls
13221423
// Pattern: WidgetName( or _WidgetName( (widget constructors)
1323-
final widgetCallPattern = RegExp(r'(\s*)(?:return\s+)?(_?[A-Z]\w*)\s*\(');
1424+
// This should match actual constructor calls, not method calls
1425+
final widgetCallPattern = RegExp(r'(\s*)(?:return\s+)?([A-Z]\w*)\s*\(');
13241426
final match = widgetCallPattern.firstMatch(line);
13251427

13261428
if (match != null) {
@@ -1339,17 +1441,44 @@ class SolidBuilder extends Builder {
13391441
}
13401442

13411443
final indentation = match.group(1) ?? '';
1444+
final widgetName = match.group(2) ?? '';
13421445

1343-
// Check if the line contains reactive field references
1446+
// Skip if this is clearly a method call (has a dot before the matched name)
1447+
if (line.contains('.$widgetName(')) {
1448+
resultLines.add(line);
1449+
continue;
1450+
}
1451+
1452+
// Skip non-widget classes
1453+
if (['Navigator', 'Future', 'Stream', 'Duration', 'Timer', 'Named', 'AllMapped', 'UpperCase', 'RegExp', 'At', 'Data', 'Counter'].contains(widgetName)) {
1454+
resultLines.add(line);
1455+
continue;
1456+
}
1457+
1458+
// Skip parameter assignments (lines containing : before =>)
1459+
if (line.contains(':') && line.contains('=>')) {
1460+
resultLines.add(line);
1461+
continue;
1462+
}
1463+
1464+
// Check if the line contains reactive field references AND is a widget constructor
13441465
bool containsReactiveAccess = false;
1345-
for (final fieldName in signalFieldNames) {
1346-
if (line.contains('\$$fieldName') ||
1347-
line.contains('\${$fieldName') ||
1348-
line.contains('$fieldName.') ||
1349-
line.contains('$fieldName,') ||
1350-
line.contains('$fieldName)')) {
1351-
containsReactiveAccess = true;
1352-
break;
1466+
1467+
// First check if this is actually a widget constructor line
1468+
bool isWidgetConstructor = true; // Assume it's a widget if we got this far
1469+
1470+
1471+
// Only check for reactive access if this is actually a widget constructor
1472+
if (isWidgetConstructor) {
1473+
for (final fieldName in signalFieldNames) {
1474+
if (line.contains('\$$fieldName') ||
1475+
line.contains('\${$fieldName') ||
1476+
line.contains('$fieldName.') ||
1477+
line.contains('$fieldName,') ||
1478+
line.contains('$fieldName)')) {
1479+
containsReactiveAccess = true;
1480+
break;
1481+
}
13531482
}
13541483
}
13551484

@@ -1438,16 +1567,18 @@ class SolidBuilder extends Builder {
14381567
}
14391568

14401569
// Create SignalBuilder wrapper for ANY widget accessing reactive values
1570+
String transformedLine = cleanedLine;
1571+
14411572
if (wasReturnStatement) {
14421573
resultLines.add('${indentation}return SignalBuilder(');
14431574
resultLines.add('$indentation builder: (context, child) {');
1444-
resultLines.add('$indentation return $cleanedLine;');
1575+
resultLines.add('$indentation return $transformedLine;');
14451576
resultLines.add('$indentation },');
14461577
resultLines.add('$indentation);');
14471578
} else {
14481579
resultLines.add('${indentation}SignalBuilder(');
14491580
resultLines.add('$indentation builder: (context, child) {');
1450-
resultLines.add('$indentation return $cleanedLine;');
1581+
resultLines.add('$indentation return $transformedLine;');
14511582
resultLines.add('$indentation },');
14521583
resultLines.add('$indentation),');
14531584
}

packages/solid_generator/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: solid_generator
22
description: The Solid transpiler to generate fine-grained reactivity in Flutter applications.
3-
version: 1.0.1
3+
version: 1.0.2
44
homepage: https://solid.mariuti.com
55
repository: https://github.com/nank1ro/solid
66
documentation: https://solid.mariuti.com

0 commit comments

Comments
 (0)