Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
141 changes: 31 additions & 110 deletions lib/src/annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@
// 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.

library source_gen.annotation;

import 'dart:io';
import 'dart:mirrors';

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:path/path.dart' as p;

import 'constants.dart';
import 'type_checker.dart';

dynamic instantiateAnnotation(ElementAnnotation annotation) {
var annotationObject = annotation.constantValue;
try {
return _getValue(annotationObject, annotation.element.context.typeProvider);
return _getValue(annotation.constantValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK you always want to use computeConstantValue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just ran a little test – in all cases constantValue and computeConstantValue return the same result. 🤷‍♂️

} on CannotCreateFromAnnotationException catch (e) {
if (e.innerException != null) {
// If there was a issue creating a nested object, there's not much we
Expand Down Expand Up @@ -51,33 +49,35 @@ dynamic instantiateAnnotation(ElementAnnotation annotation) {
"${valueDeclaration.runtimeType}.");
}

dynamic _getValue(DartObject object, TypeProvider typeProvider) {
if (object.isNull) {
dynamic _getValue(DartObject object) {
var reader = new ConstantReader(object);

if (reader.isNull) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: You can use anyValue for this purpose if you'd like.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly no, we need to talk about that.

Type, List, and Map are weird – you get DartType.

I think we should update anyValue to exclude those and explain why.

return null;
}

if (object.type == typeProvider.boolType) {
return object.toBoolValue();
if (reader.isBool) {
return reader.boolValue;
}

if (object.type == typeProvider.intType) {
return object.toIntValue();
if (reader.isInt) {
return reader.intValue;
}

if (object.type == typeProvider.stringType) {
return object.toStringValue();
if (reader.isString) {
return reader.stringValue;
}

if (object.type == typeProvider.doubleType) {
return object.toDoubleValue();
if (reader.isDouble) {
return reader.doubleValue;
}

if (object.type == typeProvider.symbolType) {
return new Symbol(object.toSymbolValue());
if (reader.isSymbol) {
return reader.symbolValue;
}

if (object.type == typeProvider.typeType) {
var typeData = object.toTypeValue();
if (reader.isType) {
var typeData = reader.typeValue;

if (typeData is InterfaceType) {
var declarationMirror =
Expand All @@ -91,20 +91,17 @@ dynamic _getValue(DartObject object, TypeProvider typeProvider) {
}

try {
var listValue = object.toListValue();
if (listValue != null) {
return listValue
.map((DartObject element) => _getValue(element, typeProvider))
.toList();
}
if (reader.isList) {
var listValue = reader.listValue;

var mapValue = object.toMapValue();
if (mapValue != null) {
return listValue.map((DartObject element) => _getValue(element)).toList();
} else if (reader.isMap) {
var mapValue = reader.mapValue;
var result = {};
mapValue.forEach((DartObject key, DartObject value) {
dynamic mappedKey = _getValue(key, typeProvider);
dynamic mappedKey = _getValue(key);
if (mappedKey != null) {
result[mappedKey] = _getValue(value, typeProvider);
result[mappedKey] = _getValue(value);
}
});
return result;
Expand Down Expand Up @@ -169,13 +166,11 @@ dynamic _createFromConstructor(
fieldName = initializer.fieldName.name;
}

var typeProvider = ctor.context.typeProvider;

var fieldObjectImpl = obj.fields[fieldName];
if (p.parameterKind == ParameterKind.NAMED) {
namedArgs[new Symbol(p.name)] = _getValue(fieldObjectImpl, typeProvider);
namedArgs[new Symbol(p.name)] = _getValue(fieldObjectImpl);
} else {
positionalArgs.add(_getValue(fieldObjectImpl, typeProvider));
positionalArgs.add(_getValue(fieldObjectImpl));
}
}

Expand Down Expand Up @@ -225,81 +220,7 @@ bool matchAnnotation(Type annotationType, ElementAnnotation annotation) {
'Could not determine type of annotation. Are you missing a dependency?');
}

return matchTypes(annotationType, annotationValueType);
}

/// Checks whether [annotationValueType] is equivalent to [annotationType].
///
/// Currently, this uses mirrors to compare the name and library uri of the two
/// types.
bool matchTypes(Type annotationType, ParameterizedType annotationValueType) {
var classMirror = reflectClass(annotationType);
var classMirrorSymbol = classMirror.simpleName;

var annTypeName = annotationValueType.name;
var annotationTypeSymbol = new Symbol(annTypeName);

if (classMirrorSymbol != annotationTypeSymbol) {
return false;
}

var annotationLibSource = annotationValueType.element.library.source;

var libOwnerUri = (classMirror.owner as LibraryMirror).uri;
var annotationLibSourceUri = annotationLibSource.uri;

if (annotationLibSourceUri.scheme == 'file' &&
libOwnerUri.scheme == 'package') {
// try to turn the libOwnerUri into a file uri
libOwnerUri = _fileUriFromPackageUri(libOwnerUri);
} else if (annotationLibSourceUri.scheme == 'asset' &&
libOwnerUri.scheme == 'package') {
// try to turn the libOwnerUri into a asset uri
libOwnerUri = _assetUriFromPackageUri(libOwnerUri);
}

return annotationLibSource.uri == libOwnerUri;
}

Uri _fileUriFromPackageUri(Uri libraryPackageUri) {
assert(libraryPackageUri.scheme == 'package');

var fullLibraryPath = p.join(_packageRoot, libraryPackageUri.path);

var file = new File(fullLibraryPath);

assert(file.existsSync());

var normalPath = file.resolveSymbolicLinksSync();

return new Uri.file(normalPath);
}

Uri _assetUriFromPackageUri(Uri libraryPackageUri) {
assert(libraryPackageUri.scheme == 'package');
var originalSegments = libraryPackageUri.pathSegments;
var newSegments = [originalSegments[0]]
..add('lib')
..addAll(originalSegments.getRange(1, originalSegments.length));
var runtimeChecker = new TypeChecker.fromRuntime(annotationType);

return new Uri(scheme: 'asset', pathSegments: newSegments);
return runtimeChecker.isExactlyType(annotationValueType);
}

String get _packageRoot {
if (_packageRootCache == null) {
var dir = Platform.packageRoot;

if (dir.isEmpty) {
dir = p.join(p.current, 'packages');
}

// Handle the case where we're running via pub and dir is a file: URI
dir = p.prettyUri(dir);

assert(FileSystemEntity.isDirectorySync(dir));
_packageRootCache = dir;
}
return _packageRootCache;
}

String _packageRootCache;
79 changes: 76 additions & 3 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';

import 'revive.dart';
import 'type_checker.dart';
Expand Down Expand Up @@ -98,6 +99,36 @@ abstract class ConstantReader {
/// Throws [FormatException] if [isString] is `false`.
String get stringValue;

/// Returns whether this constant represents a `double` literal.
///
/// If `true`, [doubleValue] will return a `double` (not throw).
bool get isDouble;

/// Returns this constant as an `double` value.
///
/// Throws [FormatException] if [isDouble] is `false`.
double get doubleValue;

/// Returns whether this constant represents a `Symbol` literal.
///
/// If `true`, [symbolValue] will return a `Symbol` (not throw).
bool get isSymbol;

/// Returns this constant as an `Symbol` value.
///
/// Throws [FormatException] if [isSymbol] is `false`.
Symbol get symbolValue;

/// Returns whether this constant represents a `Type` literal.
///
/// If `true`, [typeValue] will return a `DartType` (not throw).
bool get isType;

/// Returns a [DartType] representing this as a `Type` value.
///
/// Throws [FormatException] if [isType] is `false`.
DartType get typeValue;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matanlurey – can't get the actual Type because runtime vs static.


/// Returns whether this constant represents `null`.
bool get isNull;

Expand All @@ -115,9 +146,8 @@ abstract class ConstantReader {
Revivable revive();
}

dynamic _throw(String expected, [dynamic object]) {
throw new FormatException('Not a $expected', '$object');
}
dynamic _throw(String expected, [dynamic object]) => throw new FormatException(
'Not an instance of $expected.', object == null ? null : '$object');

/// Implements a [ConstantReader] representing a `null` value.
class _NullConstant implements ConstantReader {
Expand Down Expand Up @@ -159,6 +189,24 @@ class _NullConstant implements ConstantReader {
@override
bool get isString => false;

@override
bool get isDouble => false;

@override
double get doubleValue => _throw("double");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh...geez...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


@override
bool get isSymbol => false;

@override
Symbol get symbolValue => _throw("Symbol");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


@override
get isType => false;

@override
DartType get typeValue => _throw("Type");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


@override
bool instanceOf(TypeChecker checker) => false;

Expand All @@ -180,6 +228,9 @@ class _Constant implements ConstantReader {
_object.toBoolValue() ??
_object.toIntValue() ??
_object.toStringValue() ??
_object.toDoubleValue() ??
(isSymbol ? this.symbolValue : null) ??
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Special case here to return an actual Symbol and not a String

_object.toTypeValue() ??
_object.toListValue() ??
_object.toMapValue();

Expand Down Expand Up @@ -220,6 +271,28 @@ class _Constant implements ConstantReader {
@override
bool get isString => _object.toStringValue() != null;

@override
bool get isDouble => _object.toDoubleValue() != null;

@override
double get doubleValue =>
isDouble ? _object.toDoubleValue() : _throw('double', _object);

@override
bool get isSymbol => _object.toSymbolValue() != null;

@override
Symbol get symbolValue => isSymbol
? new Symbol(_object.toSymbolValue())
: _throw('Symbol', _object);

@override
bool get isType => _object.toTypeValue() != null;

@override
DartType get typeValue =>
isType ? _object.toTypeValue() : _throw("Type", _object);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistent use of ' please.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done – enable a lint 😁


@override
bool instanceOf(TypeChecker checker) =>
checker.isAssignableFromType(_object.type);
Expand Down
3 changes: 2 additions & 1 deletion lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ String suggestLibraryName(AssetId source) {
/// Returns a URL representing [element].
String urlOfElement(Element element) => element.kind == ElementKind.DYNAMIC
? 'dart:core#dynmaic'
: normalizeUrl(element.source.uri)
// using librarySource.uri – in case the element is in a part
: normalizeUrl(element.librarySource.uri)
.replace(fragment: element.name)
.toString();

Expand Down
2 changes: 2 additions & 0 deletions test/annotation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ ElementAnnotation _mockElementAnnotation(String typeName, Uri libraryUri) {
when(value.type).thenReturn(type);
when(type.name).thenReturn(typeName);
when(type.element).thenReturn(element);
when(element.name).thenReturn(typeName);
when(element.library).thenReturn(library);
when(element.librarySource).thenReturn(source);
when(library.source).thenReturn(source);
when(source.uri).thenReturn(libraryUri);
return annotation;
Expand Down
24 changes: 24 additions & 0 deletions test/constants_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ void main() {
const aNull = null;
const aList = const [1, 2, 3];
const aMap = const {1: 'A', 2: 'B'};
const aDouble = 1.23;
const aSymbol = #shanna;
const aType = DateTime;

@aString // [0]
@aInt // [1]
Expand All @@ -38,6 +41,9 @@ void main() {
@aList // [6]
@aMap // [7]
@deprecated // [8]
@aDouble // [9]
@aSymbol // [10]
@aType // [11]
class Example {
final String aString;
final int aInt;
Expand Down Expand Up @@ -111,6 +117,24 @@ void main() {
{1: 'A', 2: 'B'});
});

test('should read a double', () {
expect(constants[9].isDouble, isTrue);
expect(constants[9].doubleValue, 1.23);
expect(constants[9].anyValue, 1.23);
});

test('should read a Symbol', () {
expect(constants[10].isSymbol, isTrue);
expect(constants[10].symbolValue, #shanna);
expect(constants[10].anyValue, #shanna);
});

test('should read a Type', () {
expect(constants[11].isType, isTrue);
expect(constants[11].typeValue.name, 'DateTime');
expect(constants[11].anyValue.toString(), 'DateTime');
});

test('should fail reading from `null`', () {
final $null = constants[3];
expect($null.isNull, isTrue, reason: '${$null}');
Expand Down