feat(dart/analysis): Build DirectiveMetadata for LibrarySpecificUnit

initial commit for the dart analyzer task
This commit is contained in:
keertip 2015-05-04 09:20:27 -07:00
parent 0856516ae9
commit 0b1bb172c9
6 changed files with 647 additions and 3 deletions

View File

@ -1,12 +1,14 @@
library angular2.src.analysis.analyzer_plugin; library angular2.src.analysis.analyzer_plugin;
import 'package:analyzer/plugin/plugin.dart'; import 'package:analyzer/plugin/plugin.dart';
import 'package:analyzer/plugin/task.dart';
import 'src/tasks.dart';
/// Contribute a plugin to the dart analyzer for analysis of /// Contribute a plugin to the dart analyzer for analysis of
/// Angular 2 dart code. /// Angular 2 dart code.
class AngularAnalyzerPlugin implements Plugin { class AngularAnalyzerPlugin implements Plugin {
/// the unique indetifier for this plugin /// The unique identifier for this plugin.
static const String UNIQUE_IDENTIFIER = 'angular2.analysis'; static const String UNIQUE_IDENTIFIER = 'angular2.analysis';
@override @override
@ -17,6 +19,7 @@ class AngularAnalyzerPlugin implements Plugin {
@override @override
void registerExtensions(RegisterExtension registerExtension) { void registerExtensions(RegisterExtension registerExtension) {
// TODO(keerti): register extension for analysis String taskId = TASK_EXTENSION_POINT_ID;
registerExtension(taskId, BuildUnitDirectivesTask.DESCRIPTOR);
} }
} }

View File

@ -0,0 +1,112 @@
library angular2.src.analysis.analyzer_plugin.src.tasks;
import 'package:analyzer/src/generated/ast.dart' hide Directive;
import 'package:analyzer/src/generated/element.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/task/general.dart';
import 'package:analyzer/task/dart.dart';
import 'package:analyzer/task/model.dart';
import 'package:angular2/src/core/annotations/annotations.dart';
import 'package:angular2/src/render/api.dart';
/// The [DirectiveMetadata]s of a [LibrarySpecificUnit].
final ListResultDescriptor<DirectiveMetadata> DIRECTIVES =
new ListResultDescriptor<DirectiveMetadata>('ANGULAR2_DIRECTIVES', null);
/// A task that builds [DirectiveMetadata]s for directive classes.
class BuildUnitDirectivesTask extends SourceBasedAnalysisTask {
static const String UNIT_INPUT = 'UNIT_INPUT';
static final TaskDescriptor DESCRIPTOR = new TaskDescriptor(
'BuildUnitDirectivesTask', createTask, buildInputs,
<ResultDescriptor>[DIRECTIVES]);
BuildUnitDirectivesTask(AnalysisContext context, AnalysisTarget target)
: super(context, target);
@override
TaskDescriptor get descriptor => DESCRIPTOR;
@override
void internalPerform() {
CompilationUnit unit = getRequiredInput(UNIT_INPUT);
List<DirectiveMetadata> metaList = <DirectiveMetadata>[];
for (CompilationUnitMember unitMember in unit.declarations) {
if (unitMember is ClassDeclaration) {
for (Annotation annotationNode in unitMember.metadata) {
Directive directive = _createDirective(annotationNode);
if (directive != null) {
DirectiveMetadata meta = new DirectiveMetadata(
type: _getDirectiveType(directive),
selector: directive.selector);
metaList.add(meta);
}
}
}
}
outputs[DIRECTIVES] = metaList;
}
/// Returns an Angular [Directive] that corresponds to the given [node].
/// Returns `null` if not an Angular annotation.
Directive _createDirective(Annotation node) {
// TODO(scheglov) add support for all arguments
if (_isAngularAnnotation(node, 'Component')) {
String selector = _getNamedArgument(node, 'selector');
return new Component(selector: selector);
}
if (_isAngularAnnotation(node, 'Directive')) {
String selector = _getNamedArgument(node, 'selector');
return new Directive(selector: selector);
}
return null;
}
int _getDirectiveType(Directive directive) {
if (directive is Component) {
return DirectiveMetadata.COMPONENT_TYPE;
}
return DirectiveMetadata.DIRECTIVE_TYPE;
}
/// Returns the value of an argument with the given [name].
/// Returns `null` if not found or cannot be evaluated statically.
Object _getNamedArgument(Annotation node, String name) {
if (node.arguments != null) {
List<Expression> arguments = node.arguments.arguments;
for (Expression argument in arguments) {
if (argument is NamedExpression &&
argument.name != null &&
argument.name.label != null &&
argument.name.label.name == name) {
Expression expression = argument.expression;
if (expression is SimpleStringLiteral) {
return expression.value;
}
}
}
}
return null;
}
/// Returns `true` is the given [node] is resolved to a creation of an Angular
/// annotation class with the given [name].
bool _isAngularAnnotation(Annotation node, String name) {
if (node.element is ConstructorElement) {
ClassElement clazz = node.element.enclosingElement;
return clazz.library.name ==
'angular2.src.core.annotations.annotations' &&
clazz.name == name;
}
return null;
}
static Map<String, TaskInput> buildInputs(LibrarySpecificUnit target) {
return <String, TaskInput>{UNIT_INPUT: RESOLVED_UNIT.of(target)};
}
static BuildUnitDirectivesTask createTask(
AnalysisContext context, LibrarySpecificUnit target) {
return new BuildUnitDirectivesTask(context, target);
}
}

View File

@ -0,0 +1,9 @@
library angular2.src.analysis.analyzer_plugin.tasks;
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/task/model.dart';
/// The analysis errors associated with a target.
/// The value combines errors represented by multiple other results.
final CompositeResultDescriptor<List<AnalysisError>> HTML_ERRORS =
new CompositeResultDescriptor<List<AnalysisError>>('ANGULAR_HTML_ERRORS');

View File

@ -1,4 +1,4 @@
name: angular2_analysis_plugin name: angular2_analyzer_plugin
version: 0.0.0 version: 0.0.0
description: Dart analyzer plugin for Angular 2 description: Dart analyzer plugin for Angular 2
environment: environment:
@ -6,6 +6,10 @@ environment:
dependencies: dependencies:
angular2: '0.0.0' angular2: '0.0.0'
analyzer: '^0.24.4' analyzer: '^0.24.4'
dev_dependencies:
unittest: any
typed_mock: any
test_reflective_loader: any
dependency_overrides: dependency_overrides:
angular2: angular2:
path: ../../dist/dart/angular2 path: ../../dist/dart/angular2

View File

@ -0,0 +1,311 @@
library test.src.mock_sdk;
import 'package:analyzer/file_system/file_system.dart' as resource;
import 'package:analyzer/file_system/memory_file_system.dart' as resource;
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
class MockSdk implements DartSdk {
static const _MockSdkLibrary LIB_CORE = const _MockSdkLibrary('dart:core',
'/lib/core/core.dart', '''
library dart.core;
import 'dart:async';
class Object {
bool operator ==(other) => identical(this, other);
String toString() => 'a string';
int get hashCode => 0;
}
class Function {}
class StackTrace {}
class Symbol {}
class Type {}
abstract class Comparable<T> {
int compareTo(T other);
}
abstract class String implements Comparable<String> {
external factory String.fromCharCodes(Iterable<int> charCodes,
[int start = 0, int end]);
bool get isEmpty => false;
bool get isNotEmpty => false;
int get length => 0;
String toUpperCase();
List<int> get codeUnits;
}
class bool extends Object {}
abstract class num implements Comparable<num> {
bool operator <(num other);
bool operator <=(num other);
bool operator >(num other);
bool operator >=(num other);
num operator +(num other);
num operator -(num other);
num operator *(num other);
num operator /(num other);
int toInt();
num abs();
int round();
}
abstract class int extends num {
bool get isEven => false;
int operator -();
external static int parse(String source,
{ int radix,
int onError(String source) });
}
class double extends num {}
class DateTime extends Object {}
class Null extends Object {}
class Deprecated extends Object {
final String expires;
const Deprecated(this.expires);
}
const Object deprecated = const Deprecated("next release");
class Iterator<E> {
bool moveNext();
E get current;
}
abstract class Iterable<E> {
Iterator<E> get iterator;
bool get isEmpty;
}
abstract class List<E> implements Iterable<E> {
void add(E value);
E operator [](int index);
void operator []=(int index, E value);
Iterator<E> get iterator => null;
void clear();
}
abstract class Map<K, V> extends Object {
Iterable<K> get keys;
}
external bool identical(Object a, Object b);
void print(Object object) {}
class _Override {
const _Override();
}
const Object override = const _Override();
''');
static const _MockSdkLibrary LIB_ASYNC = const _MockSdkLibrary('dart:async',
'/lib/async/async.dart', '''
library dart.async;
import 'dart:math';
class Future<T> {
factory Future.delayed(Duration duration, [T computation()]) => null;
factory Future.value([value]) => null;
static Future wait(List<Future> futures) => null;
}
class Stream<T> {}
abstract class StreamTransformer<S, T> {}
''');
static const _MockSdkLibrary LIB_COLLECTION = const _MockSdkLibrary(
'dart:collection', '/lib/collection/collection.dart', '''
library dart.collection;
abstract class HashMap<K, V> implements Map<K, V> {}
''');
static const _MockSdkLibrary LIB_CONVERT = const _MockSdkLibrary(
'dart:convert', '/lib/convert/convert.dart', '''
library dart.convert;
import 'dart:async';
abstract class Converter<S, T> implements StreamTransformer {}
class JsonDecoder extends Converter<String, Object> {}
''');
static const _MockSdkLibrary LIB_MATH = const _MockSdkLibrary('dart:math',
'/lib/math/math.dart', '''
library dart.math;
const double E = 2.718281828459045;
const double PI = 3.1415926535897932;
const double LN10 = 2.302585092994046;
num min(num a, num b) => 0;
num max(num a, num b) => 0;
external double cos(num x);
external double sin(num x);
external double sqrt(num x);
class Random {
bool nextBool() => true;
double nextDouble() => 2.0;
int nextInt() => 1;
}
''');
static const _MockSdkLibrary LIB_HTML = const _MockSdkLibrary('dart:html',
'/lib/html/dartium/html_dartium.dart', '''
library dart.html;
class HtmlElement {}
''');
static const List<SdkLibrary> LIBRARIES = const [
LIB_CORE,
LIB_ASYNC,
LIB_COLLECTION,
LIB_CONVERT,
LIB_MATH,
LIB_HTML,
];
final resource.MemoryResourceProvider provider =
new resource.MemoryResourceProvider();
/**
* The [AnalysisContext] which is used for all of the sources.
*/
InternalAnalysisContext _analysisContext;
MockSdk() {
LIBRARIES.forEach((_MockSdkLibrary library) {
provider.newFile(library.path, library.content);
});
}
@override
AnalysisContext get context {
if (_analysisContext == null) {
_analysisContext = new SdkAnalysisContext();
SourceFactory factory = new SourceFactory([new DartUriResolver(this)]);
_analysisContext.sourceFactory = factory;
ChangeSet changeSet = new ChangeSet();
for (String uri in uris) {
Source source = factory.forUri(uri);
changeSet.addedSource(source);
}
_analysisContext.applyChanges(changeSet);
}
return _analysisContext;
}
@override
List<SdkLibrary> get sdkLibraries => LIBRARIES;
@override
String get sdkVersion => throw unimplemented;
UnimplementedError get unimplemented => new UnimplementedError();
@override
List<String> get uris {
List<String> uris = <String>[];
for (SdkLibrary library in LIBRARIES) {
uris.add(library.shortName);
}
return uris;
}
@override
Source fromFileUri(Uri uri) {
String filePath = uri.path;
String libPath = '/lib';
if (!filePath.startsWith("$libPath/")) {
return null;
}
for (SdkLibrary library in LIBRARIES) {
String libraryPath = library.path;
if (filePath.replaceAll('\\', '/') == libraryPath) {
try {
resource.File file = provider.getResource(uri.path);
Uri dartUri = Uri.parse(library.shortName);
return file.createSource(dartUri);
} catch (exception) {
return null;
}
}
if (filePath.startsWith("$libraryPath/")) {
String pathInLibrary = filePath.substring(libraryPath.length + 1);
String path = '${library.shortName}/${pathInLibrary}';
try {
resource.File file = provider.getResource(uri.path);
Uri dartUri = new Uri(scheme: 'dart', path: path);
return file.createSource(dartUri);
} catch (exception) {
return null;
}
}
}
return null;
}
@override
SdkLibrary getSdkLibrary(String dartUri) {
// getSdkLibrary() is only used to determine whether a library is internal
// to the SDK. The mock SDK doesn't have any internals, so it's safe to
// return null.
return null;
}
@override
Source mapDartUri(String dartUri) {
const Map<String, String> uriToPath = const {
"dart:core": "/lib/core/core.dart",
"dart:html": "/lib/html/dartium/html_dartium.dart",
"dart:async": "/lib/async/async.dart",
"dart:collection": "/lib/collection/collection.dart",
"dart:convert": "/lib/convert/convert.dart",
"dart:math": "/lib/math/math.dart"
};
String path = uriToPath[dartUri];
if (path != null) {
resource.File file = provider.getResource(path);
Uri uri = new Uri(scheme: 'dart', path: dartUri.substring(5));
return file.createSource(uri);
}
// If we reach here then we tried to use a dartUri that's not in the
// table above.
return null;
}
}
class _MockSdkLibrary implements SdkLibrary {
final String shortName;
final String path;
final String content;
const _MockSdkLibrary(this.shortName, this.path, this.content);
@override
String get category => throw unimplemented;
@override
bool get isDart2JsLibrary => throw unimplemented;
@override
bool get isDocumented => throw unimplemented;
@override
bool get isImplementation => throw unimplemented;
@override
bool get isInternal => throw unimplemented;
@override
bool get isShared => throw unimplemented;
@override
bool get isVmLibrary => throw unimplemented;
UnimplementedError get unimplemented => new UnimplementedError();
}

View File

@ -0,0 +1,205 @@
library angular2.src.analysis.analyzer_plugin.src.tasks_test;
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/context/cache.dart';
import 'package:analyzer/src/generated/engine.dart'
show AnalysisOptionsImpl, TimestampedData;
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/task/dart.dart';
import 'package:analyzer/src/task/driver.dart';
import 'package:analyzer/src/task/general.dart';
import 'package:analyzer/src/task/manager.dart';
import 'package:analyzer/task/dart.dart';
import 'package:analyzer/task/model.dart';
import 'package:angular2/src/render/api.dart';
import 'package:angular2_analyzer_plugin/src/tasks.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package:typed_mock/typed_mock.dart';
import 'package:unittest/unittest.dart';
import 'mock_sdk.dart';
main() {
groupSep = ' | ';
defineReflectiveTests(BuildUnitDirectivesTaskTest);
}
@reflectiveTest
class BuildUnitDirectivesTaskTest extends _AbstractDartTaskTest {
MemoryResourceProvider resourceProvider = new MemoryResourceProvider();
void test_Component() {
_addAngularSources();
Source source = _newSource('/test.dart', r'''
import '/angular2/annotations.dart';
@Component(selector: 'comp-a')
class ComponentA {
}
@Component(selector: 'comp-b')
class ComponentB {
}
''');
LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
_computeResult(target, DIRECTIVES);
expect(task, new isInstanceOf<BuildUnitDirectivesTask>());
// validate
List<DirectiveMetadata> directives = outputs[DIRECTIVES];
expect(directives, hasLength(2));
expect(directives[0].selector, 'comp-a');
expect(directives[1].selector, 'comp-b');
}
void test_Directive() {
_addAngularSources();
Source source = _newSource('/test.dart', r'''
import '/angular2/annotations.dart';
@Directive(selector: 'deco-a')
class ComponentA {
}
@Directive(selector: 'deco-b')
class ComponentB {
}
''');
LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
_computeResult(target, DIRECTIVES);
expect(task, new isInstanceOf<BuildUnitDirectivesTask>());
// validate
List<DirectiveMetadata> directives = outputs[DIRECTIVES];
expect(directives, hasLength(2));
expect(directives[0].selector, 'deco-a');
expect(directives[1].selector, 'deco-b');
}
void _addAngularSources() {
_newSource('/angular2/annotations.dart', r'''
library angular2.src.core.annotations.annotations;
abstract class Directive {
final String selector;
final dynamic properties;
final dynamic hostListeners;
final List lifecycle;
const Directive({selector, properties, hostListeners, lifecycle})
: selector = selector,
properties = properties,
hostListeners = hostListeners,
lifecycle = lifecycle,
super();
}
class Component extends Directive {
final String changeDetection;
final List injectables;
const Component({selector, properties, events, hostListeners,
injectables, lifecycle, changeDetection: 'DEFAULT'})
: changeDetection = changeDetection,
injectables = injectables,
super(
selector: selector,
properties: properties,
events: events,
hostListeners: hostListeners,
lifecycle: lifecycle);
}
''');
}
}
class _AbstractDartTaskTest {
MemoryResourceProvider resourceProvider = new MemoryResourceProvider();
Source emptySource;
DartSdk sdk = new MockSdk();
_MockContext context = new _MockContext();
Map<AnalysisTarget, CacheEntry> entryMap = <AnalysisTarget, CacheEntry>{};
TaskManager taskManager = new TaskManager();
AnalysisDriver analysisDriver;
AnalysisTask task;
Map<ResultDescriptor<dynamic>, dynamic> outputs;
CacheEntry getCacheEntry(AnalysisTarget target) {
return entryMap.putIfAbsent(target, () => new CacheEntry());
}
void setUp() {
emptySource = _newSource('/test.dart');
// prepare AnalysisContext
context.sourceFactory = new SourceFactory(<UriResolver>[
new DartUriResolver(sdk),
new ResourceUriResolver(resourceProvider)
]);
// prepare TaskManager
taskManager.addTaskDescriptor(GetContentTask.DESCRIPTOR);
// TODO(scheglov) extract into API
taskManager.addTaskDescriptor(ScanDartTask.DESCRIPTOR);
taskManager.addTaskDescriptor(ParseDartTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildClassConstructorsTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildCompilationUnitElementTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildLibraryConstructorsTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildLibraryElementTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildPublicNamespaceTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildDirectiveElementsTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildSourceClosuresTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildExportNamespaceTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildEnumMemberElementsTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildFunctionTypeAliasesTask.DESCRIPTOR);
taskManager.addTaskDescriptor(BuildTypeProviderTask.DESCRIPTOR);
taskManager.addTaskDescriptor(GatherUsedImportedElementsTask.DESCRIPTOR);
taskManager.addTaskDescriptor(GatherUsedLocalElementsTask.DESCRIPTOR);
taskManager.addTaskDescriptor(GenerateHintsTask.DESCRIPTOR);
taskManager.addTaskDescriptor(ResolveUnitTypeNamesTask.DESCRIPTOR);
taskManager.addTaskDescriptor(ResolveLibraryTypeNamesTask.DESCRIPTOR);
taskManager.addTaskDescriptor(ResolveReferencesTask.DESCRIPTOR);
taskManager.addTaskDescriptor(ResolveVariableReferencesTask.DESCRIPTOR);
taskManager.addTaskDescriptor(VerifyUnitTask.DESCRIPTOR);
// Angular specific tasks
taskManager.addTaskDescriptor(BuildUnitDirectivesTask.DESCRIPTOR);
// prepare AnalysisDriver
analysisDriver = new AnalysisDriver(taskManager, context);
}
void _computeResult(AnalysisTarget target, ResultDescriptor result) {
task = analysisDriver.computeResult(target, result);
expect(task.caughtException, isNull);
outputs = task.outputs;
}
Source _newSource(String path, [String content = '']) {
File file = resourceProvider.newFile(path, content);
return file.createSource();
}
}
class _MockContext extends TypedMock implements ExtendedAnalysisContext {
AnalysisOptionsImpl analysisOptions = new AnalysisOptionsImpl();
SourceFactory sourceFactory;
TypeProvider typeProvider;
Map<AnalysisTarget, CacheEntry> entryMap = <AnalysisTarget, CacheEntry>{};
String get name => '_MockContext';
bool exists(Source source) => source.exists();
@override
CacheEntry getCacheEntry(AnalysisTarget target) {
return entryMap.putIfAbsent(target, () => new CacheEntry());
}
TimestampedData<String> getContents(Source source) => source.contents;
noSuchMethod(Invocation invocation) {
print('noSuchMethod: ${invocation.memberName}');
return super.noSuchMethod(invocation);
}
}