Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FED-1720 Conditionally null-safe builder output (tests) #868

Merged
merged 20 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bc71e44
First stab at conditional null safety output based on pubspec constraint
greglittlefield-wf Oct 14, 2023
ad11110
Tweak output to limit generated code diffs in over_react
greglittlefield-wf Oct 16, 2023
78a7719
Merge branch 'null-safety' into null-safety-conditional-builder
greglittlefield-wf Oct 16, 2023
b926c55
Try reading language version from package config instead, better pubs…
greglittlefield-wf Oct 16, 2023
d8d57ea
Remove pubspec parsing logic, clean up
greglittlefield-wf Oct 16, 2023
8e7d890
Remove pubspec parsing logic, clean up
greglittlefield-wf Oct 16, 2023
605faa8
Don't default to null safety in Dart 2
greglittlefield-wf Oct 16, 2023
937086b
Tweak null safety generation comments
greglittlefield-wf Oct 16, 2023
af4100e
Make null safety reason strings line up, add test case
greglittlefield-wf Oct 16, 2023
453e484
Add unit tests for language version utilities
greglittlefield-wf Oct 17, 2023
7b1cfd4
Copy builder_integration_tests from master to run in dart=2.11
sydneyjodon-wk Dec 6, 2023
183c763
Merge branch 'null-safety-conditional-builder' into FED-1720-conditio…
sydneyjodon-wk Dec 6, 2023
5b381b0
Update dart_test.yaml
sydneyjodon-wk Dec 6, 2023
fb49d16
Update dart_test.yaml
sydneyjodon-wk Dec 6, 2023
07c7287
Update build.yaml
sydneyjodon-wk Dec 7, 2023
f3f576f
Fix lints
sydneyjodon-wk Dec 7, 2023
4c72dd8
Merge branch 'v5_wip' into FED-1720-conditionally-null-safe-builder
sydneyjodon-wk Dec 11, 2023
399ac8c
Add package level tests
sydneyjodon-wk Dec 12, 2023
0e52744
Revert pubspec update
sydneyjodon-wk Dec 12, 2023
7ffd41a
Address feedback
sydneyjodon-wk Dec 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ targets:
# These tests analyze cleanly without a build so the generated files do not need to be checked in.
- "test/over_react/component_declaration/builder_integration_tests/backwards_compatible/**"
- "test/over_react/component_declaration/builder_integration_tests/new_boilerplate/**"
- "test/over_react/component_declaration/non_null_safe_builder_integration_tests/backwards_compatible/**"
- "test/over_react/component_declaration/non_null_safe_builder_integration_tests/new_boilerplate/**"
# These test un-built base class behavior, and contain code that might look like declarations
# and should definitely not be built
- "test/over_react/component_declaration/builder_helpers_test.dart"
Expand All @@ -49,6 +51,8 @@ targets:
# These tests analyze cleanly without a build so the generated files do not need to be checked in.
- "test/over_react/component_declaration/builder_integration_tests/backwards_compatible/**"
- "test/over_react/component_declaration/builder_integration_tests/new_boilerplate/**"
- "test/over_react/component_declaration/non_null_safe_builder_integration_tests/backwards_compatible/**"
- "test/over_react/component_declaration/non_null_safe_builder_integration_tests/new_boilerplate/**"
exclude:
- "web/component1/**"
# The Dart 2 only boilerplate does not analyze cleanly without a build so the generated files need to be
Expand Down
2 changes: 2 additions & 0 deletions dart_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ presets:
exclude_tags: no-ddc
paths:
- test/over_react_component_declaration_test.dart
- test/over_react_component_declaration_non_null_safe_test.dart
- test/over_react_component_test.dart
- test/over_react_dom_test.dart
- test/over_react_prod_test.dart
Expand All @@ -26,6 +27,7 @@ presets:
exclude_tags: ddc
paths:
- test/over_react_component_declaration_test.dart
- test/over_react_component_declaration_non_null_safe_test.dart
- test/over_react_component_test.dart
- test/over_react_dom_test.dart
- test/over_react_prod_test.dart
Expand Down
109 changes: 94 additions & 15 deletions lib/src/builder/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@
// limitations under the License.

