|
| 1 | +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file |
| 2 | +// for details. All rights reserved. Use of this source code is governed by a |
| 3 | +// BSD-style license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +import 'package:dart_model/dart_model.dart'; |
| 6 | +// ignore: implementation_imports |
| 7 | +import 'package:dart_model/src/macro_metadata.g.dart'; |
| 8 | +import 'package:macro/macro.dart'; |
| 9 | +import 'package:macro_service/macro_service.dart'; |
| 10 | + |
| 11 | +import 'templating.dart'; |
| 12 | + |
| 13 | +/// Covers macro metadata cases where the params will always be written as |
| 14 | +/// literals in the annotation. |
| 15 | +/// |
| 16 | +/// Outputs comments with evaluation results. |
| 17 | +/// |
| 18 | +/// Throws if the annotation has something other than supported literals. |
| 19 | +/// TODO(davidmorgan): support diagnostics, make failures diagnostics. |
| 20 | +class LiteralParams { |
| 21 | + final int? anInt; |
| 22 | + final num? aNum; |
| 23 | + final double? aDouble; |
| 24 | + final String? aString; |
| 25 | + final Object? anObject; |
| 26 | + final List<int>? ints; |
| 27 | + final List<num>? nums; |
| 28 | + final List<double>? doubles; |
| 29 | + final List<String>? strings; |
| 30 | + final List<Object>? objects; |
| 31 | + |
| 32 | + const LiteralParams( |
| 33 | + {required this.anInt, |
| 34 | + this.aNum, |
| 35 | + this.aDouble, |
| 36 | + this.aString, |
| 37 | + this.anObject, |
| 38 | + this.ints, |
| 39 | + this.nums, |
| 40 | + this.doubles, |
| 41 | + this.strings, |
| 42 | + this.objects}); |
| 43 | +} |
| 44 | + |
| 45 | +class LiteralParamsImplementation implements ClassDeclarationsMacro { |
| 46 | + // TODO(davidmorgan): this should be injected by the bootstrap script. |
| 47 | + @override |
| 48 | + MacroDescription get description => MacroDescription( |
| 49 | + annotation: QualifiedName( |
| 50 | + uri: 'package:_test_macros/literal_params.dart', |
| 51 | + name: 'LiteralParams'), |
| 52 | + runsInPhases: [2]); |
| 53 | + |
| 54 | + @override |
| 55 | + Future<void> buildDeclarationsForClass( |
| 56 | + ClassDeclarationsBuilder builder) async { |
| 57 | + // TODO(davidmorgan): need a way to find the correct annotation, this just |
| 58 | + // uses the first. |
| 59 | + final annotation = builder |
| 60 | + .target.metadataAnnotations.first.expression.asConstructorInvocation; |
| 61 | + |
| 62 | + final namedArguments = { |
| 63 | + for (final argument in annotation.arguments) |
| 64 | + if (argument.type == ArgumentType.namedArgument) |
| 65 | + argument.asNamedArgument.name: |
| 66 | + argument.asNamedArgument.expression.evaluate |
| 67 | + }; |
| 68 | + |
| 69 | + builder.declareInType(Augmentation( |
| 70 | + code: expandTemplate([ |
| 71 | + for (final entry in namedArguments.entries) |
| 72 | + ' // ${entry.key}: ${entry.value}, ${entry.value.runtimeType}', |
| 73 | + ].join('\n')))); |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +// TODO(davidmorgan): common code for this in `dart_model` so macros don't |
| 78 | +// all have to write expression evaluation code. |
| 79 | +extension ExpressionExtension on Expression { |
| 80 | + Object get evaluate => switch (type) { |
| 81 | + ExpressionType.integerLiteral => int.parse(asIntegerLiteral.text), |
| 82 | + ExpressionType.doubleLiteral => double.parse(asDoubleLiteral.text), |
| 83 | + ExpressionType.stringLiteral => asStringLiteral.evaluate, |
| 84 | + ExpressionType.booleanLiteral => bool.parse(asBooleanLiteral.text), |
| 85 | + ExpressionType.listLiteral => |
| 86 | + asListLiteral.elements.map((e) => e.evaluate).toList(), |
| 87 | + // TODO(davidmorgan): need the type name to do something useful here, |
| 88 | + // for now just return the JSON. |
| 89 | + ExpressionType.constructorInvocation => |
| 90 | + asConstructorInvocation.toString(), |
| 91 | + // TODO(davidmorgan): need to follow references to do something useful |
| 92 | + // here, for now just return the JSON. |
| 93 | + ExpressionType.staticGet => asStaticGet.toString(), |
| 94 | + _ => throw UnsupportedError( |
| 95 | + 'Not supported in @LiteralParams annotation: $this'), |
| 96 | + }; |
| 97 | +} |
| 98 | + |
| 99 | +extension ElementExtension on Element { |
| 100 | + Object get evaluate => switch (type) { |
| 101 | + ElementType.expressionElement => |
| 102 | + asExpressionElement.expression.evaluate, |
| 103 | + _ => throw UnsupportedError( |
| 104 | + 'Not supported in @LiteralParams annotation: $this'), |
| 105 | + }; |
| 106 | +} |
| 107 | + |
| 108 | +extension StringLiteralExtension on StringLiteral { |
| 109 | + Object get evaluate { |
| 110 | + if (parts.length != 1) { |
| 111 | + throw UnsupportedError( |
| 112 | + 'Not supported in @LiteralParams annotation: $this'); |
| 113 | + } |
| 114 | + final part = parts.single; |
| 115 | + return switch (part.type) { |
| 116 | + StringLiteralPartType.stringPart => part.asStringPart.text, |
| 117 | + _ => throw UnsupportedError( |
| 118 | + 'Not supported in @LiteralParams annotation: $this'), |
| 119 | + }; |
| 120 | + } |
| 121 | +} |
0 commit comments