├── .test_config
├── analysis_options.yaml
├── test_package
├── pubspec.yaml
├── README.md
└── lib
│ ├── bar.dart
│ └── foo.dart
├── .travis.yml
├── codereview.settings
├── .gitignore
├── test
├── parts
│ ├── foo.dart
│ └── bar.dart
├── cycle_b.dart
├── cycle_a.dart
├── init_method_test.html
├── initializer_test.html
├── deferred_library_test.html
├── initializer_from_test.html
├── initializer_parts_test.html
├── initializer_super_test.html
├── foo.dart
├── initializer_custom_filter_test.html
├── initializer_cycle_error_test.html
├── initializer_type_filter_test.html
├── foo
│ └── bar.dart
├── initializer_cycle_error_test.dart
├── init_method_test.dart
├── initializer_super_test.dart
├── deferred_library_test.dart
├── initializer_parts_test.dart
├── initializer_from_test.dart
├── common.dart
├── initializer_type_filter_test.dart
├── initializer_test.dart
├── initializer_custom_filter_test.dart
└── transformer_test.dart
├── lib
├── src
│ ├── initialize_tracker.dart
│ ├── init_method.dart
│ ├── initializer.dart
│ ├── static_loader.dart
│ └── mirror_loader.dart
├── build
│ ├── loader_replacer.dart
│ └── initializer_plugin.dart
├── initialize.dart
└── transformer.dart
├── tool
├── rename_build_outputs.dart
└── all_tests.sh
├── pubspec.yaml
├── LICENSE
├── README.md
└── CHANGELOG.md
/.test_config:
--------------------------------------------------------------------------------
1 | {
2 | "test_package": true
3 | }
4 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | analyzer:
2 | strong-mode: true
3 |
--------------------------------------------------------------------------------
/test_package/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: test_package
2 | dependencies:
3 | initialize:
4 | path: ../
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: dart
2 |
3 | dart:
4 | - dev
5 | - stable
6 |
7 | script: ./tool/all_tests.sh
8 |
--------------------------------------------------------------------------------
/codereview.settings:
--------------------------------------------------------------------------------
1 | CODE_REVIEW_SERVER: http://codereview.chromium.org/
2 | VIEW_VC: https://github.com/dart-lang/initialize/commit/
3 | CC_LIST: reviews@dartlang.org
4 |
--------------------------------------------------------------------------------
/test_package/README.md:
--------------------------------------------------------------------------------
1 | Initialize tests package
2 | ========================
3 |
4 | Package used in the `initialize` packages tests. This is just a helper to make
5 | sure that it correctly normalizes urls in mirror mode.
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Pub files.
2 | /build/
3 | packages
4 | .pub
5 | pubspec.lock
6 | .packages
7 |
8 | # Files created by dart2js.
9 | *.dart.js
10 | *.dart.precompiled.js
11 | *.js_
12 | *.js.deps
13 | *.js.map
14 | *.sw?
15 |
16 | # Intellij files.
17 | .idea/
18 |
--------------------------------------------------------------------------------
/test_package/lib/bar.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | @initializeTracker
5 | library test_package.bar;
6 |
7 | import 'package:initialize/src/initialize_tracker.dart';
8 |
--------------------------------------------------------------------------------
/test/parts/foo.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | part of initialize.initializer_parts_test;
5 |
6 | @initializeTracker
7 | class Foo {}
8 |
9 | @initializeTracker
10 | foo() {}
11 |
--------------------------------------------------------------------------------
/test_package/lib/foo.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | @initializeTracker
5 | library test_package.foo;
6 |
7 | import 'bar.dart'; // Keep for the annotation
8 | import 'package:initialize/src/initialize_tracker.dart';
9 |
--------------------------------------------------------------------------------
/test/cycle_b.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.test.cycle_b;
5 |
6 | import 'package:initialize/src/initialize_tracker.dart';
7 | import 'cycle_a.dart';
8 |
9 | @initializeTracker
10 | class CycleB extends CycleA {}
11 |
--------------------------------------------------------------------------------
/test/cycle_a.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.test.cycle_a;
5 |
6 | import 'package:initialize/src/initialize_tracker.dart';
7 | import 'cycle_b.dart' as cycle_b;
8 |
9 | /// Uses [cycle_b].
10 | @initializeTracker
11 | class CycleA {}
12 |
--------------------------------------------------------------------------------
/test/parts/bar.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | part of initialize.initializer_parts_test;
5 |
6 | @initializeTracker
7 | class Bar2 {}
8 |
9 | @initializeTracker
10 | class Bar {}
11 |
12 | @initializeTracker
13 | bar2() {}
14 |
15 | @initializeTracker
16 | bar() {}
17 |
--------------------------------------------------------------------------------
/test/init_method_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/initializer_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/deferred_library_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/initializer_from_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/initializer_parts_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/initializer_super_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/foo.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | @initializeTracker
5 | library initialize.test.foo;
6 |
7 | import 'package:initialize/src/initialize_tracker.dart';
8 |
9 | @initializeTracker
10 | class Foo {}
11 |
12 | @initializeTracker
13 | fooBar() {}
14 |
15 | @initializeTracker
16 | foo() {}
17 |
--------------------------------------------------------------------------------
/test/initializer_custom_filter_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/initializer_cycle_error_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/initializer_type_filter_test.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/foo/bar.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | @initializeTracker
5 | library initialize.test.foo.bar;
6 |
7 | export '../foo.dart';
8 | import '../foo.dart';
9 | import 'package:initialize/src/initialize_tracker.dart';
10 |
11 | // Foo should be initialized first.
12 | @initializeTracker
13 | class Bar extends Foo {}
14 |
15 | @initializeTracker
16 | bar() {}
17 |
--------------------------------------------------------------------------------
/lib/src/initialize_tracker.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.test.initialize_tracker;
5 |
6 | import 'package:initialize/initialize.dart';
7 |
8 | // Initializer that just saves everything it sees.
9 | class InitializeTracker implements Initializer {
10 | static final List seen = [];
11 |
12 | const InitializeTracker();
13 |
14 | @override
15 | void initialize(value) => seen.add(value);
16 | }
17 |
18 | const initializeTracker = const InitializeTracker();
19 |
--------------------------------------------------------------------------------
/tool/rename_build_outputs.dart:
--------------------------------------------------------------------------------
1 | library initialize.tool.rename_build_outputs;
2 |
3 | import 'dart:io';
4 |
5 | import 'package:path/path.dart';
6 |
7 | main() {
8 | var scriptPath = Platform.script.path;
9 | if (context.style.name == 'windows') scriptPath = scriptPath.substring(1);
10 | var dir = join(dirname(dirname(scriptPath)), 'build', 'test');
11 | for (var file in new Directory(dir).listSync()) {
12 | var filepath = file.path;
13 | var name = basename(filepath);
14 | if (name.endsWith('.initialize.dart')) {
15 | var newPath = join(dirname(filepath),
16 | name.replaceFirst('.initialize.dart', '.initialize_test.dart'));
17 | print('Copying $filepath to $newPath');
18 | new File(filepath).copySync(newPath);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/src/init_method.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | part of initialize;
5 |
6 | /// Metadata used to label static or top-level methods that are called
7 | /// automatically when calling static_init.run(). This class is private because
8 | /// it shouldn't be used directly in annotations, instead use the `initMethod`
9 | /// singleton below.
10 | typedef dynamic _ZeroArg();
11 |
12 | class _InitMethod implements Initializer<_ZeroArg> {
13 | const _InitMethod();
14 |
15 | @override
16 | initialize(_ZeroArg method) => method();
17 | }
18 |
19 | /// We only ever need one instance of the `_InitMethod` class, this is it.
20 | const initMethod = const _InitMethod();
21 |
--------------------------------------------------------------------------------
/test/initializer_cycle_error_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | library initialize.initializer_cycle_error_test;
9 |
10 | import 'cycle_a.dart' as cycle_a; // Causes a cycle.
11 | import 'package:initialize/initialize.dart';
12 | import 'package:test/test.dart';
13 |
14 | /// Uses [cycle_a].
15 | main() {
16 | test('super class cycles are not supported', () {
17 | expect(run, throwsUnsupportedError);
18 | },
19 | skip: 'Should be skipped only in pub-serve mode, blocked on '
20 | 'https://github.com/dart-lang/test/issues/388.');
21 | }
22 |
--------------------------------------------------------------------------------
/lib/build/loader_replacer.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.build.loader_replacer;
5 |
6 | import 'dart:async';
7 | import 'package:barback/barback.dart';
8 |
9 | /// Removes `mirror_loader.dart` and replaces it with `static_loader.dart`.
10 | class LoaderReplacer extends Transformer {
11 | LoaderReplacer.asPlugin();
12 |
13 | bool isPrimary(AssetId id) =>
14 | id.package == 'initialize' && id.path == 'lib/initialize.dart';
15 |
16 | Future apply(Transform transform) {
17 | var id = transform.primaryInput.id;
18 | return transform.primaryInput.readAsString().then((code) {
19 | transform.addOutput(new Asset.fromString(
20 | id, code.replaceFirst('mirror_loader.dart', 'static_loader.dart')));
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/init_method_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | library initialize.init_method_test;
9 |
10 | import 'package:initialize/initialize.dart';
11 | import 'package:test/test.dart';
12 |
13 | int calledFoo = 0;
14 | int calledBar = 0;
15 |
16 | main() {
17 | // Run all initializers.
18 | return run().then((_) {
19 | test('initMethod annotation invokes functions once', () {
20 | expect(calledFoo, 1);
21 | expect(calledBar, 1);
22 | // Re-run all initializers, should be a no-op.
23 | return run().then((_) {
24 | expect(calledFoo, 1);
25 | expect(calledBar, 1);
26 | });
27 | });
28 | });
29 | }
30 |
31 | @initMethod
32 | foo() => calledFoo++;
33 |
34 | @initMethod
35 | bar() => calledBar++;
36 |
--------------------------------------------------------------------------------
/test/initializer_super_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | library initialize.initializer_super_test;
9 |
10 | import 'package:initialize/src/initialize_tracker.dart';
11 | import 'package:initialize/initialize.dart';
12 | import 'package:test/test.dart';
13 |
14 | main() {
15 | // Run all initializers.
16 | return run().then((_) {
17 | test('annotations are seen in post-order with superclasses first', () {
18 | var expectedNames = [
19 | A,
20 | C,
21 | B,
22 | E,
23 | D,
24 | ];
25 | expect(InitializeTracker.seen, expectedNames);
26 | });
27 | });
28 | }
29 |
30 | @initializeTracker
31 | class D extends E {}
32 |
33 | @initializeTracker
34 | class E extends B {}
35 |
36 | @initializeTracker
37 | class B extends C {}
38 |
39 | @initializeTracker
40 | class C extends A {}
41 |
42 | @initializeTracker
43 | class A {}
44 |
--------------------------------------------------------------------------------
/test/deferred_library_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | @initializeTracker
9 | library initialize.deferred_library_test;
10 |
11 | import 'foo.dart' deferred as foo;
12 | import 'package:initialize/src/initialize_tracker.dart';
13 | import 'package:initialize/initialize.dart';
14 | import 'package:test/test.dart';
15 |
16 | main() {
17 | test('annotations can be loaded lazily', () {
18 | // Initialize everything not in deferred imports.
19 | return run().then((_) {
20 | expect(InitializeTracker.seen.length, 1);
21 |
22 | // Now load the foo library and re-run initializers.
23 | return foo.loadLibrary().then((_) => run()).then((_) {
24 | expect(InitializeTracker.seen.length, 5);
25 | });
26 | });
27 | },
28 | skip: 'Should be skipped only in pub-serve mode, blocked on '
29 | 'https://github.com/dart-lang/test/issues/388.');
30 | }
31 |
--------------------------------------------------------------------------------
/test/initializer_parts_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | @initializeTracker
9 | library initialize.initializer_parts_test;
10 |
11 | import 'package:initialize/src/initialize_tracker.dart';
12 | import 'package:initialize/initialize.dart';
13 | import 'package:test/test.dart';
14 |
15 | part 'parts/foo.dart';
16 | part 'parts/bar.dart';
17 |
18 | main() {
19 | // Run all initializers.
20 | return run().then((_) {
21 | test('parts', () {
22 | var expectedNames = [
23 | const LibraryIdentifier(#initialize.initializer_parts_test, null,
24 | 'initializer_parts_test.dart'),
25 | bar2,
26 | bar,
27 | foo,
28 | baz,
29 | Bar2,
30 | Bar,
31 | Foo,
32 | Baz,
33 | ];
34 | expect(InitializeTracker.seen, expectedNames);
35 | });
36 | });
37 | }
38 |
39 | @initializeTracker
40 | class Baz {}
41 |
42 | @initializeTracker
43 | baz() {}
44 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: initialize
2 | version: 0.6.2+7
3 | author: Polymer.dart Authors
4 | description: Generic building blocks for doing static initialization.
5 | homepage: https://github.com/dart-lang/initialize
6 | dependencies:
7 | analyzer: '>=0.27.2 <0.30.0'
8 | barback: '>=0.14.2 <0.16.0'
9 | code_transformers: '>=0.3.0 <0.6.0'
10 | dart_style: '>=0.1.3 <0.3.0'
11 | glob: ">=1.0.4 <2.0.0"
12 | html: '>=0.12.0 <0.14.0'
13 | path: '>=1.3.0 <2.0.0'
14 | dev_dependencies:
15 | test_package:
16 | path: test_package
17 | test: '>=0.12.0 <0.13.0'
18 | transformer_test: '>=0.2.0 <0.3.0'
19 | environment:
20 | sdk: ">=1.9.0-dev.7.1 <2.0.0"
21 | transformers:
22 | - initialize/build/loader_replacer:
23 | $include: lib/initialize.dart
24 | - initialize:
25 | $include: '**/*_test.*'
26 | entry_points:
27 | - test/deferred_library_test.html
28 | - test/initializer_test.html
29 | - test/initializer_from_test.html
30 | - test/initializer_parts_test.html
31 | - test/initializer_super_test.html
32 | - test/initializer_cycle_error_test.html
33 | - test/initializer_custom_filter_test.html
34 | - test/initializer_type_filter_test.html
35 | - test/init_method_test.html
36 | - test/pub_serve:
37 | $include: test/**_test{.*,}.dart
38 | - $dart2js:
39 | $include: test/*_test.initialize{.*,}.dart
40 |
--------------------------------------------------------------------------------
/tool/all_tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
4 | # for details. All rights reserved. Use of this source code is governed by a
5 | # BSD-style license that can be found in the LICENSE file.
6 |
7 | # Fast fail the script on failures.
8 | set -e
9 |
10 | dartanalyzer --fatal-warnings lib/initialize.dart lib/transformer.dart
11 |
12 | # Run the un-transformed command-line tests.
13 | dart test/deferred_library_test.dart
14 | dart test/init_method_test.dart
15 | dart test/initializer_custom_filter_test.dart
16 | dart test/initializer_cycle_error_test.dart
17 | dart test/initializer_from_test.dart
18 | dart test/initializer_parts_test.dart
19 | dart test/initializer_super_test.dart
20 | dart test/initializer_test.dart
21 | dart test/initializer_type_filter_test.dart
22 | dart test/transformer_test.dart
23 |
24 | pub build test --mode=debug
25 |
26 | # Run the transformed command-line tests.
27 | # TODO(jakemac): Add back once initialize supports deferred libraries.
28 | # dart test/deferred_library_test.dart
29 | dart build/test/init_method_test.initialize.dart
30 | dart build/test/initializer_custom_filter_test.initialize.dart
31 | dart build/test/initializer_test.initialize.dart
32 | dart build/test/initializer_parts_test.initialize.dart
33 | dart build/test/initializer_super_test.initialize.dart
34 | dart build/test/initializer_type_filter_test.initialize.dart
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015, the Dart project authors. All rights reserved.
2 | Redistribution and use in source and binary forms, with or without
3 | modification, are permitted provided that the following conditions are
4 | met:
5 |
6 | * Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above
9 | copyright notice, this list of conditions and the following
10 | disclaimer in the documentation and/or other materials provided
11 | with the distribution.
12 | * Neither the name of Google Inc. nor the names of its
13 | contributors may be used to endorse or promote products derived
14 | from this software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/lib/src/initializer.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | part of initialize;
5 |
6 | /// Implement this class to create your own initializer.
7 | ///
8 | /// Hello world example:
9 | ///
10 | /// class Print implements Initializer {
11 | /// final String message;
12 | /// const Print(this.message);
13 | ///
14 | /// @override
15 | /// initialize(Type t) => print('$t says `$message`');
16 | /// }
17 | ///
18 | /// @Print('hello world!')
19 | /// class Foo {}
20 | ///
21 | /// Call [run] from your main and this will print 'Foo says `hello world!`'
22 | ///
23 | abstract class Initializer {
24 | dynamic initialize(T target);
25 | }
26 |
27 | /// Typedef for a custom filter function.
28 | typedef bool InitializerFilter(Initializer initializer);
29 |
30 | /// When annotating libraries, this is passed to the initializer.
31 | class LibraryIdentifier {
32 | // The qualified name of the library.
33 | final Symbol name;
34 |
35 | // The package this library lives in. May be null if its the same as the root
36 | // package.
37 | final String package;
38 |
39 | // The path to the library.
40 | final String path;
41 |
42 | const LibraryIdentifier(this.name, this.package, this.path);
43 |
44 | bool operator ==(dynamic other) =>
45 | other is LibraryIdentifier &&
46 | name == other.name &&
47 | package == other.package &&
48 | path == other.path;
49 |
50 | String toString() => '$name: $package:$path';
51 | }
52 |
--------------------------------------------------------------------------------
/test/initializer_from_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | @initializeTracker
9 | library initialize.test.initializer_from_test;
10 |
11 | import 'package:initialize/src/initialize_tracker.dart';
12 | import 'package:initialize/initialize.dart';
13 | import 'package:test/test.dart';
14 | import 'package:test_package/bar.dart' as bar;
15 |
16 | /// Uses [bar]
17 | main() {
18 | test('The `from` option', () async {
19 | final expectedNames = [];
20 |
21 | // First just run on the test packages bar.dart file.
22 | await run(from: Uri.parse('package:test_package/bar.dart'));
23 | expectedNames.add(
24 | const LibraryIdentifier(#test_package.bar, 'test_package', 'bar.dart'));
25 | expect(InitializeTracker.seen, expectedNames);
26 |
27 | // Now we run on the rest (just this file).
28 | await run();
29 | expect(InitializeTracker.seen.length, 2);
30 | // Don't know what the path will be, so have to explicitly check fields
31 | // and use an [endsWith] matcher for the path.
32 | expect(
33 | InitializeTracker.seen[1].name, #initialize.test.initializer_from_test);
34 | expect(InitializeTracker.seen[1].package, isNull);
35 | expect(
36 | InitializeTracker.seen[1].path, endsWith('initializer_from_test.dart'));
37 | },
38 | skip: 'Should be skipped only in pub-serve mode, blocked on '
39 | 'https://github.com/dart-lang/test/issues/388.');
40 | }
41 |
--------------------------------------------------------------------------------
/lib/initialize.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize;
5 |
6 | // The `loader_replacer` transformer will replace this with a static_loader.
7 | import 'src/mirror_loader.dart' as loader;
8 | import 'dart:async';
9 | import 'dart:collection';
10 |
11 | part 'src/init_method.dart';
12 | part 'src/initializer.dart';
13 |
14 | /// Top level function which crawls the dependency graph and runs initializers.
15 | /// If [typeFilter] and/or [customFilter] are supplied then only those types of
16 | /// annotations will be parsed. If both filters are supplied they are treated
17 | /// as an AND.
18 | ///
19 | /// If [from] is supplied then initializers will be found starting from the
20 | /// library at the supplied uri.
21 | ///
22 | /// **Warning**: Do not use [from] directly in your code unless you are building
23 | /// a framework that will use a transformer to remove this argument later. This
24 | /// parameter is supported in Dartium, but [run] will throw if you use the
25 | /// argument after building an application with `pub build` or `pub serve`.
26 | Future run({List typeFilter, InitializerFilter customFilter, Uri from}) {
27 | return _runInitQueue(loader.loadInitializers(
28 | typeFilter: typeFilter, customFilter: customFilter, from: from));
29 | }
30 |
31 | Future _runInitQueue(Queue initializers) {
32 | if (initializers.isEmpty) return new Future.value(null);
33 |
34 | var initializer = initializers.removeFirst();
35 | var val = initializer();
36 | if (val is! Future) val = new Future.value(val);
37 |
38 | return val.then((_) => _runInitQueue(initializers));
39 | }
40 |
--------------------------------------------------------------------------------
/test/common.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2013, 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 | library initialize.test.build.common;
5 |
6 | import 'package:barback/barback.dart';
7 | import 'package:transformer_test/src/test_harness.dart';
8 | import 'package:test/test.dart';
9 |
10 | testPhases(String testName, List> phases,
11 | Map inputFiles, Map expectedFiles,
12 | [List expectedMessages]) {
13 | test(testName, () {
14 | var helper = new TestHelper(phases, inputFiles, expectedMessages)..run();
15 | return helper.checkAll(expectedFiles).whenComplete(() => helper.tearDown());
16 | });
17 | }
18 |
19 | // Simple mock of initialize.
20 | const mockInitialize = '''
21 | library initialize;
22 |
23 | abstract class Initializer {}
24 |
25 | class _InitMethod implements Initializer {
26 | const _InitMethod();
27 | }
28 | const _InitMethod initMethod = const _InitMethod();''';
29 |
30 | // Some simple initializers for use in tests.
31 | const commonInitializers = '''
32 | library test_initializers;
33 |
34 | import 'package:initialize/initialize.dart';
35 |
36 | class _ConstInit extends Initializer {
37 | const ConstInit();
38 | }
39 | const _ConstInit constInit = const _ConstInit();
40 |
41 | class DynamicInit extends Initializer {
42 | final dynamic _value;
43 | const DynamicInit(this._value);
44 | }
45 |
46 | class NamedArgInit extends Initializer {
47 | final dynamic _first;
48 | final dynamic name;
49 | const NamedArgInit(this._first, {this.name});
50 | }
51 | ''';
52 |
--------------------------------------------------------------------------------
/lib/src/static_loader.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.static_loader;
5 |
6 | import 'dart:collection' show Queue;
7 | import 'package:initialize/initialize.dart';
8 |
9 | /// This represents an annotation/declaration pair.
10 | class InitEntry {
11 | /// The annotation instance.
12 | final Initializer meta;
13 |
14 | /// The target of the annotation to pass to initialize.
15 | final T target;
16 |
17 | InitEntry(this.meta, this.target);
18 | }
19 |
20 | /// Set of initializers that are invoked by `run`. This is initialized with
21 | /// code automatically generated by the transformer.
22 | Queue initializers = new Queue();
23 |
24 | /// Returns initializer functions matching the supplied filters and removes them
25 | /// from `initializers` so they won't be ran again.
26 | Queue loadInitializers(
27 | {List typeFilter, InitializerFilter customFilter, Uri from}) {
28 | if (from != null) {
29 | throw 'The `from` option is not supported in deploy mode.';
30 | }
31 | Queue result = new Queue();
32 |
33 | var matchesFilters = (initializer) {
34 | if (typeFilter != null &&
35 | !typeFilter.any((t) => initializer.meta.runtimeType == t)) {
36 | return false;
37 | }
38 | if (customFilter != null && !customFilter(initializer.meta)) {
39 | return false;
40 | }
41 | return true;
42 | };
43 |
44 | result.addAll(initializers
45 | .where(matchesFilters)
46 | .map((i) => () => i.meta.initialize(i.target)));
47 |
48 | initializers.removeWhere(matchesFilters);
49 |
50 | return result;
51 | }
52 |
--------------------------------------------------------------------------------
/test/initializer_type_filter_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | library initialize.initializer_type_filter_test;
9 |
10 | import 'package:initialize/initialize.dart';
11 | import 'package:test/test.dart';
12 |
13 | // Initializers will mess with this value, and it gets reset to 0 at the
14 | // start of every test.
15 | var total;
16 |
17 | main() {
18 | setUp(() {
19 | total = 0;
20 | });
21 |
22 | test('filter option limits which types of annotations will be ran', () {
23 | return run(typeFilter: const [_Adder])
24 | .then((_) {
25 | expect(total, 2);
26 | })
27 | .then((_) => run(typeFilter: const [_Subtractor]))
28 | .then((_) {
29 | expect(total, 0);
30 | })
31 | .then((_) => run(typeFilter: const [_Adder]))
32 | .then((_) {
33 | // Sanity check, future calls should be no-ops
34 | expect(total, 0);
35 | })
36 | .then((_) => run(typeFilter: const [_Subtractor]))
37 | .then((_) {
38 | expect(total, 0);
39 | });
40 | });
41 | }
42 |
43 | @adder
44 | a() {}
45 | @subtractor
46 | b() {}
47 | @adder
48 | @subtractor
49 | c() {}
50 |
51 | // Initializer that increments `total` by one.
52 | class _Adder implements Initializer {
53 | const _Adder();
54 |
55 | @override
56 | initialize(_) => total++;
57 | }
58 |
59 | const adder = const _Adder();
60 |
61 | // Initializer that decrements `total` by one.
62 | class _Subtractor implements Initializer {
63 | const _Subtractor();
64 |
65 | @override
66 | initialize(_) => total--;
67 | }
68 |
69 | const subtractor = const _Subtractor();
70 |
--------------------------------------------------------------------------------
/test/initializer_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | @initializeTracker
9 | library initialize.initializer_test;
10 |
11 | import 'foo/bar.dart';
12 | import 'package:initialize/src/initialize_tracker.dart';
13 | import 'package:initialize/initialize.dart';
14 | import 'package:test_package/foo.dart' as test_foo;
15 | import 'package:test/test.dart';
16 |
17 | /// Uses [test_foo].
18 | main() {
19 | // Run all initializers.
20 | return run().then((_) {
21 | test('annotations are seen in post-order with superclasses first', () {
22 | var expectedNames = [
23 | const LibraryIdentifier(#initialize.test.foo, null, 'foo.dart'),
24 | fooBar,
25 | foo,
26 | Foo,
27 | const LibraryIdentifier(#initialize.test.foo.bar, null, 'foo/bar.dart'),
28 | bar,
29 | Bar,
30 | const LibraryIdentifier(#test_package.bar, 'test_package', 'bar.dart'),
31 | const LibraryIdentifier(#test_package.foo, 'test_package', 'foo.dart'),
32 | const LibraryIdentifier(
33 | #initialize.initializer_test, null, 'initializer_test.dart'),
34 | zap,
35 | Zoop, // Zap extends Zoop, so Zoop comes first.
36 | Zap,
37 | ];
38 | expect(InitializeTracker.seen, expectedNames);
39 | });
40 |
41 | test('annotations only run once', () {
42 | // Run the initializers again, should be a no-op.
43 | var originalSize = InitializeTracker.seen.length;
44 | return run().then((_) {
45 | expect(InitializeTracker.seen.length, originalSize);
46 | });
47 | });
48 | });
49 | }
50 |
51 | @initializeTracker
52 | class Zoop {}
53 |
54 | @initializeTracker
55 | class Zap extends Zoop {}
56 |
57 | @initializeTracker
58 | zap() {}
59 |
--------------------------------------------------------------------------------
/test/initializer_custom_filter_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | // TODO(jakemac): swap this to @TestOn('pub-serve') once
6 | // https://github.com/dart-lang/test/issues/388 is completed.
7 | @TestOn('!js')
8 | library initialize.initializer_custom_filter_test;
9 |
10 | import 'dart:async';
11 | import 'package:initialize/initialize.dart';
12 | import 'package:test/test.dart';
13 | import 'package:initialize/src/initialize_tracker.dart';
14 |
15 | main() {
16 | test('filter option limits which types of annotations will be ran', () {
17 | var originalSize;
18 | return runPhase(1)
19 | .then((_) {
20 | // Even though Baz extends Bar, only Baz should be run.
21 | expect(InitializeTracker.seen, [Baz]);
22 | })
23 | .then((_) => runPhase(2))
24 | .then((_) {
25 | expect(InitializeTracker.seen, [Baz, foo]);
26 | })
27 | .then((_) => runPhase(3))
28 | .then((_) {
29 | expect(InitializeTracker.seen, [Baz, foo, Foo]);
30 | })
31 | .then((_) => runPhase(4))
32 | .then((_) {
33 | expect(InitializeTracker.seen, [Baz, foo, Foo, Bar]);
34 | })
35 | .then((_) {
36 | originalSize = InitializeTracker.seen.length;
37 | })
38 | .then((_) => runPhase(1))
39 | .then((_) => runPhase(2))
40 | .then((_) => runPhase(3))
41 | .then((_) => runPhase(4))
42 | .then((_) => run())
43 | .then((_) {
44 | expect(InitializeTracker.seen.length, originalSize);
45 | });
46 | });
47 | }
48 |
49 | Future runPhase(int phase) => run(
50 | customFilter: (Initializer meta) =>
51 | meta is PhasedInitializer && meta.phase == phase);
52 |
53 | @PhasedInitializer(3)
54 | class Foo {}
55 |
56 | @PhasedInitializer(2)
57 | foo() {}
58 |
59 | @PhasedInitializer(4)
60 | class Bar {}
61 |
62 | @PhasedInitializer(1)
63 | class Baz extends Bar {}
64 |
65 | // Initializer that has a phase associated with it, this can be used in
66 | // combination with a custom filter to run intialization in phases.
67 | class PhasedInitializer extends InitializeTracker {
68 | final int phase;
69 |
70 | const PhasedInitializer(this.phase);
71 | }
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Initialize [](https://travis-ci.org/dart-lang/initialize)
2 |
3 | This package provides a common interface for initialization annotations on top
4 | level methods, classes, and libraries. The interface looks like this:
5 |
6 | ```dart
7 | abstract class Initializer {
8 | dynamic initialize(T target);
9 | }
10 | ```
11 |
12 | The `initialize` method will be called once for each annotation. The type `T` is
13 | determined by what was annotated. For libraries it will be a `LibraryIdentifier`
14 | representing that library, for a class it will be the `Type` representing that
15 | class, and for a top level method it will be the `Function` object representing
16 | that method.
17 |
18 | If a future is returned from the initialize method, it will wait until the future
19 | completes before running the next initializer.
20 |
21 | ## Usage
22 |
23 | ### @initMethod
24 |
25 | There is one initializer which comes with this package, `@initMethod`. Annotate
26 | any top level function with this and it will be invoked automatically. For
27 | example, the program below will print `hello`:
28 |
29 | ```dart
30 | import 'package:initialize/initialize.dart';
31 |
32 | @initMethod
33 | printHello() => print('hello');
34 |
35 | main() => run();
36 | ```
37 |
38 | ### Running the initializers
39 |
40 | In order to run all the initializers, you need to import
41 | `package:initialize/initialize.dart` and invoke the `run` method. This should
42 | typically be the first thing to happen in your main. That method returns a Future,
43 | so you should put the remainder of your program inside the chained then call.
44 |
45 | ```dart
46 | import 'package:initialize/initialize.dart';
47 |
48 | main() {
49 | run().then((_) {
50 | print('hello world!');
51 | });
52 | }
53 | ```
54 |
55 | ## Transformer
56 |
57 | During development a mirror based system is used to find and run the initializers,
58 | but for deployment there is a transformer which can replace that with a static list
59 | of initializers to be ran.
60 |
61 | This will create a new entry point which bootstraps your existing app, this will
62 | have the same file name except `.dart` with be replaced with `.initialize.dart`.
63 | If you supply an html file to `entry_points` then it will bootstrap the dart
64 | script tag on that page and replace the `src` attribute to with the new
65 | bootstrap file.
66 |
67 | Below is an example pubspec with the transformer:
68 |
69 | name: my_app
70 | dependencies:
71 | initialize: any
72 | transformers:
73 | - initialize:
74 | entry_points: web/index.html
75 |
76 | ## Creating your own initializer
77 |
78 | Lets look at a slightly simplified version of the `@initMethod` class:
79 |
80 | ```dart
81 | class InitMethod implements Initializer {
82 | const InitMethod();
83 |
84 | @override
85 | initialize(Function method) => method();
86 | }
87 | ```
88 |
89 | You would now be able to add `@InitMethod()` in front of any function and it
90 | will be automatically invoked when the user calls `run()`.
91 |
92 | For classes which are stateless, you can usually just have a single const
93 | instance, and that is how the actual InitMethod implementation works. Simply add
94 | something like the following:
95 |
96 | ```dart
97 | const initMethod = const InitMethod();
98 | ```
99 |
100 | Now when people use the annotation, it just looks like `@initMethod` without any
101 | parenthesis, and its a bit more efficient since there is a single instance. You
102 | can also make your class private to force users into using the static instance.
103 |
104 | ## Creating custom transformer plugins
105 |
106 | It is possible to create a custom plugin for the initialize transformer which
107 | allows you to have full control over what happens to your annotations at compile
108 | time. Implement `InitializerPlugin` class and pass that in to the
109 | `InitializeTransformer` to make it take effect.
110 |
111 | You will need to be familiar with the `analyzer` package in order to write these
112 | plugins, but they can be extremely powerful. See the `DefaultInitializerPlugin`
113 | in `lib/build/initializer_plugin.dart` as a reference. Chances are you may want
114 | to extend that class in order to get a lot of the default functionality.
115 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.6.2+7
2 |
3 | * Strong mode fixes.
4 |
5 | ## 0.6.2+6
6 |
7 | * Small bug fixes for https://github.com/dart-lang/web-components/issues/54.
8 |
9 | ## 0.6.2+5
10 |
11 | * Update analyzer minimum version to `0.27.2`.
12 |
13 | ## 0.6.2+4
14 |
15 | * Stop using deprecated analyzer apis.
16 |
17 | ## 0.6.2+3
18 |
19 | * Update code_transformers, analyzer, and html version constraints.
20 |
21 | ## 0.6.2+2
22 |
23 | * Update `transformer_test` dep to `0.2.x`.
24 |
25 | ## 0.6.2+1
26 |
27 | * Add support for code_transformers `0.4.x`.
28 |
29 | ## 0.6.2
30 |
31 | * Update analyzer to `>=0.27.0 <0.28.0`.
32 |
33 | ## 0.6.1+2
34 |
35 | * Update analyzer to `<0.27.0` and dart_style to `<0.3.0`.
36 |
37 | ## 0.6.1+1
38 |
39 | * Update to work with deferred loaded libararies in reflective mode, (but not
40 | yet in the transformer).
41 |
42 | ## 0.6.1
43 |
44 | * Update to analyzer `<0.26.0`.
45 |
46 | ## 0.6.0+5
47 |
48 | * Fix bootstrap files to return the result of the original main.
49 |
50 | ## 0.6.0+4
51 |
52 | * Switch `html5lib` package dependency to `html`.
53 |
54 | ## 0.6.0+3
55 |
56 | * Make sure to always use the canonical libraries and super declarations in
57 | development mode. This eliminates an uncommon issue where a single initializer
58 | could be ran more than once.
59 |
60 | ## 0.6.0+2
61 |
62 | * Private identifiers will now be evaluated and inlined into the bootstrap file
63 | by the transformer, [29](https://github.com/dart-lang/initialize/issues/29).
64 |
65 | ## 0.6.0+1
66 |
67 | * Fix for `LibraryIdentifier` paths when initializer starts from inline scripts
68 | inside of subfolders.
69 |
70 | ## 0.6.0
71 |
72 | * Added the `from` option to `run`. This should be a `Uri` pointing to a library
73 | in the mirror system, and throws if not in mirrors mode. This can be used to
74 | run initializers in a custom order at development time.
75 | * This package no longer tries to handle initializing scripts found in html
76 | imports. If you need this feature please use `initWebComponents` from the
77 | `web_components` package.
78 |
79 | ## 0.5.1+8
80 |
81 | * Make sure to crawl the entire supertype chain for annotations, and run them
82 | in reverse order.
83 |
84 | ## 0.5.1+7
85 |
86 | * Change to source order-crawling of directives instead of alphabetical. The one
87 | exception is for `part` directives, those are still crawled in alphabetical
88 | order since we can't currently get the original source order from the mirror
89 | system.
90 |
91 | ## 0.5.1+6
92 |
93 | * Fix some analyzer warnings.
94 |
95 | ## 0.5.1+5
96 |
97 | * Fix an issue where for certain programs the transformer could fail,
98 | [33](https://github.com/dart-lang/polymer-dart/issues/33).
99 |
100 |
101 | ## 0.5.1+4
102 |
103 | * Update to use mock dart sdk from `code_transformers` and update the `analyzer`
104 | and `code_transformers` dependencies.
105 |
106 | ## 0.5.1+3
107 |
108 | * Fix up mirror based import crawling so it detects dartium and only crawl all
109 | libraries in the mirror system in that case.
110 |
111 | ## 0.5.1+2
112 |
113 | * Fix handling of exported libraries. Specifically, annotations on exported
114 | libraries will now be reached and imports in exported libraries will now be
115 | reached.
116 | * Add support for scripts living in html imports without adding an html
117 | dependency by crawling all libraries in the mirror system in reverse order,
118 | instead of just the root one.
119 |
120 | ## 0.5.1+1
121 |
122 | * Make sure to always use `path.url` in the transformer.
123 |
124 | ## 0.5.1
125 |
126 | * Added support for more types of expressions in constructor annotations. More
127 | specifically, any const expressions that evaluate to a `String`, `int`,
128 | `double`, or `bool` are now allowed. The evaluated value is what will be inlined
129 | in the bootstrap file in this case.
130 |
131 |
132 | ## 0.5.0
133 |
134 | * The `InitializePluginTransformer` is gone in favor of a new
135 | `InitializerPlugin` class which you can pass a list of to the
136 | `InitializeTransformer`. These plugins now have access to the fully resolved ast
137 | nodes and can directly control what is output in the bootstrap file.
138 |
139 | ## 0.4.0
140 |
141 | Lots of transformer updates:
142 |
143 | * The `new_entry_point` option is gone. The bootstrapped file will now always
144 | just be the original name but `.dart` will be replaced with `.initialize.dart`.
145 | * The `html_entry_point` option is gone, and the file extension is now used to
146 | detect if it is an html or dart file. You should no longer list the dart file
147 | contained in the html file. Effectively resolves
148 | [13](https://github.com/dart-lang/initialize/issues/13).
149 | * The `entry_point` option has been renamed `entry_points` and now accepts
150 | either a single file path or list of file paths. Additionally, it now supports
151 | Glob syntax so many files can be selected at once. Resolves
152 | [19](https://github.com/dart-lang/initialize/issues/19).
153 |
154 | ## 0.3.1
155 |
156 | * Added `InitializePluginTransformer` class in `plugin_transformer.dart` which
157 | provides a base transformer class which can be extended to perform custom
158 | transformations for annotations. These transformers should be included after the
159 | main `initialize` transformer and work by parsing the bootstrap file so the
160 | program doesn't need to be re-analyzed.
161 |
162 | ## 0.3.0
163 |
164 | * Library initializers now pass a `LibraryIdentifier` to `initialize` instead of
165 | just a `Symbol`. This provides the package and path to the library in addition
166 | to the symbol so that paths can be normalized.
167 |
168 | ## 0.2.0
169 |
170 | * `entryPoint` and `newEntryPoint` transformer options were renamed to
171 | `entry_point` and `new_entry_pont`.
172 |
173 | * Added `html_entry_point` option to the transformer. This will search that file
174 | for any script tag whose src is `entry_point` and rewrite it to point at the
175 | bootstrapped file `new_entry_point`.
176 |
177 | * Top level properties and static class properties are now supported in
178 | initializer constructors, as well as List and Map literals,
179 | [5](https://github.com/dart-lang/initialize/issues/5).
180 |
181 | ## 0.1.0+1
182 |
183 | Quick fix for the transformer on windows.
184 |
185 | ## 0.1.0
186 |
187 | Initial beta release. There is one notable missing feature in the release
188 | regarding constructor arguments, see
189 | [5](https://github.com/dart-lang/initialize/issues/5).
190 |
--------------------------------------------------------------------------------
/lib/src/mirror_loader.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.mirror_loader;
5 |
6 | import 'dart:collection' show Queue;
7 | import 'dart:mirrors';
8 | import 'package:path/path.dart' as path;
9 | import 'package:initialize/initialize.dart';
10 |
11 | final LibraryMirror _root = currentMirrorSystem().isolate.rootLibrary;
12 | final Map _libs = currentMirrorSystem().libraries;
13 |
14 | Queue loadInitializers(
15 | {List typeFilter, InitializerFilter customFilter, Uri from}) {
16 | return new InitializationCrawler(typeFilter, customFilter, from: from).run();
17 | }
18 |
19 | // Crawls a library and all its dependencies for `Initializer` annotations using
20 | // mirrors
21 | class InitializationCrawler {
22 | // Set of all visited annotations, keys are the declarations that were
23 | // annotated, values are the annotations that have been processed.
24 | static final _annotationsFound =
25 | new Map>();
26 |
27 | // If non-null, then only these annotations should be processed.
28 | final List typeFilter;
29 |
30 | // If non-null, then only annotations which return true when passed to this
31 | // function will be processed.
32 | final InitializerFilter customFilter;
33 |
34 | /// The library to start crawling from.
35 | final LibraryMirror _rootLibrary;
36 |
37 | /// Note: The [from] argument is only supported in the mirror_loader.dart. It
38 | /// is not supported statically.
39 | InitializationCrawler(this.typeFilter, this.customFilter, {Uri from})
40 | : _rootLibrary = from == null ? _root : _libs[from] {
41 | if (_rootLibrary == null) throw 'Unable to find library at $from.';
42 | }
43 |
44 | // The primary function in this class, invoke it to crawl and collect all the
45 | // annotations into a queue of init functions.
46 | Queue run() {
47 | var librariesSeen = new Set();
48 | var queue = new Queue();
49 |
50 | _readLibraryDeclarations(_rootLibrary, librariesSeen, queue);
51 | return queue;
52 | }
53 |
54 | /// Returns the canonical [LibraryMirror] for a given [LibraryMirror]. This
55 | /// is defined as the one loaded from a `package:` url if available, otherwise
56 | /// it is just [lib].
57 | LibraryMirror _canonicalLib(LibraryMirror lib) {
58 | var uri = lib.uri;
59 | if (_isHttpStylePackageUrl(uri)) {
60 | var packageUri = _packageUriFor(uri);
61 | if (_libs.containsKey(packageUri)) return _libs[packageUri];
62 | }
63 | return lib;
64 | }
65 |
66 | /// Returns the canonical [ClassMirror] for a given [ClassMirror]. This is
67 | /// defined as the one that appears in the canonical owner [LibararyMirror].
68 | ClassMirror _canonicalClassDeclaration(ClassMirror declaration) =>
69 | _canonicalLib(declaration.owner).declarations[declaration.simpleName];
70 |
71 | /// Whether [uri] is an http URI that contains a 'packages' segment, and
72 | /// therefore could be converted into a 'package:' URI.
73 | bool _isHttpStylePackageUrl(Uri uri) {
74 | var uriPath = uri.path;
75 | return uri.scheme == _root.uri.scheme &&
76 | // Don't process cross-domain uris.
77 | uri.authority == _root.uri.authority &&
78 | uriPath.endsWith('.dart') &&
79 | (uriPath.contains('/packages/') || uriPath.startsWith('packages/'));
80 | }
81 |
82 | /// Returns a `package:` version of [uri].
83 | Uri _packageUriFor(Uri uri) {
84 | var packagePath = uri.path
85 | .substring(uri.path.lastIndexOf('packages/') + 'packages/'.length);
86 | return Uri.parse('package:$packagePath');
87 | }
88 |
89 | // Reads Initializer annotations on this library and all its dependencies in
90 | // post-order.
91 | Queue _readLibraryDeclarations(LibraryMirror lib,
92 | Set librariesSeen, Queue queue) {
93 | lib = _canonicalLib(lib);
94 | if (librariesSeen.contains(lib)) return queue;
95 | librariesSeen.add(lib);
96 |
97 | // First visit all our dependencies.
98 | for (var dependency in lib.libraryDependencies) {
99 | // Skip dart: imports, they never use this package.
100 | var targetLibrary = dependency.targetLibrary;
101 | if (targetLibrary == null || targetLibrary.uri.scheme == 'dart') continue;
102 | _readLibraryDeclarations(dependency.targetLibrary, librariesSeen, queue);
103 | }
104 |
105 | // Second parse the library directive annotations.
106 | _readAnnotations(lib, queue);
107 |
108 | // Last, parse all class and method annotations.
109 | for (var declaration in _sortedDeclarationsWithMetadata(lib)) {
110 | _readAnnotations(declaration, queue);
111 | // Check classes for static annotations which are not supported
112 | if (declaration is ClassMirror) {
113 | for (var classDeclaration in declaration.declarations.values) {
114 | _readAnnotations(classDeclaration, queue);
115 | }
116 | }
117 | }
118 |
119 | return queue;
120 | }
121 |
122 | Iterable _sortedDeclarationsWithMetadata(
123 | LibraryMirror lib) {
124 | return new List()
125 | ..addAll(_sortDeclarations(
126 | lib,
127 | lib.declarations.values
128 | .where((d) => d is MethodMirror && d.metadata.isNotEmpty)))
129 | ..addAll(_sortDeclarations(
130 | lib,
131 | lib.declarations.values
132 | .where((d) => d is ClassMirror && d.metadata.isNotEmpty)));
133 | }
134 |
135 | List _sortDeclarations(
136 | LibraryMirror sourceLib, Iterable declarations) {
137 | var declarationList = declarations.toList();
138 | declarationList.sort((DeclarationMirror a, DeclarationMirror b) {
139 | // If in the same file, compare by line.
140 | var aSourceUri = a.location.sourceUri;
141 | var bSourceUri = b.location.sourceUri;
142 | if (aSourceUri == bSourceUri) {
143 | return a.location.line.compareTo(b.location.line);
144 | }
145 |
146 | // Run parts first if one is from the original library.
147 | if (aSourceUri == sourceLib.uri) return 1;
148 | if (bSourceUri == sourceLib.uri) return -1;
149 |
150 | // Sort parts alphabetically.
151 | return aSourceUri.path.compareTo(bSourceUri.path);
152 | });
153 | return declarationList;
154 | }
155 |
156 | /// Reads annotations on a [DeclarationMirror] and adds them to [_initQueue]
157 | /// if they are [Initializer]s.
158 | void _readAnnotations(DeclarationMirror declaration, Queue queue) {
159 | var annotations =
160 | declaration.metadata.where((m) => _filterMetadata(declaration, m));
161 | for (var meta in annotations) {
162 | _annotationsFound.putIfAbsent(
163 | declaration, () => new Set());
164 | _annotationsFound[declaration].add(meta);
165 |
166 | // Initialize super classes first, if they are in the same library,
167 | // otherwise we throw an error. This can only be the case if there are
168 | // cycles in the imports.
169 | if (declaration is ClassMirror && declaration.superclass != null) {
170 | if (declaration.superclass.owner == declaration.owner) {
171 | _readAnnotations(declaration.superclass, queue);
172 | } else {
173 | // Make sure to check the canonical superclass declaration, the one
174 | // we get here is not always that. Specifically, this occurs if all of
175 | // the following conditions are met:
176 | //
177 | // 1. The current library is never loaded via a `package:` dart
178 | // import anywhere in the program.
179 | // 2. The current library loads the superclass via a relative file
180 | // import.
181 | // 3. The super class is imported via a `package:` import somewhere
182 | // else in the program.
183 | var canonicalSuperDeclaration =
184 | _canonicalClassDeclaration(declaration.superclass);
185 | var superMetas = canonicalSuperDeclaration.metadata
186 | .where((m) => _filterMetadata(canonicalSuperDeclaration, m))
187 | .toList();
188 | if (superMetas.isNotEmpty) {
189 | throw new UnsupportedError(
190 | 'We have detected a cycle in your import graph when running '
191 | 'initializers on ${declaration.qualifiedName}. This means the '
192 | 'super class ${canonicalSuperDeclaration.qualifiedName} has a '
193 | 'dependency on this library (possibly transitive).');
194 | }
195 | }
196 | }
197 |
198 | var annotatedValue;
199 | if (declaration is ClassMirror) {
200 | annotatedValue = declaration.reflectedType;
201 | } else if (declaration is MethodMirror) {
202 | if (declaration.owner is! LibraryMirror) {
203 | // TODO(jakemac): Support static class methods.
204 | throw _TOP_LEVEL_FUNCTIONS_ONLY;
205 | }
206 | annotatedValue = (declaration.owner as ObjectMirror)
207 | .getField(declaration.simpleName)
208 | .reflectee;
209 | } else if (declaration is LibraryMirror) {
210 | var package;
211 | var filePath;
212 | Uri uri = declaration.uri;
213 | // Convert to a package style uri if possible.
214 | if (_isHttpStylePackageUrl(uri)) {
215 | uri = _packageUriFor(uri);
216 | }
217 | if (uri.scheme == 'file' || uri.scheme.startsWith('http')) {
218 | filePath = path.url.relative(uri.path,
219 | from: _root.uri.path.endsWith('/')
220 | ? _root.uri.path
221 | : path.url.dirname(_root.uri.path));
222 | } else if (uri.scheme == 'package') {
223 | var segments = uri.pathSegments;
224 | package = segments[0];
225 | filePath = path.url.joinAll(segments.getRange(1, segments.length));
226 | } else {
227 | throw new UnsupportedError('Unsupported uri scheme ${uri.scheme} for '
228 | 'library ${declaration}.');
229 | }
230 | annotatedValue =
231 | new LibraryIdentifier(declaration.qualifiedName, package, filePath);
232 | } else {
233 | throw _UNSUPPORTED_DECLARATION;
234 | }
235 | queue.addLast(() => meta.reflectee.initialize(annotatedValue));
236 | }
237 | }
238 |
239 | // Filter function that returns true only if `meta` is an `Initializer`,
240 | // it passes the `typeFilter` and `customFilter` if they exist, and it has not
241 | // yet been seen.
242 | bool _filterMetadata(DeclarationMirror declaration, InstanceMirror meta) {
243 | if (meta.reflectee is! Initializer) return false;
244 | if (typeFilter != null &&
245 | !typeFilter.any((t) => meta.reflectee.runtimeType == t)) {
246 | return false;
247 | }
248 | if (customFilter != null && !customFilter(meta.reflectee)) return false;
249 | if (!_annotationsFound.containsKey(declaration)) return true;
250 | if (_annotationsFound[declaration].contains(meta)) return false;
251 | return true;
252 | }
253 | }
254 |
255 | final _TOP_LEVEL_FUNCTIONS_ONLY = new UnsupportedError(
256 | 'Only top level methods are supported for initializers');
257 |
258 | final _UNSUPPORTED_DECLARATION = new UnsupportedError(
259 | 'Initializers are only supported on libraries, classes, and top level '
260 | 'methods');
261 |
--------------------------------------------------------------------------------
/lib/build/initializer_plugin.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.build.initializer_plugin;
5 |
6 | import 'package:analyzer/dart/ast/ast.dart';
7 | import 'package:analyzer/dart/element/element.dart';
8 | import 'package:analyzer/src/generated/constant.dart';
9 | import 'package:barback/barback.dart';
10 | import 'package:code_transformers/resolver.dart';
11 | import 'package:initialize/transformer.dart';
12 | import 'package:path/path.dart' as path;
13 |
14 | /// A plug which allows an initializer to write out an [InitEntry] given some
15 | /// [InitializerData] from an annotation that was found.
16 | abstract class InitializerPlugin {
17 | /// Whether or not this plugin should be applied to an [Initializer] given
18 | /// some [InitializerData]. If [true] is returned then this plugin will take
19 | /// ownership of this [InitializerData] and no subsequent plugins will have
20 | /// an opportunity to access it.
21 | bool shouldApply(InitializerPluginData data);
22 |
23 | /// Returns a [String] or [null]. The [String] should represent dart code
24 | /// which creates a new [InitEntry] and that entry is added to the static
25 | /// initializers list. If [null] is returned then no entry is added at all for
26 | /// this [InitializerData].
27 | String apply(InitializerPluginData data);
28 | }
29 |
30 | /// A class which wraps all the default data passed to an [InitializerPlugin]
31 | /// for each annotation.
32 | class InitializerPluginData {
33 | final InitializerData initializer;
34 | final AssetId bootstrapId;
35 | final Map libraryPrefixes;
36 | final TransformLogger logger;
37 | final Resolver resolver;
38 | InitializerPluginData(this.initializer, this.bootstrapId,
39 | this.libraryPrefixes, this.resolver, this.logger);
40 | }
41 |
42 | /// The basic [InitializerPlugin]. This generates a new [InitEntry] to be added
43 | /// to the static initializers list, and applies to every item it sees.
44 | class DefaultInitializerPlugin implements InitializerPlugin {
45 | const DefaultInitializerPlugin();
46 |
47 | /// Applies to everything. Put other plugins before this one to override this
48 | /// behaviour.
49 | bool shouldApply(InitializerPluginData data) => true;
50 |
51 | /// Creates a normal [InitEntry] string.
52 | String apply(InitializerPluginData pluginData) {
53 | var target = buildTarget(pluginData);
54 | var meta = buildMeta(pluginData);
55 | return 'new InitEntry($meta, $target)';
56 | }
57 |
58 | /// Builds a [String] representing the meta of an [InitEntry] given an
59 | /// [ElementAnnotation] that was found.
60 | String buildMeta(InitializerPluginData pluginData) {
61 | var logger = pluginData.logger;
62 | var elementAnnotation = pluginData.initializer.annotationElement;
63 | var elementAnnotationElement = elementAnnotation.element;
64 | if (elementAnnotationElement is ConstructorElement) {
65 | return buildConstructorMeta(elementAnnotation, pluginData);
66 | } else if (elementAnnotationElement is PropertyAccessorElement) {
67 | return buildPropertyMeta(elementAnnotation, pluginData);
68 | } else {
69 | logger.error('Unsupported annotation type. Only constructors and '
70 | 'properties are supported as initializers.');
71 | }
72 | return null;
73 | }
74 |
75 | /// Builds a [String] representing the meta of an [InitEntry] given an
76 | /// [ElementAnnotation] whose element was a [ConstructorElement].
77 | String buildConstructorMeta(
78 | ElementAnnotation elementAnnotation, InitializerPluginData pluginData) {
79 | var logger = pluginData.logger;
80 | var node = pluginData.initializer.targetNode;
81 | var metaPrefix =
82 | pluginData.libraryPrefixes[elementAnnotation.element.library];
83 |
84 | var annotation = pluginData.initializer.annotationNode;
85 | if (annotation == null) {
86 | logger.error(
87 | 'Initializer annotations are only supported on libraries, classes, '
88 | 'and top level methods. Found $node.');
89 | }
90 | var clazz = annotation.name;
91 | var constructor = annotation.constructorName == null
92 | ? ''
93 | : '.${annotation.constructorName}';
94 | var args = buildArgumentList(annotation.arguments, pluginData);
95 | return 'const $metaPrefix.${clazz}$constructor$args';
96 | }
97 |
98 | /// Builds a [String] representing the meta of an [InitEntry] given an
99 | /// [ElementAnnotation] whose element was a [PropertyAccessorElement].
100 | String buildPropertyMeta(
101 | ElementAnnotation annotation, InitializerPluginData pluginData) {
102 | var metaPrefix = pluginData.libraryPrefixes[annotation.element.library];
103 | return '$metaPrefix.${annotation.element.name}';
104 | }
105 |
106 | /// Builds a [String] for the target of an [InitEntry] given an [Element] that
107 | /// was annotated.
108 | String buildTarget(InitializerPluginData pluginData) {
109 | var element = pluginData.initializer.targetElement;
110 | var logger = pluginData.logger;
111 | if (element is LibraryElement) {
112 | return buildLibraryTarget(element, pluginData);
113 | } else if (element is ClassElement) {
114 | return buildClassTarget(element, pluginData);
115 | } else if (element is FunctionElement) {
116 | return buildFunctionTarget(element, pluginData);
117 | } else {
118 | logger.error('Initializers can only be applied to top level functions, '
119 | 'libraries, and classes.');
120 | }
121 | return null;
122 | }
123 |
124 | /// Builds a [String] for the target of an [InitEntry] given [element] which
125 | /// is an annotated class.
126 | String buildClassTarget(
127 | ClassElement element, InitializerPluginData pluginData) =>
128 | buildSimpleTarget(element, pluginData);
129 |
130 | /// Builds a [String] for the target of an [InitEntry] given [element] which
131 | /// is an annotated function.
132 | String buildFunctionTarget(
133 | FunctionElement element, InitializerPluginData pluginData) =>
134 | buildSimpleTarget(element, pluginData);
135 |
136 | /// Builds a [String] for the target of an [InitEntry] for a simple [Element].
137 | /// This is just the library prefix followed by the element name.
138 | String buildSimpleTarget(Element element, InitializerPluginData pluginData) =>
139 | '${pluginData.libraryPrefixes[element.library]}.${element.name}';
140 |
141 | /// Builds a [String] for the target of an [InitEntry] given [element] which
142 | /// is an annotated library.
143 | String buildLibraryTarget(
144 | LibraryElement element, InitializerPluginData pluginData) {
145 | var bootstrapId = pluginData.bootstrapId;
146 | var logger = pluginData.logger;
147 | var segments = element.source.uri.pathSegments;
148 | var package = segments[0];
149 | var libraryPath;
150 | var packageString;
151 | if (bootstrapId.package == package &&
152 | bootstrapId.path.startsWith('${segments[1]}/')) {
153 | // reset `package` to null, we will do a relative path in this case.
154 | packageString = 'null';
155 | libraryPath = path.url.relative(
156 | path.url.joinAll(segments.getRange(1, segments.length)),
157 | from: path.url.dirname(path.url.join(bootstrapId.path)));
158 | } else if (segments[1] == 'lib') {
159 | packageString = "'$package'";
160 | libraryPath = path.url.joinAll(segments.getRange(2, segments.length));
161 | } else {
162 | logger.error('Unable to import `${element.source.uri.path}` from '
163 | '${bootstrapId.path}.');
164 | }
165 |
166 | return "const LibraryIdentifier"
167 | "(#${element.name}, $packageString, '$libraryPath')";
168 | }
169 |
170 | /// Builds a [String] representing an [ArgumentList] taking into account the
171 | /// [libraryPrefixes] from [pluginData].
172 | String buildArgumentList(
173 | ArgumentList args, InitializerPluginData pluginData) {
174 | var buffer = new StringBuffer();
175 | buffer.write('(');
176 | var first = true;
177 | for (var arg in args.arguments) {
178 | if (!first) buffer.write(', ');
179 | first = false;
180 |
181 | Expression expression;
182 | if (arg is NamedExpression) {
183 | buffer.write('${arg.name.label.name}: ');
184 | expression = arg.expression;
185 | } else {
186 | expression = arg;
187 | }
188 |
189 | buffer.write(buildExpression(expression, pluginData));
190 | }
191 | buffer.write(')');
192 | return buffer.toString();
193 | }
194 |
195 | /// Builds a [String] representing [expression] taking into account the
196 | /// [libraryPrefixes] from [pluginData].
197 | String buildExpression(
198 | Expression expression, InitializerPluginData pluginData) {
199 | var logger = pluginData.logger;
200 | var libraryPrefixes = pluginData.libraryPrefixes;
201 | var buffer = new StringBuffer();
202 | if (expression is StringLiteral && expression.stringValue != null) {
203 | buffer.write(_stringValue(expression.stringValue));
204 | } else if (expression is BooleanLiteral ||
205 | expression is DoubleLiteral ||
206 | expression is IntegerLiteral ||
207 | expression is NullLiteral) {
208 | buffer.write('${expression}');
209 | } else if (expression is ListLiteral) {
210 | buffer.write('const [');
211 | var first = true;
212 | for (Expression listExpression in expression.elements) {
213 | if (!first) buffer.write(', ');
214 | first = false;
215 | buffer.write(buildExpression(listExpression, pluginData));
216 | }
217 | buffer.write(']');
218 | } else if (expression is MapLiteral) {
219 | buffer.write('const {');
220 | var first = true;
221 | for (MapLiteralEntry entry in expression.entries) {
222 | if (!first) buffer.write(', ');
223 | first = false;
224 | buffer.write(buildExpression(entry.key, pluginData));
225 | buffer.write(': ');
226 | buffer.write(buildExpression(entry.value, pluginData));
227 | }
228 | buffer.write('}');
229 | } else if (expression is Identifier) {
230 | var element = expression.bestElement;
231 | if (element == null) {
232 | logger.error('Unable to get `bestElement` for expression: $expression');
233 | } else if (!element.isPublic) {
234 | // Inline the evaluated value of private identifiers.
235 | buffer.write(_evaluateExpression(expression, pluginData));
236 | } else {
237 | libraryPrefixes.putIfAbsent(
238 | element.library, () => 'i${libraryPrefixes.length}');
239 |
240 | buffer.write('${libraryPrefixes[element.library]}.');
241 | if (element is ClassElement) {
242 | buffer.write(element.name);
243 | } else if (element is PropertyAccessorElement) {
244 | var variable = element.variable;
245 | if (variable is FieldElement) {
246 | buffer.write('${variable.enclosingElement.name}.');
247 | }
248 | buffer.write('${variable.name}');
249 | } else {
250 | logger.error('Unsupported argument to initializer constructor.');
251 | }
252 | }
253 | } else if (expression is PropertyAccess) {
254 | buffer.write(buildExpression(expression.target, pluginData));
255 | buffer.write('.${expression.propertyName}');
256 | } else if (expression is InstanceCreationExpression) {
257 | logger.error('Unsupported expression in initializer, found $expression. '
258 | 'Instance creation expressions are not supported (yet). Instead, '
259 | 'please assign it to a const variable and use that instead.');
260 | } else {
261 | buffer.write(_evaluateExpression(expression, pluginData));
262 | }
263 | return buffer.toString();
264 | }
265 |
266 | _evaluateExpression(Expression expression, InitializerPluginData pluginData) {
267 | var logger = pluginData.logger;
268 | var result = pluginData.resolver.evaluateConstant(
269 | pluginData.initializer.targetElement.library, expression);
270 | if (!result.isValid) {
271 | logger.error('Invalid expression in initializer, found $expression. '
272 | 'And got the following errors: ${result.errors}.');
273 | return null;
274 | }
275 |
276 | var value = _getValue(result.value);
277 |
278 | if (value == null) {
279 | logger.error('Unsupported expression in initializer, found '
280 | '$expression. Please file a bug at '
281 | 'https://github.com/dart-lang/initialize/issues');
282 | }
283 |
284 | if (value is String) value = _stringValue(value);
285 |
286 | return value;
287 | }
288 |
289 | // Returns an expression for a string value. Wraps it in single quotes and
290 | // escapes existing single quotes and escapes.
291 | _stringValue(String value) {
292 | value = value.replaceAll(r'\', r'\\').replaceAll(r"'", r"\'");
293 | return "'$value'";
294 | }
295 |
296 | // Gets an actual value for a [DartObject].
297 | _getValue(DartObject object) {
298 | if (object == null) return null;
299 | var value = object.toBoolValue() ??
300 | object.toDoubleValue() ??
301 | object.toIntValue() ??
302 | object.toStringValue();
303 | if (value == null) {
304 | List list = object.toListValue();
305 | if (list != null) {
306 | return list.map((DartObject element) => _getValue(element)).toList();
307 | }
308 | Map map = object.toMapValue();
309 | if (map != null) {
310 | Map result = {};
311 | map.forEach((DartObject key, DartObject value) {
312 | dynamic mappedKey = _getValue(key);
313 | if (mappedKey != null) {
314 | result[mappedKey] = _getValue(value);
315 | }
316 | });
317 | return result;
318 | }
319 | }
320 | return value;
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/test/transformer_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | @TestOn('vm')
5 | library initialize.transformer_test;
6 |
7 | import 'common.dart';
8 | import 'package:analyzer/dart/element/element.dart';
9 | import 'package:dart_style/dart_style.dart';
10 | import 'package:initialize/transformer.dart';
11 | import 'package:test/test.dart';
12 |
13 | var formatter = new DartFormatter();
14 |
15 | main() {
16 | group('Html entry points', htmlEntryPointTests);
17 | group('Dart entry points', dartEntryPointTests);
18 | group('InitializerPlugins', pluginTests);
19 | }
20 |
21 | void htmlEntryPointTests() {
22 | var phases = [
23 | [
24 | new InitializeTransformer(['web/*.html'])
25 | ]
26 | ];
27 |
28 | testPhases('basic', phases, {
29 | 'a|web/index.html': '''
30 |
31 |
32 |
33 | '''
34 | .replaceAll(' ', ''),
35 | 'a|web/index.dart': '''
36 | library web_foo;
37 |
38 | import 'foo.dart';
39 | ''',
40 | 'a|web/foo.dart': '''
41 | @constInit
42 | library foo;
43 |
44 | import 'package:initialize/initialize.dart';
45 | import 'package:test_initializers/common.dart';
46 | import 'package:bar/bar.dart';
47 |
48 | @constInit
49 | class Foo extends Bar {}
50 |
51 | @initMethod
52 | foo() {}
53 | ''',
54 | 'bar|lib/bar.dart': '''
55 | @DynamicInit('bar')
56 | @DynamicInit('bar2')
57 | library bar;
58 |
59 | import 'package:initialize/initialize.dart';
60 | import 'package:test_initializers/common.dart';
61 | import 'baz.dart';
62 |
63 | @DynamicInit('Bar')
64 | @DynamicInit('Bar2')
65 | class Bar {}
66 |
67 | @DynamicInit('bar()')
68 | @initMethod
69 | bar() {}
70 | ''',
71 | 'bar|lib/baz.dart': '''
72 | @constInit
73 | library baz;
74 |
75 | import 'package:test_initializers/common.dart';
76 | ''',
77 | // Mock out the Initialize package plus some initializers.
78 | 'initialize|lib/initialize.dart': mockInitialize,
79 | 'test_initializers|lib/common.dart': commonInitializers,
80 | }, {
81 | 'a|web/index.html': '''
82 |
83 |
84 |
85 | '''
86 | .replaceAll(' ', ''),
87 | 'a|web/index.initialize.dart': formatter.format('''
88 | import 'package:initialize/src/static_loader.dart';
89 | import 'package:initialize/initialize.dart';
90 | import 'index.dart' as i0;
91 | import 'package:bar/baz.dart' as i1;
92 | import 'package:test_initializers/common.dart' as i2;
93 | import 'package:bar/bar.dart' as i3;
94 | import 'package:initialize/initialize.dart' as i4;
95 | import 'foo.dart' as i5;
96 |
97 | main() {
98 | initializers.addAll([
99 | new InitEntry(i2.constInit, const LibraryIdentifier(#baz, 'bar', 'baz.dart')),
100 | new InitEntry(const i2.DynamicInit('bar'), const LibraryIdentifier(#bar, 'bar', 'bar.dart')),
101 | new InitEntry(const i2.DynamicInit('bar2'), const LibraryIdentifier(#bar, 'bar', 'bar.dart')),
102 | new InitEntry(const i2.DynamicInit('bar()'), i3.bar),
103 | new InitEntry(i4.initMethod, i3.bar),
104 | new InitEntry(const i2.DynamicInit('Bar'), i3.Bar),
105 | new InitEntry(const i2.DynamicInit('Bar2'), i3.Bar),
106 | new InitEntry(i2.constInit, const LibraryIdentifier(#foo, null, 'foo.dart')),
107 | new InitEntry(i4.initMethod, i5.foo),
108 | new InitEntry(i2.constInit, i5.Foo),
109 | ]);
110 |
111 | return i0.main();
112 | }
113 | ''')
114 | }, []);
115 | }
116 |
117 | void dartEntryPointTests() {
118 | var phases = [
119 | [
120 | new InitializeTransformer(['web/index.dart'])
121 | ]
122 | ];
123 |
124 | testPhases('constructor arguments', phases, {
125 | 'a|web/index.dart': '''
126 | @DynamicInit(foo)
127 | @DynamicInit(_foo)
128 | @DynamicInit(Foo.foo)
129 | @DynamicInit(bar.Foo.bar)
130 | @DynamicInit(bar.Foo.foo)
131 | @DynamicInit(const [foo, Foo.foo, 'foo'])
132 | @DynamicInit(const {'foo': foo, 'Foo.foo': Foo.foo, 'bar': 'bar'})
133 | @DynamicInit('foo')
134 | @DynamicInit(true)
135 | @DynamicInit(null)
136 | @DynamicInit(1)
137 | @DynamicInit(1.1)
138 | @DynamicInit('foo-\$x\${y}')
139 | @DynamicInit(1 + 2)
140 | @DynamicInit(1.0 + 0.2)
141 | @DynamicInit(1 == 1)
142 | @NamedArgInit(1, name: 'Bill')
143 | library web_foo;
144 |
145 | import 'package:test_initializers/common.dart';
146 | import 'foo.dart';
147 | import 'foo.dart' as bar;
148 |
149 | const x = 'x';
150 | const y = 'y';
151 | const _foo = '_foo';
152 |
153 | class MyConst {
154 | const MyConst;
155 | }
156 | ''',
157 | 'a|web/foo.dart': '''
158 | library foo;
159 |
160 | const String foo = 'foo';
161 |
162 | class Bar {
163 | const Bar();
164 | }
165 |
166 | class Foo {
167 | static foo = 'Foo.foo';
168 | static bar = const Bar();
169 | }
170 | ''',
171 | // Mock out the Initialize package plus some initializers.
172 | 'initialize|lib/initialize.dart': mockInitialize,
173 | 'test_initializers|lib/common.dart': commonInitializers,
174 | }, {
175 | 'a|web/index.initialize.dart': formatter.format('''
176 | import 'package:initialize/src/static_loader.dart';
177 | import 'package:initialize/initialize.dart';
178 | import 'index.dart' as i0;
179 | import 'package:test_initializers/common.dart' as i1;
180 | import 'foo.dart' as i2;
181 |
182 | main() {
183 | initializers.addAll([
184 | new InitEntry(const i1.DynamicInit(i2.foo), const LibraryIdentifier(#web_foo, null, 'index.dart')),
185 | new InitEntry(const i1.DynamicInit('_foo'), const LibraryIdentifier(#web_foo, null, 'index.dart')),
186 | new InitEntry(const i1.DynamicInit(i2.Foo.foo), const LibraryIdentifier(#web_foo, null, 'index.dart')),
187 | new InitEntry(const i1.DynamicInit(i2.Foo.bar), const LibraryIdentifier(#web_foo, null, 'index.dart')),
188 | new InitEntry(const i1.DynamicInit(i2.Foo.foo), const LibraryIdentifier(#web_foo, null, 'index.dart')),
189 | new InitEntry(const i1.DynamicInit(const [i2.foo, i2.Foo.foo, 'foo']), const LibraryIdentifier(#web_foo, null, 'index.dart')),
190 | new InitEntry(const i1.DynamicInit(const {'foo': i2.foo, 'Foo.foo': i2.Foo.foo, 'bar': 'bar'}), const LibraryIdentifier(#web_foo, null, 'index.dart')),
191 | new InitEntry(const i1.DynamicInit('foo'), const LibraryIdentifier(#web_foo, null, 'index.dart')),
192 | new InitEntry(const i1.DynamicInit(true), const LibraryIdentifier(#web_foo, null, 'index.dart')),
193 | new InitEntry(const i1.DynamicInit(null), const LibraryIdentifier(#web_foo, null, 'index.dart')),
194 | new InitEntry(const i1.DynamicInit(1), const LibraryIdentifier(#web_foo, null, 'index.dart')),
195 | new InitEntry(const i1.DynamicInit(1.1), const LibraryIdentifier(#web_foo, null, 'index.dart')),
196 | new InitEntry(const i1.DynamicInit('foo-xy'), const LibraryIdentifier(#web_foo, null, 'index.dart')),
197 | new InitEntry(const i1.DynamicInit(3), const LibraryIdentifier(#web_foo, null, 'index.dart')),
198 | new InitEntry(const i1.DynamicInit(1.2), const LibraryIdentifier(#web_foo, null, 'index.dart')),
199 | new InitEntry(const i1.DynamicInit(true), const LibraryIdentifier(#web_foo, null, 'index.dart')),
200 | new InitEntry(const i1.NamedArgInit(1, name: 'Bill'), const LibraryIdentifier(#web_foo, null, 'index.dart')),
201 | ]);
202 |
203 | return i0.main();
204 | }
205 | ''')
206 | }, []);
207 |
208 | testPhases('exported library annotations', phases, {
209 | 'a|web/index.dart': '''
210 | library web_foo;
211 |
212 | export 'foo.dart';
213 | ''',
214 | 'a|web/foo.dart': '''
215 | @constInit
216 | library foo;
217 |
218 | import 'package:test_initializers/common.dart';
219 |
220 | @constInit
221 | foo() {};
222 |
223 | @constInit
224 | class Foo {}
225 | ''',
226 | // Mock out the Initialize package plus some initializers.
227 | 'initialize|lib/initialize.dart': mockInitialize,
228 | 'test_initializers|lib/common.dart': commonInitializers,
229 | }, {
230 | 'a|web/index.initialize.dart': formatter.format('''
231 | import 'package:initialize/src/static_loader.dart';
232 | import 'package:initialize/initialize.dart';
233 | import 'index.dart' as i0;
234 | import 'foo.dart' as i1;
235 | import 'package:test_initializers/common.dart' as i2;
236 |
237 | main() {
238 | initializers.addAll([
239 | new InitEntry(i2.constInit, const LibraryIdentifier(#foo, null, 'foo.dart')),
240 | new InitEntry(i2.constInit, i1.foo),
241 | new InitEntry(i2.constInit, i1.Foo),
242 | ]);
243 |
244 | return i0.main();
245 | }
246 | ''')
247 | }, []);
248 |
249 | testPhases('imports from exported libraries', phases, {
250 | 'a|web/index.dart': '''
251 | library web_foo;
252 |
253 | export 'foo.dart';
254 | ''',
255 | 'a|web/foo.dart': '''
256 | library foo;
257 |
258 | import 'foo/bar.dart';
259 | ''',
260 | 'a|web/foo/bar.dart': '''
261 | @constInit
262 | library bar;
263 |
264 | import 'package:test_initializers/common.dart';
265 |
266 | @constInit
267 | bar() {};
268 |
269 | @constInit
270 | class Bar {}
271 | ''',
272 | // Mock out the Initialize package plus some initializers.
273 | 'initialize|lib/initialize.dart': mockInitialize,
274 | 'test_initializers|lib/common.dart': commonInitializers,
275 | }, {
276 | 'a|web/index.initialize.dart': formatter.format('''
277 | import 'package:initialize/src/static_loader.dart';
278 | import 'package:initialize/initialize.dart';
279 | import 'index.dart' as i0;
280 | import 'foo/bar.dart' as i1;
281 | import 'package:test_initializers/common.dart' as i2;
282 |
283 | main() {
284 | initializers.addAll([
285 | new InitEntry(i2.constInit, const LibraryIdentifier(#bar, null, 'foo/bar.dart')),
286 | new InitEntry(i2.constInit, i1.bar),
287 | new InitEntry(i2.constInit, i1.Bar),
288 | ]);
289 |
290 | return i0.main();
291 | }
292 | ''')
293 | }, []);
294 |
295 | testPhases('library parts and exports', phases, {
296 | 'a|web/index.dart': '''
297 | @constInit
298 | library index;
299 |
300 | import 'package:test_initializers/common.dart';
301 | export 'export.dart';
302 |
303 | part 'foo.dart';
304 | part 'bar.dart';
305 |
306 | @constInit
307 | index() {};
308 |
309 | @constInit
310 | class Index {};
311 | ''',
312 | 'a|web/foo.dart': '''
313 | part of index;
314 |
315 | @constInit
316 | foo() {};
317 |
318 | @constInit
319 | class Foo {};
320 | ''',
321 | 'a|web/bar.dart': '''
322 | part of index;
323 |
324 | @constInit
325 | bar() {};
326 |
327 | @constInit
328 | class Bar {};
329 | ''',
330 | 'a|web/export.dart': '''
331 | @constInit
332 | library export;
333 |
334 | import 'package:test_initializers/common.dart';
335 |
336 | @constInit
337 | class Export {};
338 | ''',
339 | // Mock out the Initialize package plus some initializers.
340 | 'initialize|lib/initialize.dart': mockInitialize,
341 | 'test_initializers|lib/common.dart': commonInitializers,
342 | }, {
343 | 'a|web/index.initialize.dart': formatter.format('''
344 | import 'package:initialize/src/static_loader.dart';
345 | import 'package:initialize/initialize.dart';
346 | import 'index.dart' as i0;
347 | import 'export.dart' as i1;
348 | import 'package:test_initializers/common.dart' as i2;
349 |
350 | main() {
351 | initializers.addAll([
352 | new InitEntry(i2.constInit, const LibraryIdentifier(#export, null, 'export.dart')),
353 | new InitEntry(i2.constInit, i1.Export),
354 | new InitEntry(i2.constInit, const LibraryIdentifier(#index, null, 'index.dart')),
355 | new InitEntry(i2.constInit, i0.bar),
356 | new InitEntry(i2.constInit, i0.foo),
357 | new InitEntry(i2.constInit, i0.index),
358 | new InitEntry(i2.constInit, i0.Bar),
359 | new InitEntry(i2.constInit, i0.Foo),
360 | new InitEntry(i2.constInit, i0.Index),
361 | ]);
362 |
363 | return i0.main();
364 | }
365 | ''')
366 | }, []);
367 | }
368 |
369 | class SkipConstructorsPlugin extends InitializerPlugin {
370 | bool shouldApply(InitializerPluginData data) {
371 | return data.initializer.annotationElement.element is ConstructorElement;
372 | }
373 |
374 | String apply(_) => null;
375 | }
376 |
377 | void pluginTests() {
378 | var phases = [
379 | [
380 | new InitializeTransformer(['web/index.dart'],
381 | plugins: [new SkipConstructorsPlugin()])
382 | ]
383 | ];
384 |
385 | testPhases('can omit statements', phases, {
386 | 'a|web/index.dart': '''
387 | library index;
388 |
389 | import 'package:initialize/initialize.dart';
390 | import 'package:test_initializers/common.dart';
391 | import 'foo.dart';
392 |
393 | @initMethod
394 | @DynamicInit('index')
395 | index() {}
396 | ''',
397 | 'a|web/foo.dart': '''
398 | library foo;
399 |
400 | import 'package:initialize/initialize.dart';
401 | import 'package:test_initializers/common.dart';
402 |
403 | @initMethod
404 | @DynamicInit('Foo')
405 | foo() {}
406 | ''',
407 | // Mock out the Initialize package plus some initializers.
408 | 'initialize|lib/initialize.dart': mockInitialize,
409 | 'test_initializers|lib/common.dart': commonInitializers,
410 | }, {
411 | 'a|web/index.initialize.dart': formatter.format('''
412 | import 'package:initialize/src/static_loader.dart';
413 | import 'package:initialize/initialize.dart';
414 | import 'index.dart' as i0;
415 | import 'foo.dart' as i1;
416 | import 'package:initialize/initialize.dart' as i2;
417 | import 'package:test_initializers/common.dart' as i3;
418 |
419 | main() {
420 | initializers.addAll([
421 | new InitEntry(i2.initMethod, i1.foo),
422 | new InitEntry(i2.initMethod, i0.index),
423 | ]);
424 |
425 | return i0.main();
426 | }
427 | ''')
428 | }, []);
429 | }
430 |
--------------------------------------------------------------------------------
/lib/transformer.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, 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 | library initialize.transformer;
5 |
6 | import 'dart:async';
7 | import 'dart:collection' show Queue;
8 | import 'package:analyzer/dart/ast/ast.dart';
9 | import 'package:analyzer/dart/element/element.dart';
10 | import 'package:analyzer/dart/element/type.dart';
11 | import 'package:barback/barback.dart';
12 | import 'package:code_transformers/assets.dart';
13 | import 'package:code_transformers/resolver.dart';
14 | import 'package:code_transformers/src/dart_sdk.dart' as dart_sdk;
15 | import 'package:dart_style/dart_style.dart';
16 | import 'package:glob/glob.dart';
17 | import 'package:html/dom.dart' as dom;
18 | import 'package:html/parser.dart' show parse;
19 | import 'package:path/path.dart' as path;
20 |
21 | import 'build/initializer_plugin.dart';
22 | export 'build/initializer_plugin.dart';
23 |
24 | /// Create a new [Asset] which inlines your [Initializer] annotations into
25 | /// a new file that bootstraps your application.
26 | Asset generateBootstrapFile(Resolver resolver, Transform transform,
27 | AssetId primaryAssetId, AssetId newEntryPointId,
28 | {bool errorIfNotFound: true,
29 | List plugins,
30 | bool appendDefaultPlugin: true}) {
31 | if (appendDefaultPlugin) {
32 | if (plugins == null) plugins = [];
33 | plugins.add(const DefaultInitializerPlugin());
34 | }
35 | return new _BootstrapFileBuilder(
36 | resolver, transform, primaryAssetId, newEntryPointId, errorIfNotFound,
37 | plugins: plugins)
38 | .run();
39 | }
40 |
41 | /// Transformer which removes the mirror-based initialization logic and replaces
42 | /// it with static logic.
43 | class InitializeTransformer extends Transformer {
44 | final Resolvers _resolvers;
45 | final Iterable _entryPointGlobs;
46 | final bool _errorIfNotFound;
47 | final List plugins;
48 |
49 | InitializeTransformer(List entryPoints,
50 | {bool errorIfNotFound: true, this.plugins})
51 | : _entryPointGlobs = entryPoints.map((e) => new Glob(e)),
52 | _errorIfNotFound = errorIfNotFound,
53 | _resolvers = new Resolvers.fromMock(dart_sdk.mockSdkSources);
54 |
55 | factory InitializeTransformer.asPlugin(BarbackSettings settings) =>
56 | new InitializeTransformer(_readFileList(settings, 'entry_points'));
57 |
58 | bool isPrimary(AssetId id) => _entryPointGlobs.any((g) => g.matches(id.path));
59 |
60 | Future apply(Transform transform) {
61 | if (transform.primaryInput.id.path.endsWith('.dart')) {
62 | return _buildBootstrapFile(transform);
63 | } else if (transform.primaryInput.id.path.endsWith('.html')) {
64 | return transform.primaryInput.readAsString().then((html) {
65 | var document = parse(html);
66 | var originalDartFile =
67 | _findMainScript(document, transform.primaryInput.id, transform);
68 | return _buildBootstrapFile(transform, primaryId: originalDartFile)
69 | .then((AssetId newDartFile) {
70 | return _replaceEntryWithBootstrap(transform, document,
71 | transform.primaryInput.id, originalDartFile, newDartFile);
72 | });
73 | });
74 | } else {
75 | transform.logger.warning(
76 | 'Invalid entry point ${transform.primaryInput.id}. Must be either a '
77 | '.dart or .html file.');
78 | }
79 | return new Future.value();
80 | }
81 |
82 | // Returns the AssetId of the newly created bootstrap file.
83 | Future _buildBootstrapFile(Transform transform,
84 | {AssetId primaryId}) {
85 | if (primaryId == null) primaryId = transform.primaryInput.id;
86 | var newEntryPointId = new AssetId(primaryId.package,
87 | '${path.url.withoutExtension(primaryId.path)}.initialize.dart');
88 | return transform.hasInput(newEntryPointId).then((exists) {
89 | if (exists) {
90 | transform.logger
91 | .error('New entry point file $newEntryPointId already exists.');
92 | return null;
93 | }
94 |
95 | return _resolvers.get(transform, [primaryId]).then((resolver) {
96 | transform.addOutput(generateBootstrapFile(
97 | resolver, transform, primaryId, newEntryPointId,
98 | errorIfNotFound: _errorIfNotFound, plugins: plugins));
99 | resolver.release();
100 | return newEntryPointId;
101 | });
102 | });
103 | }
104 |
105 | // Finds the first (and only) dart script on an html page and returns the
106 | // [AssetId] that points to it
107 | AssetId _findMainScript(
108 | dom.Document document, AssetId entryPoint, Transform transform) {
109 | var scripts = _getScripts(document);
110 | if (scripts.length != 1) {
111 | transform.logger.error('Expected exactly one dart script in $entryPoint '
112 | 'but found ${scripts.length}.');
113 | return null;
114 | }
115 |
116 | var src = _getScriptAttribute(scripts[0]);
117 | if (src == null) {
118 | // TODO(jakemac): Support inline scripts,
119 | transform.logger.error('Inline scripts are not supported at this time, '
120 | 'see https://github.com/dart-lang/initialize/issues/20.');
121 | return null;
122 | }
123 |
124 | return uriToAssetId(
125 | entryPoint, src, transform.logger, scripts[0].sourceSpan);
126 | }
127 |
128 | // Replaces script tags pointing to [originalDartFile] with [newDartFile] in
129 | // [entryPoint].
130 | void _replaceEntryWithBootstrap(Transform transform, dom.Document document,
131 | AssetId entryPoint, AssetId originalDartFile, AssetId newDartFile) {
132 | var scripts = _getScripts(document).where((script) {
133 | var assetId = uriToAssetId(entryPoint, _getScriptAttribute(script),
134 | transform.logger, script.sourceSpan);
135 | return assetId == originalDartFile;
136 | }).toList();
137 |
138 | if (scripts.length != 1) {
139 | transform.logger
140 | .error('Expected exactly one script pointing to $originalDartFile in '
141 | '$entryPoint, but found ${scripts.length}.');
142 | return;
143 | }
144 | _setScriptAttribute(
145 | scripts[0],
146 | path.url
147 | .relative(newDartFile.path, from: path.dirname(entryPoint.path)));
148 | transform.addOutput(new Asset.fromString(entryPoint, document.outerHtml));
149 | }
150 |
151 | String _getScriptAttribute(dom.Element element) {
152 | switch (element.localName) {
153 | case 'script':
154 | return element.attributes['src'];
155 | case 'link':
156 | return element.attributes['href'];
157 | default:
158 | throw 'Unrecognized element $element';
159 | }
160 | }
161 |
162 | void _setScriptAttribute(dom.Element element, String path) {
163 | switch (element.localName) {
164 | case 'script':
165 | element.attributes['src'] = path;
166 | break;
167 | case 'link':
168 | element.attributes['href'] = path;
169 | break;
170 | }
171 | }
172 |
173 | List _getScripts(dom.Document document) =>
174 | document.querySelectorAll(
175 | 'script[type="application/dart"], link[rel="x-dart-test"]');
176 | }
177 |
178 | // Class which builds a bootstrap file.
179 | class _BootstrapFileBuilder {
180 | final Resolver _resolver;
181 | final Transform _transform;
182 | final bool _errorIfNotFound;
183 | AssetId _entryPoint;
184 | AssetId _newEntryPoint;
185 |
186 | /// The resolved initialize library.
187 | LibraryElement _initializeLibrary;
188 |
189 | /// The resolved Initializer class from the initialize library.
190 | ClassElement _initializer;
191 |
192 | /// Queue for intialization annotations.
193 | final _initQueue = new Queue();
194 |
195 | /// All the annotations we have seen for each element
196 | final _seenAnnotations = new Map>();
197 |
198 | /// The list of [InitializerPlugin]s to apply. The first plugin which asks to
199 | /// be applied to a given initializer is the only one that will apply.
200 | List _plugins;
201 |
202 | TransformLogger _logger;
203 |
204 | _BootstrapFileBuilder(this._resolver, this._transform, this._entryPoint,
205 | this._newEntryPoint, this._errorIfNotFound,
206 | {List plugins}) {
207 | _logger = _transform.logger;
208 | _initializeLibrary =
209 | _resolver.getLibrary(new AssetId('initialize', 'lib/initialize.dart'));
210 | if (_initializeLibrary != null) {
211 | _initializer = _initializeLibrary.getType('Initializer');
212 | } else if (_errorIfNotFound) {
213 | _logger.warning('Unable to read "package:initialize/initialize.dart". '
214 | 'This file must be imported via $_entryPoint or a transitive '
215 | 'dependency.');
216 | }
217 | _plugins = plugins != null ? plugins : [const DefaultInitializerPlugin()];
218 | }
219 |
220 | /// Creates and returns the new bootstrap file.
221 | Asset run() {
222 | var entryLib = _resolver.getLibrary(_entryPoint);
223 | _readLibraries(entryLib);
224 |
225 | return new Asset.fromString(_newEntryPoint, _buildNewEntryPoint(entryLib));
226 | }
227 |
228 | /// Reads Initializer annotations on this library and all its dependencies in
229 | /// post-order.
230 | void _readLibraries(LibraryElement library, [Set seen]) {
231 | if (seen == null) seen = new Set();
232 | seen.add(library);
233 |
234 | // Visit all our dependencies.
235 | for (var library in _sortedLibraryDependencies(library)) {
236 | // Don't include anything from the sdk.
237 | if (library.isInSdk) continue;
238 | if (seen.contains(library)) continue;
239 | _readLibraries(library, seen);
240 | }
241 |
242 | // Read annotations in this order: library, top level methods, classes.
243 | _readAnnotations(library);
244 | for (var method in _topLevelMethodsOfLibrary(library, seen)) {
245 | _readAnnotations(method);
246 | }
247 | for (var clazz in _classesOfLibrary(library, seen)) {
248 | readSuperClassAnnotations(InterfaceType superClass) {
249 | if (superClass == null) return;
250 | readSuperClassAnnotations(superClass.superclass);
251 | if (_readAnnotations(superClass.element) &&
252 | superClass.element.library != clazz.library) {
253 | _logger.warning(
254 | 'We have detected a cycle in your import graph when running '
255 | 'initializers on ${clazz.name}. This means the super class '
256 | '${superClass.name} has a dependency on this library '
257 | '(possibly transitive).');
258 | }
259 | }
260 |
261 | readSuperClassAnnotations(clazz.supertype);
262 | _readAnnotations(clazz);
263 | }
264 | }
265 |
266 | bool _readAnnotations(Element element) {
267 | var found = false;
268 | // analyzer 0.29 doesn't allow this optimization :
269 | //if (element.metadata.isEmpty) return found;
270 |
271 | var metaNodes;
272 | var node = element.computeNode();
273 | if (node is SimpleIdentifier && node.parent is LibraryIdentifier) {
274 | metaNodes = (node.parent.parent as AnnotatedNode).metadata;
275 | } else if (node is ClassDeclaration || node is FunctionDeclaration) {
276 | metaNodes = (node as AnnotatedNode).metadata;
277 | } else {
278 | return found;
279 | }
280 |
281 | metaNodes.where((Annotation metaNode) {
282 | // First filter out anything that is not a Initializer.
283 | var meta = metaNode.elementAnnotation;
284 | var e = meta.element;
285 | if (e is PropertyAccessorElement) {
286 | // 'as dynamic' is because evaluationResult is a property on an impl class, e.g. one that
287 | // isn't supposed to be used externally.
288 | return _isInitializer(
289 | (e.variable as dynamic).evaluationResult.value.type);
290 | } else if (e is ConstructorElement) {
291 | return _isInitializer(e.returnType);
292 | }
293 | return false;
294 | }).where((Annotation metaNode) {
295 | var meta = metaNode.elementAnnotation;
296 | _seenAnnotations.putIfAbsent(element, () => new Set());
297 | return !_seenAnnotations[element].contains(meta);
298 | }).forEach((Annotation metaNode) {
299 | var meta = metaNode.elementAnnotation;
300 | _seenAnnotations[element].add(meta);
301 | _initQueue.addLast(new InitializerData._(node, metaNode));
302 | found = true;
303 | });
304 | return found;
305 | }
306 |
307 | String _buildNewEntryPoint(LibraryElement entryLib) {
308 | var importsBuffer = new StringBuffer();
309 | var initializersBuffer = new StringBuffer();
310 | var libraryPrefixes = new Map();
311 |
312 | // Import the static_loader, initializer, and original entry point.
313 | importsBuffer
314 | .writeln("import 'package:initialize/src/static_loader.dart';");
315 | importsBuffer.writeln("import 'package:initialize/initialize.dart';");
316 | libraryPrefixes[entryLib] = 'i0';
317 |
318 | initializersBuffer.writeln('initializers.addAll([');
319 | while (_initQueue.isNotEmpty) {
320 | var next = _initQueue.removeFirst();
321 |
322 | libraryPrefixes.putIfAbsent(
323 | next.targetElement.library, () => 'i${libraryPrefixes.length}');
324 | libraryPrefixes.putIfAbsent(next.annotationElement.element.library,
325 | () => 'i${libraryPrefixes.length}');
326 |
327 | // Run the first plugin which asks to be ran and then stop.
328 | var data = new InitializerPluginData(
329 | next, _newEntryPoint, libraryPrefixes, _resolver, _logger);
330 | var plugin = _plugins.firstWhere((p) => p.shouldApply(data), orElse: () {
331 | _logger.error('No InitializerPlugin handled the annotation: '
332 | '${next.annotationElement} on: ${next.targetElement}.');
333 | });
334 | if (plugin == null) continue;
335 |
336 | var text = plugin.apply(data);
337 | if (text != null) initializersBuffer.writeln('$text,');
338 | }
339 | initializersBuffer.writeln(']);');
340 |
341 | libraryPrefixes
342 | .forEach((lib, prefix) => _writeImport(lib, prefix, importsBuffer));
343 |
344 | // TODO(jakemac): copyright and library declaration
345 | return new DartFormatter().format('''
346 | $importsBuffer
347 | main() {
348 | $initializersBuffer
349 | return i0.main();
350 | }
351 | ''');
352 | }
353 |
354 | _writeImport(LibraryElement lib, String prefix, StringBuffer buffer) {
355 | AssetId id = (lib.source as dynamic).assetId;
356 |
357 | if (id.path.startsWith('lib/')) {
358 | var packagePath = id.path.replaceFirst('lib/', '');
359 | buffer.write("import 'package:${id.package}/${packagePath}'");
360 | } else if (id.package != _newEntryPoint.package) {
361 | _logger.error("Can't import `${id}` from `${_newEntryPoint}`");
362 | } else if (path.url.split(id.path)[0] ==
363 | path.url.split(_newEntryPoint.path)[0]) {
364 | var relativePath = path.url
365 | .relative(id.path, from: path.url.dirname(_newEntryPoint.path));
366 | buffer.write("import '${relativePath}'");
367 | } else {
368 | _logger.error("Can't import `${id}` from `${_newEntryPoint}`");
369 | }
370 | buffer.writeln(' as $prefix;');
371 | }
372 |
373 | bool _isInitializer(InterfaceType type) {
374 | // If `_initializer` wasn't found then it was never loaded (even
375 | // transitively), and so no annotations can be initializers.
376 | if (_initializer == null) return false;
377 | if (type == null) return false;
378 | if (type.element.type == _initializer.type) return true;
379 | if (_isInitializer(type.superclass)) return true;
380 | for (var interface in type.interfaces) {
381 | if (_isInitializer(interface)) return true;
382 | }
383 | return false;
384 | }
385 |
386 | /// Retrieves all top-level methods that are visible if you were to import
387 | /// [lib]. This includes exported methods from other libraries too.
388 | List _topLevelMethodsOfLibrary(
389 | LibraryElement library, Set seen) {
390 | var methods = [];
391 |
392 | var orderedExports = new List.from(library.exports)
393 | ..sort((a, b) => a.uriOffset.compareTo(b.uriOffset));
394 | for (var export in orderedExports) {
395 | if (seen.contains(export.exportedLibrary)) continue;
396 | methods.addAll(_topLevelMethodsOfLibrary(export.exportedLibrary, seen));
397 | }
398 |
399 | for (CompilationUnitElement unit in _orderedUnits(library)) {
400 | methods.addAll(new List.from(unit.functions)
401 | ..sort((a, b) => a.nameOffset.compareTo(b.nameOffset)));
402 | }
403 |
404 | return methods;
405 | }
406 |
407 | /// Retrieves all classes that are visible if you were to import [lib]. This
408 | /// includes exported classes from other libraries.
409 | List _classesOfLibrary(
410 | LibraryElement library, Set seen) {
411 | var classes = [];
412 |
413 | var orderedExports = new List.from(library.exports)
414 | ..sort((a, b) => a.uriOffset.compareTo(b.uriOffset));
415 | for (var export in orderedExports) {
416 | if (seen.contains(export.exportedLibrary)) continue;
417 | classes.addAll(_classesOfLibrary(export.exportedLibrary, seen));
418 | }
419 |
420 | for (var unit in _orderedUnits(library)) {
421 | classes.addAll(new List.from(unit.types)
422 | ..sort((a, b) => a.nameOffset.compareTo(b.nameOffset)));
423 | }
424 |
425 | return classes;
426 | }
427 |
428 | List _orderedUnits(LibraryElement library) {
429 | var definingUnit = library.definingCompilationUnit;
430 | // The first item is the source library, remove it for now.
431 | return new List.from(library.units)
432 | ..sort((a, b) {
433 | if (a == definingUnit) return 1;
434 | if (b == definingUnit) return -1;
435 | return a.uri.compareTo(b.uri);
436 | });
437 | }
438 |
439 | Iterable _sortedLibraryDependencies(LibraryElement library) {
440 | // TODO(jakemac): Investigate supporting annotations on part-of directives.
441 | getLibrary(UriReferencedElement element) {
442 | if (element is ImportElement) return element.importedLibrary;
443 | if (element is ExportElement) return element.exportedLibrary;
444 | }
445 |
446 | return (new List.from(library.imports)
447 | ..addAll(library.exports)
448 | ..sort((a, b) => a.nameOffset.compareTo(b.nameOffset)))
449 | .map(getLibrary);
450 | }
451 | }
452 |
453 | /// An [Initializer] annotation and the target of that annotation.
454 | class InitializerData {
455 | /// The target [AstNode] of the annotation.
456 | final AstNode targetNode;
457 |
458 | /// The [Annotation] representing the annotation itself.
459 | final Annotation annotationNode;
460 |
461 | /// The [ElementAnnotation] representing the annotation itself.
462 | ElementAnnotation get annotationElement => annotationNode.elementAnnotation;
463 |
464 | /// The target [Element] of the annotation.
465 | Element get targetElement {
466 | if (targetNode is SimpleIdentifier &&
467 | targetNode.parent is LibraryIdentifier) {
468 | return (targetNode.parent.parent as LibraryDirective).element;
469 | } else if (targetNode is ClassDeclaration ||
470 | targetNode is FunctionDeclaration) {
471 | return (targetNode as dynamic).element;
472 | } else {
473 | return null;
474 | }
475 | }
476 |
477 | InitializerData._(this.targetNode, this.annotationNode);
478 | }
479 |
480 | // Reads a file list from a barback settings configuration field.
481 | _readFileList(BarbackSettings settings, String field) {
482 | var value = settings.configuration[field];
483 | if (value == null) return null;
484 | var files = [];
485 | bool error;
486 | if (value is List) {
487 | files = value;
488 | error = value.any((e) => e is! String);
489 | } else if (value is String) {
490 | files = [value];
491 | error = false;
492 | } else {
493 | error = true;
494 | }
495 | if (error) {
496 | print('Bad value for "$field" in the initialize transformer. '
497 | 'Expected either one String or a list of Strings.');
498 | }
499 | return files;
500 | }
501 |
--------------------------------------------------------------------------------