| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:_fe_analyzer_shared/src/metadata/expressions.dart' as shared; |
| import 'package:_fe_analyzer_shared/src/scanner/scanner.dart' show Token; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/clone.dart'; |
| |
| import '../base/extension_scope.dart'; |
| import '../base/loader.dart'; |
| import '../base/scope.dart' show LookupScope; |
| import '../kernel/body_builder_context.dart'; |
| import '../kernel/macro/metadata.dart' hide ExtensionScope; |
| import '../source/source_library_builder.dart' show SourceLibraryBuilder; |
| |
| bool computeSharedExpressionForTesting = false; |
| bool delaySharedExpressionLookupForTesting = false; |
| |
| class Annotation { |
| final MetadataBuilder metadataBuilder; |
| |
| final Token atToken; |
| final bool createFileUriExpression; |
| |
| Annotation( |
| this.metadataBuilder, |
| this.atToken, { |
| required this.createFileUriExpression, |
| }); |
| |
| late int annotationIndex; |
| late Expression expression; |
| } |
| |
| class MetadataBuilder { |
| /// Token for `@` for annotations that have not yet been parsed. |
| Token? _atToken; |
| |
| final int atOffset; |
| |
| /// `true` if the annotation begins with 'patch'. |
| /// |
| /// This is used for detecting `@patch` annotations in patch libraries where |
| /// it can be assumed that it implies that it _is_ a `@patch` annotation. |
| final bool hasPatch; |
| |
| /// Expression for an already parsed annotation. |
| Expression? _expression; |
| |
| final Uri fileUri; |
| |
| MetadataBuilder(Token this._atToken, this.fileUri) |
| : atOffset = _atToken.charOffset, |
| hasPatch = _atToken.next?.lexeme == 'patch'; |
| |
| // Coverage-ignore(suite): Not run. |
| Token? get beginToken => _atToken; |
| |
| shared.Expression? _sharedExpression; |
| |
| shared.Expression? _unresolvedSharedExpressionForTesting; |
| |
| // Coverage-ignore(suite): Not run. |
| shared.Expression? get expression => _sharedExpression; |
| |
| // Coverage-ignore(suite): Not run. |
| shared.Expression? get unresolvedExpressionForTesting => |
| _unresolvedSharedExpressionForTesting; |
| |
| static void buildAnnotations({ |
| required Annotatable annotatable, |
| required Uri annotatableFileUri, |
| required List<MetadataBuilder>? metadata, |
| required Uri annotationsFileUri, |
| required BodyBuilderContext bodyBuilderContext, |
| required SourceLibraryBuilder libraryBuilder, |
| required ExtensionScope extensionScope, |
| required LookupScope scope, |
| }) { |
| if (metadata == null) return; |
| |
| // Cloner used to clone already parsed annotations. |
| CloneVisitorNotMembers? cloner; |
| |
| List<Annotation> annotations = []; |
| for (int i = 0; i < metadata.length; ++i) { |
| MetadataBuilder annotationBuilder = metadata[i]; |
| bool createFileUriExpression = |
| annotatableFileUri != annotationBuilder.fileUri; |
| Token? beginToken = annotationBuilder._atToken; |
| annotationBuilder._atToken = null; |
| if (beginToken != null) { |
| if (computeSharedExpressionForTesting) { |
| // Coverage-ignore-block(suite): Not run. |
| annotationBuilder._sharedExpression = _parseSharedExpression( |
| libraryBuilder.loader, |
| beginToken, |
| libraryBuilder.importUri, |
| annotationBuilder.fileUri, |
| scope, |
| ); |
| if (delaySharedExpressionLookupForTesting) { |
| annotationBuilder._unresolvedSharedExpressionForTesting = |
| _parseSharedExpression( |
| libraryBuilder.loader, |
| beginToken, |
| libraryBuilder.importUri, |
| annotationBuilder.fileUri, |
| scope, |
| delayLookupForTesting: true, |
| ); |
| } |
| } |
| annotations.add( |
| new Annotation( |
| annotationBuilder, |
| beginToken, |
| createFileUriExpression: createFileUriExpression, |
| ), |
| ); |
| } else { |
| // The annotation is needed for multiple declarations so we need to |
| // clone the expression to use it more than once. For instance |
| // |
| // abstract class Class { |
| // @annotation |
| // abstract int field; |
| // } |
| // |
| // will be compiled to |
| // |
| // abstract class Class { |
| // @annotation |
| // int get field; |
| // @annotation |
| // void set field(int value); |
| // } |
| // |
| cloner ??= new CloneVisitorNotMembers(); |
| Expression annotation = cloner.cloneInContext( |
| annotationBuilder._expression!, |
| ); |
| // Coverage-ignore(suite): Not run. |
| if (createFileUriExpression && annotation is! FileUriExpression) { |
| annotation = new FileUriExpression( |
| annotation, |
| annotationBuilder.fileUri, |
| )..fileOffset = annotationBuilder.atOffset; |
| } |
| annotatable.addAnnotation(annotation); |
| } |
| } |
| libraryBuilder.loader.createResolver().buildAnnotations( |
| libraryBuilder: libraryBuilder, |
| bodyBuilderContext: bodyBuilderContext, |
| annotationsFileUri: annotationsFileUri, |
| extensionScope: extensionScope, |
| scope: scope, |
| annotatable: annotatable, |
| annotations: annotations, |
| ); |
| for (Annotation annotation in annotations) { |
| annotation.metadataBuilder._expression = annotation.expression; |
| } |
| } |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| shared.Expression _parseSharedExpression( |
| Loader loader, |
| Token atToken, |
| Uri importUri, |
| Uri fileUri, |
| LookupScope scope, { |
| bool delayLookupForTesting = false, |
| }) { |
| return parseAnnotation( |
| loader, |
| atToken, |
| importUri, |
| fileUri, |
| scope, |
| delayLookupForTesting: delayLookupForTesting, |
| ); |
| } |