// Copyright (c) 2017, 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:analyzer/dart/constant/value.dart'; import 'package:build_test/build_test.dart'; import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; void main() { group('Constant', () { late List constants; setUpAll(() async { final library = await resolveSource(r''' library test_lib; const aString = 'Hello'; const aInt = 1234; const aBool = true; const aNull = null; const aList = [1, 2, 3]; const aMap = {1: 'A', 2: 'B'}; const aDouble = 1.23; const aSymbol = #shanna; const aType = DateTime; const aSet = {1}; @aString // [0] @aInt // [1] @aBool // [2] @aNull // [3] @Example( // [4] aString: aString, aInt: aInt, aBool: aBool, nested: const Example(), ) @Super() // [5] @aList // [6] @aMap // [7] @deprecated // [8] @aDouble // [9] @aSymbol // [10] @aType // [11] @aSet // [12] class Example { final String? aString; final int? aInt; final bool? aBool; final Example? nested; const Example({this.aString, this.aInt, this.aBool, this.nested}); } class Super extends Example { const Super() : super(aString: 'Super Hello'); } ''', (resolver) async => (await resolver.findLibraryByName('test_lib'))!); constants = library .getClass('Example')! .metadata .annotations .map((e) => ConstantReader(e.computeConstantValue()!)) .toList(); }); test('should read a String', () { expect(constants[0].isString, isTrue); expect(constants[0].stringValue, 'Hello'); expect(constants[0].isLiteral, isTrue); expect(constants[0].literalValue, 'Hello'); }); test('should read an Int', () { expect(constants[1].isInt, isTrue); expect(constants[1].intValue, 1234); expect(constants[1].isLiteral, isTrue); expect(constants[1].literalValue, 1234); }); test('should read a Bool', () { expect(constants[2].isBool, isTrue); expect(constants[2].boolValue, isTrue); expect(constants[2].isLiteral, isTrue); expect(constants[2].literalValue, isTrue); }); test('should read a Null', () { expect(constants[3].isNull, isTrue); expect(constants[3].isLiteral, isTrue); expect(constants[3].literalValue, isNull); }); test('should read an arbitrary object', () { final constant = constants[4]; expect(constant.isLiteral, isFalse); expect(() => constant.literalValue, throwsFormatException); expect(constant.read('aString').stringValue, 'Hello'); expect(constant.read('aInt').intValue, 1234); expect(constant.read('aBool').boolValue, true); final nested = constant.read('nested'); expect(nested.isNull, isFalse, reason: '$nested'); expect(nested.read('aString').isNull, isTrue, reason: '$nested'); expect(nested.read('aInt').isNull, isTrue); expect(nested.read('aBool').isNull, isTrue); }); test('should read from a super object', () { final constant = constants[5]; expect(constant.read('aString').stringValue, 'Super Hello'); }); test('should read a list', () { expect(constants[6].isList, isTrue, reason: '${constants[6]}'); expect(constants[6].isLiteral, isTrue); expect(constants[6].listValue.map((c) => ConstantReader(c).intValue), [ 1, 2, 3, ]); }); test('should read a map', () { expect(constants[7].isMap, isTrue, reason: '${constants[7]}'); expect(constants[7].isLiteral, isTrue); expect( constants[7].mapValue.map( (k, v) => MapEntry( ConstantReader(k!).intValue, ConstantReader(v!).stringValue, ), ), {1: 'A', 2: 'B'}, ); }); test('should read a double', () { expect(constants[9].isDouble, isTrue); expect(constants[9].doubleValue, 1.23); expect(constants[9].isLiteral, isTrue); expect(constants[9].literalValue, 1.23); }); test('should read a Symbol', () { expect(constants[10].isSymbol, isTrue); expect(constants[10].isLiteral, isTrue); expect(constants[10].symbolValue, #shanna); expect(constants[10].literalValue, #shanna); }); test('should read a Type', () { expect(constants[11].isType, isTrue); expect(constants[11].typeValue.element!.name, 'DateTime'); expect(constants[11].isLiteral, isFalse); expect(() => constants[11].literalValue, throwsFormatException); }); test('should read a Set', () { expect(constants[12].isSet, isTrue); expect(constants[12].setValue.map((c) => ConstantReader(c).intValue), { 1, }); expect(constants[12].isLiteral, isTrue); expect( (constants[12].literalValue as Set).map( (c) => ConstantReader(c).intValue, ), {1}, ); }); test('should give back the underlying value', () { final object = constants[11].objectValue; expect(object, isNotNull); expect(object.toTypeValue(), isNotNull); }); test('should fail reading from `null`', () { final $null = constants[3]; expect($null.isNull, isTrue, reason: '${$null}'); expect(() => $null.read('foo'), throwsUnsupportedError); }); test('should not fail reading from `null` when using peek', () { final $null = constants[3]; expect($null.isNull, isTrue, reason: '${$null}'); expect($null.peek('foo'), isNull); }); test('should fail reading a missing field', () { final $super = constants[5]; expect(() => $super.read('foo'), throwsFormatException); }); test('should compare using TypeChecker', () { final $deprecated = constants[8]; const check = TypeChecker.typeNamed( Deprecated, inPackage: 'core', inSdk: true, ); expect($deprecated.instanceOf(check), isTrue, reason: '$deprecated'); }); }); group('Revivable', () { late List constants; setUpAll(() async { final library = await resolveSource(r''' library test_lib; import 'dart:io'; @Int64Like.ZERO @Duration(seconds: 30) @Enum.field1 @MapLike() @VisibleClass.secret() @fieldOnly @ClassWithStaticField.staticField @Wrapper(someFunction) @Wrapper(Wrapper.someFunction) @_NotAccessible() @PublicWithPrivateConstructor._() @_privateField @Wrapper(_privateFunction) @ProcessStartMode.normal class Example {} class Int64Like implements Int64LikeBase{ static const Int64Like ZERO = const Int64LikeBase._bits(0, 0, 0); final int _l; final int _m; final int _h; const Int64Like._bits(this._l, this._m, this._h); } class Int64LikeBase { final int _l; final int _m; final int _h; const Int64LikeBase._bits(this._l, this._m, this._h); } enum Enum { field1, field2, } abstract class MapLike { const factory MapLike() = LinkedHashMapLike; } class LinkedHashMapLike implements MapLike { const LinkedHashMapLike(); } class VisibleClass { const factory VisbileClass.secret() = _HiddenClass; } class _HiddenClass implements VisibleClass { const _HiddenClass(); } class _FieldOnlyVisible { const _FieldOnlyVisible(); } const fieldOnly = const _FieldOnlyVisible(); class ClassWithStaticField { static const staticField = const ClassWithStaticField._(); const ClassWithStaticField._(); } class Wrapper { static void someFunction(int x, String y) {} final Function f; const Wrapper(this.f); } void someFunction(int x, String y) {} class _NotAccessible { const _NotAccessible(); } class PublicWithPrivateConstructor { const PublicWithPrivateConstructor._(); } void _privateFunction() {} ''', (resolver) async => (await resolver.findLibraryByName('test_lib'))!); constants = library .getClass('Example')! .metadata .annotations .map((e) => ConstantReader(e.computeConstantValue())) .toList(); }); test('should decode Int64Like.ZERO', () { final int64Like0 = constants[0].revive(); expect(int64Like0.source.fragment, isEmpty); expect(int64Like0.accessor, 'Int64Like.ZERO'); }); test('should decode Duration', () { final duration30s = constants[1].revive(); expect(duration30s.source.toString(), 'dart:core#Duration'); expect(duration30s.accessor, isEmpty); expect( duration30s.namedArguments.map( (k, v) => MapEntry(k, ConstantReader(v).literalValue), ), {'seconds': 30}, ); }); test('should decode enums', () { final enumField1 = constants[2].revive(); expect(enumField1.source.fragment, isEmpty); expect(enumField1.accessor, 'Enum.field1'); }); test('should decode forwarding factories', () { final mapLike = constants[3].revive(); expect(mapLike.source.toString(), endsWith('#MapLike')); expect(mapLike.accessor, isEmpty); }); test('should decode forwarding factories to hidden classes', () { final hiddenClass = constants[4].revive(); expect(hiddenClass.source.toString(), endsWith('#VisibleClass')); expect(hiddenClass.accessor, 'secret'); }); test('should decode top-level fields', () { final fieldOnly = constants[5].revive(); expect(fieldOnly.source.fragment, isEmpty); expect(fieldOnly.accessor, 'fieldOnly'); }); test('should decode static fields', () { final fieldOnly = constants[6].revive(); expect(fieldOnly.source.fragment, isEmpty); expect(fieldOnly.accessor, 'ClassWithStaticField.staticField'); }); test('should decode top-level functions', () { final fieldOnly = constants[7].read('f').revive(); expect(fieldOnly.source.fragment, isEmpty); expect(fieldOnly.accessor, 'someFunction'); }); test('should decode static-class functions', () { final fieldOnly = constants[8].read('f').revive(); expect(fieldOnly.source.fragment, isEmpty); expect(fieldOnly.accessor, 'Wrapper.someFunction'); }); test('should decode private classes', () { final notAccessible = constants[9].revive(); expect(notAccessible.isPrivate, isTrue); expect(notAccessible.source.fragment, '_NotAccessible'); }); test('should decode private constructors', () { final notAccessible = constants[10].revive(); expect(notAccessible.isPrivate, isTrue); expect(notAccessible.source.fragment, 'PublicWithPrivateConstructor'); expect(notAccessible.accessor, '_'); }); test('should decode private functions', () { final function = constants[12].read('f').revive(); expect(function.isPrivate, isTrue); expect(function.source.fragment, isEmpty); expect(function.accessor, '_privateFunction'); }); test( 'should decode public static fields backed by private constructors', () { final staticFieldWithPrivateImpl = constants[13].revive(); expect(staticFieldWithPrivateImpl.accessor, 'ProcessStartMode.normal'); expect(staticFieldWithPrivateImpl.isPrivate, isFalse); expect(staticFieldWithPrivateImpl.source.fragment, isEmpty); }, ); }); }