import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:build/build.dart';
import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as p;
import 'package:package_config/package_config.dart' as pc;
import 'package:source_span/source_span.dart';

import './util.dart';
import 'codegen.dart';
import 'codegen/language_version_util.dart';
import 'parsing.dart';

Builder overReactBuilder(BuilderOptions? options) => OverReactBuilder();
Expand Down Expand Up @@ -58,6 +62,60 @@ class OverReactBuilder extends Builder {
return;
}

String nullSafetyReason;
bool nullSafety;
{
final versionToken = libraryUnit.languageVersionToken;
if (versionToken != null) {
// If there's a language version comment, honor that.
nullSafety = versionToken.supportsNullSafety;
nullSafetyReason = {
'languageVersion': '${versionToken.major}.${versionToken.minor}',
'source': 'libraryVersionComment',
}.toString();
} else {
// Otherwise, read the language version from the package config.
//
// Normally, builders would do this via (await buildStep.inputLibrary).languageVersion,
// but that would trigger resolved analysis which we don't want to do for this builder,
// for performance reasons.
final packageName = buildStep.inputId.package;

// Catch any errors coming from our implementation of `$packageConfig`.
// We can remove this once we switch to build 2.4.0's `packageConfig`
// (see `$packageConfig` doc comment for more info).
pc.LanguageVersion? packageConfigLanguageVersion;
try {
packageConfigLanguageVersion = (await buildStep.$packageConfig)
.packages
.firstWhereOrNull((p) => p.name == packageName)
?.languageVersion;
} catch (e, st) {
log.warning('Error reading package config', e, st);
}

if (packageConfigLanguageVersion != null) {
nullSafety = packageConfigLanguageVersion.supportsNullSafety;
nullSafetyReason = {
'languageVersion': packageConfigLanguageVersion,
'source': 'packageConfig',
'package': packageName,
}.toString();
} else {
// If we don't have any information to go off of, opt out of null safety in 2.x,
// and opt in for newer versions (Dart 3+).
// It should be pretty unlikely that we don't have access to the package config,
// or that the file being generated doesn't have an associated package.
final platformOnlySupportsNullSafety = !Platform.version.startsWith('2.');
nullSafety = platformOnlySupportsNullSafety;
nullSafetyReason = {
'languageVersion': 'unknown (no package config or package config entry)',
'defaultNullSafetyForCurrentSdk': platformOnlySupportsNullSafety,
}.toString();
}
}
}

