mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
Migration: add support for Angular's @Optional()
annotation.
Bug: https://github.com/dart-lang/sdk/issues/45661 Change-Id: I6c8c87c2a0d26dc8053ef66961a96e03cf5a2582 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209780 Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
ae0880a5c0
commit
4d0dc2ea8a
5 changed files with 114 additions and 0 deletions
|
@ -272,6 +272,7 @@ enum EdgeOriginKind {
|
|||
nonNullableUsage,
|
||||
nonNullAssertion,
|
||||
nullabilityComment,
|
||||
optionalAnnotation,
|
||||
optionalFormalParameter,
|
||||
parameterInheritance,
|
||||
quiverCheckNotNull,
|
||||
|
|
|
@ -542,6 +542,18 @@ class NullabilityCommentOrigin extends EdgeOrigin {
|
|||
EdgeOriginKind get kind => EdgeOriginKind.nullabilityComment;
|
||||
}
|
||||
|
||||
/// Edge origin resulting from the presence of an Angular `@Optional()`
|
||||
/// annotation.
|
||||
class OptionalAnnotationOrigin extends EdgeOrigin {
|
||||
OptionalAnnotationOrigin(Source? source, AstNode node) : super(source, node);
|
||||
|
||||
@override
|
||||
String get description => "annotated with Angular's @Optional() annotation";
|
||||
|
||||
@override
|
||||
EdgeOriginKind get kind => EdgeOriginKind.optionalAnnotation;
|
||||
}
|
||||
|
||||
/// Edge origin resulting from the presence of an optional formal parameter.
|
||||
///
|
||||
/// For example, in the following code snippet:
|
||||
|
|
|
@ -794,6 +794,15 @@ class NodeBuilder extends GeneralizingAstVisitor<DecoratedType>
|
|||
_handleNullabilityHint(node, decoratedType);
|
||||
}
|
||||
_variables!.recordDecoratedElementType(declaredElement, decoratedType);
|
||||
for (var annotation in node.metadata) {
|
||||
var element = annotation.element;
|
||||
if (element is ConstructorElement &&
|
||||
element.enclosingElement.name == 'Optional' &&
|
||||
_isAngularUri(element.librarySource.uri)) {
|
||||
_graph.makeNullable(
|
||||
decoratedType!.node!, OptionalAnnotationOrigin(source, node));
|
||||
}
|
||||
}
|
||||
if (declaredElement.isNamed) {
|
||||
_namedParameters![declaredElement.name] = decoratedType;
|
||||
} else {
|
||||
|
@ -884,6 +893,18 @@ class NodeBuilder extends GeneralizingAstVisitor<DecoratedType>
|
|||
.recordDecoratedDirectSupertypes(declaredElement, decoratedSupertypes);
|
||||
}
|
||||
|
||||
/// Determines whether the given [uri] comes from the Angular package.
|
||||
bool _isAngularUri(Uri uri) {
|
||||
if (uri.scheme != 'package') return false;
|
||||
var packageName = uri.pathSegments[0];
|
||||
if (packageName == 'angular') return true;
|
||||
if (packageName == 'third_party.dart_src.angular.angular') {
|
||||
// This name is used for angular development internally at Google.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
T _pushNullabilityNodeTarget<T>(
|
||||
NullabilityNodeTarget target, T Function() fn) {
|
||||
NullabilityNodeTarget? previousTarget = _target;
|
||||
|
|
|
@ -45,6 +45,27 @@ class AbstractContextTest with ResourceProviderMixin {
|
|||
|
||||
String get testsPath => '$homePath/tests';
|
||||
|
||||
/// Makes a mock version of the Angular package available for unit testing.
|
||||
///
|
||||
/// If optional argument [internalUris] is `true`, the mock Angular package
|
||||
/// will be located in a package called `third_party.dart_src.angular.angular`
|
||||
/// (as it is in Google3), and `package:angular` will simply re-export it;
|
||||
/// this allows the test to reflect usage in internal sources.
|
||||
void addAngularPackage({bool internalUris = false}) {
|
||||
addPackageFile(
|
||||
internalUris ? 'third_party.dart_src.angular.angular' : 'angular',
|
||||
'angular.dart', '''
|
||||
class Optional {
|
||||
const Optional();
|
||||
}
|
||||
''');
|
||||
if (internalUris) {
|
||||
addPackageFile('angular', 'angular.dart', '''
|
||||
export 'package:third_party.dart_src.angular.angular/angular.dart';
|
||||
''');
|
||||
}
|
||||
}
|
||||
|
||||
void addBuiltValuePackage() {
|
||||
addPackageFile('built_value', 'built_value.dart', '''
|
||||
abstract class Built<V extends Built<V, B>, B extends Builder<V, B>> {}
|
||||
|
|
|
@ -356,6 +356,65 @@ g() {
|
|||
await _checkSingleFileChanges(content, expected);
|
||||
}
|
||||
|
||||
Future<void> test_angular_optional_constructor_param() async {
|
||||
addAngularPackage();
|
||||
var content = '''
|
||||
import 'package:angular/angular.dart';
|
||||
|
||||
class MyComponent {
|
||||
MyComponent(@Optional() String foo);
|
||||
}
|
||||
''';
|
||||
var expected = '''
|
||||
import 'package:angular/angular.dart';
|
||||
|
||||
class MyComponent {
|
||||
MyComponent(@Optional() String? foo);
|
||||
}
|
||||
''';
|
||||
await _checkSingleFileChanges(content, expected);
|
||||
}
|
||||
|
||||
Future<void> test_angular_optional_constructor_param_field_formal() async {
|
||||
addAngularPackage();
|
||||
var content = '''
|
||||
import 'package:angular/angular.dart';
|
||||
|
||||
class MyComponent {
|
||||
String foo;
|
||||
MyComponent(@Optional() this.foo);
|
||||
}
|
||||
''';
|
||||
var expected = '''
|
||||
import 'package:angular/angular.dart';
|
||||
|
||||
class MyComponent {
|
||||
String? foo;
|
||||
MyComponent(@Optional() this.foo);
|
||||
}
|
||||
''';
|
||||
await _checkSingleFileChanges(content, expected);
|
||||
}
|
||||
|
||||
Future<void> test_angular_optional_constructor_param_internal() async {
|
||||
addAngularPackage(internalUris: true);
|
||||
var content = '''
|
||||
import 'package:angular/angular.dart';
|
||||
|
||||
class MyComponent {
|
||||
MyComponent(@Optional() String foo);
|
||||
}
|
||||
''';
|
||||
var expected = '''
|
||||
import 'package:angular/angular.dart';
|
||||
|
||||
class MyComponent {
|
||||
MyComponent(@Optional() String? foo);
|
||||
}
|
||||
''';
|
||||
await _checkSingleFileChanges(content, expected);
|
||||
}
|
||||
|
||||
Future<void> test_argumentError_checkNotNull_implies_non_null_intent() async {
|
||||
var content = '''
|
||||
void f(int i) {
|
||||
|
|
Loading…
Reference in a new issue