final outputs = <String>[];
void generateForFile(String source, AssetId id, CompilationUnit unit) {
if (!mightContainDeclarations(source)) {
Expand Down Expand Up @@ -97,17 +155,6 @@ class OverReactBuilder extends Builder {
// Validate boilerplate declarations and generate if there aren't any errors.
//

// FIXME(null-safety) detect null safety for other packages (FED-1720)
bool nullSafety;
final languageVersionToken = unit.languageVersionToken;
if (languageVersionToken != null) {
nullSafety = languageVersionToken.major > 2 ||
(languageVersionToken.major == 2 && languageVersionToken.minor >= 12);
} else {
// During development, only enable null safety by default for over_react.
nullSafety = id.package == 'over_react';
}

final generator = ImplGenerator(log, sourceFile, nullSafety: nullSafety);

for (final declaration in declarations) {
Expand Down Expand Up @@ -170,10 +217,16 @@ class OverReactBuilder extends Builder {
log.severe('Missing "part \'$expectedPart\';".');
}

// Add an extra space, since `false` is one character longer than `true`, so that the next
// sentence lines up when grepped across multiple files.
final nullSafetyCommentText = 'Using nullSafety: $nullSafety.${nullSafety ? ' ' : ''} $nullSafetyReason';

// Generated part files must have matching language version comments, so copy them over if they exist.
// TODO use CompilationUnit.languageVersionToken instead of parsing this manually once we're sure we can get on analyzer version 0.39.5 or greater
final languageVersionCommentMatch = RegExp(r'//\s*@dart\s*=\s*.+').firstMatch(source);
await _writePart(buildStep, outputId, outputs, languageVersionComment: languageVersionCommentMatch?.group(0));
await _writePart(buildStep, outputId, outputs,
nullSafetyCommentText: nullSafetyCommentText,
languageVersionComment: languageVersionCommentMatch?.group(0));
} else {
if (hasOutputPartDirective()) {
log.warning('An over_react part directive was found in ${buildStep.inputId.path}, '
Expand All @@ -200,7 +253,8 @@ class OverReactBuilder extends Builder {
return null;
}

static FutureOr<void> _writePart(BuildStep buildStep, AssetId outputId, Iterable<String> outputs, {String? languageVersionComment}) async {
static FutureOr<void> _writePart(BuildStep buildStep, AssetId outputId, Iterable<String> outputs,
{required String nullSafetyCommentText, String? languageVersionComment}) async {
final partOf = "'${p.basename(buildStep.inputId.uri.toString())}'";

final buffer = StringBuffer();
Expand All @@ -214,8 +268,16 @@ class OverReactBuilder extends Builder {
..writeln('part of $partOf;')
..writeln()
..writeln(_headerLine)
..writeln('// OverReactBuilder (package:over_react/src/builder.dart)')
..writeln(_headerLine);
..writeln('// OverReactBuilder (package:over_react/src/builder.dart)');
// Omit this for now for filed from over_react so it doesn't show up as a diff in golds and
// checked in files in the conditional null safety generation PR.
// TODO re-enable after that merges.
if (outputId.package != 'over_react') {
buffer
..writeln('//')
..write(lineComment(nullSafetyCommentText));
}
buffer.writeln(_headerLine);

for (final item in outputs) {
buffer
Expand All @@ -233,3 +295,20 @@ class OverReactBuilder extends Builder {
await buildStep.writeAsString(outputId, output);
}
}

extension on BuildStep {
// Cache the result so we don't read and parse the package config for every file.
//
// This value should be safe to reuse globally within an isolate.
static pc.PackageConfig? _cachedPackageConfig;

/// Polyfill for build 2.4.0's BuildStep.packageConfig, which only seems to be resolvable in Dart 3.
/// From: https://github.com/dart-lang/build/issues/3492#issuecomment-1533455176
///
/// We could name this `packageConfig` and have it get shadowed by the real implementation from
/// `BuildStep` when 2.4.0 is resolved to, but that feels unnecessarily risky, and we can just
/// follow up later to remove this extension and use the real implementation instead.
Future<pc.PackageConfig> get $packageConfig async {
return _cachedPackageConfig ??= await pc.loadPackageConfigUri((await Isolate.packageConfig)!);
}
}
13 changes: 13 additions & 0 deletions lib/src/builder/codegen/language_version_util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:analyzer/dart/ast/token.dart' show LanguageVersionToken;
import 'package:package_config/package_config.dart' show LanguageVersion;

bool _languageVersionSupportsNullSafety(int major, int minor) =>
major > 2 || (major == 2 && minor >= 12);

extension LanguageVersionToken$NullSafety on LanguageVersionToken {
bool get supportsNullSafety => _languageVersionSupportsNullSafety(major, minor);
}

extension LanguageVersion$NullSafety on LanguageVersion {
bool get supportsNullSafety => _languageVersionSupportsNullSafety(major, minor);
}
3 changes: 3 additions & 0 deletions lib/src/builder/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,6 @@ MethodDeclaration? metaMethodOrNull(ClassishDeclaration node) {
return node.members.whereType<MethodDeclaration>().firstWhereOrNull((member) =>
member.name.name == 'meta');
}

String lineComment(String contents) =>
contents.split('\n').map((line) => '// $line\n').join('');
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
js: ^0.6.1+1
logging: '>=0.11.3+2 <2.0.0'
meta: ^1.6.0
package_config: ^2.1.0
path: ^1.5.1
react: ^7.0.0
redux: '>=5.0.0 <6.0.0'
Expand Down
Loading
Loading