mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:58:29 +00:00
move polymer.dart into dart svn
R=sigmund@google.com Review URL: https://chromiumcodereview.appspot.com//23224003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26404 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
b56d5196b8
commit
068dec6712
|
@ -8,9 +8,12 @@
|
|||
*/packages/*/*: Skip
|
||||
*/*/packages/*/*: Skip
|
||||
*/*/*/packages/*/*: Skip
|
||||
*/*/*/*/packages/*/*: Skip
|
||||
|
||||
# Skip non-test files ending with "_test".
|
||||
scheduled_test/lib/*: Skip
|
||||
polymer/lib/*: Skip
|
||||
polymer/example/*: Skip
|
||||
|
||||
scheduled_test/test/scheduled_server_test: Pass, Fail, Slow, Crash # Issue 9231, 9582
|
||||
scheduled_test/test/scheduled_process_test: Pass, Slow # Issue 9231
|
||||
|
@ -138,6 +141,7 @@ oauth2/test/credentials_test: Fail, OK # Uses dart:io.
|
|||
oauth2/test/handle_access_token_response_test: Fail, OK # Uses dart:io.
|
||||
observe/test/transform_test: Fail, OK # Uses dart:io.
|
||||
path/test/io_test: Fail, OK # Uses dart:io.
|
||||
polymer/test/*: Fail, OK # Uses dart:io.
|
||||
watcher/test/*: Fail, OK # Uses dart:io.
|
||||
|
||||
scheduled_test/test/descriptor/async_test: Fail # http://dartbug.com/8440
|
||||
|
|
85
pkg/polymer/README.md
Normal file
85
pkg/polymer/README.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
Polymer.dart
|
||||
============
|
||||
|
||||
Polymer is a new type of library for the web, built on top of Web Components,
|
||||
and designed to leverage the evolving web platform on modern browsers.
|
||||
|
||||
Polymer.dart is a Dart port of Polymer created and maintained by the Dart team.
|
||||
The Dart team is collaborating with the Polymer team to ensure that polymer.dart
|
||||
elements and polyfills are fully compatible with Polymer.
|
||||
|
||||
For more information about Polymer, see <http://www.polymer-project.org/>.
|
||||
For more information about Dart, see <http://www.dartlang.org/>.
|
||||
|
||||
Try It Now
|
||||
-----------
|
||||
Add the polymer.dart package to your pubspec.yaml file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
polymer: any
|
||||
```
|
||||
|
||||
Instead of using `any`, we recommend using version ranges to avoid getting your
|
||||
project broken on each release. Using a version range lets you upgrade your
|
||||
package at your own pace. You can find the latest version number at
|
||||
<https://pub.dartlang.org/packages/polymer>.
|
||||
|
||||
|
||||
Learn More
|
||||
----------
|
||||
|
||||
**Note**: these documents are currently out of date.
|
||||
|
||||
* [Read an overview][overview]
|
||||
* [Setup your tools][tools]
|
||||
* [Browse the features][features]
|
||||
* [Dive into the specification][spec]
|
||||
|
||||
See our [TodoMVC][] example by opening up the Dart Editor's Welcome Page and
|
||||
selecting "TodoMVC".
|
||||
|
||||
Running Tests
|
||||
-------------
|
||||
|
||||
Dependencies are installed using the [Pub Package Manager][pub].
|
||||
```bash
|
||||
pub install
|
||||
|
||||
# Run command line tests and automated end-to-end tests. It needs two
|
||||
# executables on your path: `dart` and `content_shell` (see below
|
||||
# for links to download `content_shell`)
|
||||
test/run.sh
|
||||
```
|
||||
Note: to run browser tests you will need to have [content_shell][cs],
|
||||
which can be downloaded prebuilt for [Ubuntu Lucid][cs_lucid],
|
||||
[Windows][cs_win], or [Mac][cs_mac]. You can also build it from the
|
||||
[Dartium and content_shell sources][dartium_src].
|
||||
|
||||
For Linux users all the necessary fonts must be installed see
|
||||
<https://code.google.com/p/chromium/wiki/LayoutTestsLinux>.
|
||||
|
||||
Contacting Us
|
||||
-------------
|
||||
|
||||
Please file issues in our [Issue Tracker][issues] or contact us on the
|
||||
[Dart Web UI mailing list][mailinglist].
|
||||
|
||||
We also have the [Web UI development list][devlist] for discussions about
|
||||
internals of the code, code reviews, etc.
|
||||
|
||||
[wc]: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html
|
||||
[pub]: http://www.dartlang.org/docs/pub-package-manager/
|
||||
[cs]: http://www.chromium.org/developers/testing/webkit-layout-tests
|
||||
[cs_lucid]: http://gsdview.appspot.com/dartium-archive/continuous/drt-lucid64.zip
|
||||
[cs_mac]: http://gsdview.appspot.com/dartium-archive/continuous/drt-mac.zip
|
||||
[cs_win]: http://gsdview.appspot.com/dartium-archive/continuous/drt-win.zip
|
||||
[dartium_src]: http://code.google.com/p/dart/wiki/BuildingDartium
|
||||
[TodoMVC]: http://addyosmani.github.com/todomvc/
|
||||
[issues]: http://dartbug.com/new
|
||||
[mailinglist]: https://groups.google.com/a/dartlang.org/forum/?fromgroups#!forum/web-ui
|
||||
[devlist]: https://groups.google.com/a/dartlang.org/forum/?fromgroups#!forum/web-ui-dev
|
||||
[overview]: http://www.dartlang.org/articles/dart-web-components/
|
||||
[tools]: https://www.dartlang.org/articles/dart-web-components/tools.html
|
||||
[spec]: https://www.dartlang.org/articles/dart-web-components/spec.html
|
||||
[features]: https://www.dartlang.org/articles/dart-web-components/summary.html
|
8
pkg/polymer/bin/dwc.dart
Executable file
8
pkg/polymer/bin/dwc.dart
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env dart
|
||||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:polymer/dwc.dart' as dwc;
|
||||
|
||||
void main() => dwc.main();
|
17
pkg/polymer/build.dart
Executable file
17
pkg/polymer/build.dart
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env dart
|
||||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Build logic that lets the Dart editor build examples in the background. */
|
||||
library build;
|
||||
import 'package:polymer/component_build.dart';
|
||||
import 'dart:io';
|
||||
|
||||
void main() {
|
||||
var args = new Options().arguments.toList()..addAll(['--', '--deploy']);
|
||||
build(args, [
|
||||
'example/component/news/web/index.html',
|
||||
'example/scoped_style/index.html',
|
||||
'../../samples/third_party/todomvc/web/index.html']);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
Content-Type: text/plain
|
||||
<html><head><style>template { display: none; }</style>
|
||||
<title>Simple Web Components Example</title>
|
||||
|
||||
<script src="../../packages/polymer/testing/testing.js"></script>
|
||||
<style>template,
|
||||
thead[template],
|
||||
tbody[template],
|
||||
tfoot[template],
|
||||
th[template],
|
||||
tr[template],
|
||||
td[template],
|
||||
caption[template],
|
||||
colgroup[template],
|
||||
col[template],
|
||||
option[template] {
|
||||
display: none;
|
||||
}</style></head>
|
||||
<body><polymer-element name="x-news" extends="ul">
|
||||
<template>
|
||||
<style scoped="">
|
||||
div.breaking {
|
||||
color: Red;
|
||||
font-size: 20px;
|
||||
border: 1px dashed Purple;
|
||||
}
|
||||
div.other {
|
||||
padding: 2px 0 0 0;
|
||||
border: 1px solid Cyan;
|
||||
}
|
||||
</style>
|
||||
<div class="breaking">
|
||||
<h2>Breaking Stories</h2>
|
||||
<ul>
|
||||
<content select=".breaking"></content>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="other">
|
||||
<h2>Other News</h2>
|
||||
<ul>
|
||||
<content></content>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</polymer-element>
|
||||
|
||||
<h1>Simple Web Components Example</h1>
|
||||
<ul is="x-news"><shadow-root>
|
||||
<style scoped="">
|
||||
div.breaking {
|
||||
color: Red;
|
||||
font-size: 20px;
|
||||
border: 1px dashed Purple;
|
||||
}
|
||||
div.other {
|
||||
padding: 2px 0 0 0;
|
||||
border: 1px solid Cyan;
|
||||
}
|
||||
</style>
|
||||
<div class="breaking">
|
||||
<h2>Breaking Stories</h2>
|
||||
<ul>
|
||||
<content select=".breaking"></content>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="other">
|
||||
<h2>Other News</h2>
|
||||
<ul>
|
||||
<content></content>
|
||||
</ul>
|
||||
</div>
|
||||
</shadow-root>
|
||||
<li><a href="//example.com/stories/1">A story</a></li>
|
||||
<li><a href="//example.com/stories/2">Another story</a></li>
|
||||
<li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
|
||||
<li><a href="//example.com/stories/4">Yet another story</a></li>
|
||||
<li><a href="//example.com/stories/4">Awesome story</a></li>
|
||||
<li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" src="packages/shadow_dom/shadow_dom.debug.js"></script>
|
||||
<script type="application/dart" src="news_index_test.html_bootstrap.dart"></script></body></html>
|
29
pkg/polymer/example/component/news/test/news_index_test.html
Normal file
29
pkg/polymer/example/component/news/test/news_index_test.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
This example is from
|
||||
https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Simple Web Components Example</title>
|
||||
<link rel="import" href="../web/news-component.html">
|
||||
<script src="packages/polymer/testing/testing.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Simple Web Components Example</h1>
|
||||
<ul is="x-news">
|
||||
<li><a href="//example.com/stories/1">A story</a></li>
|
||||
<li><a href="//example.com/stories/2">Another story</a></li>
|
||||
<li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
|
||||
<li><a href="//example.com/stories/4">Yet another story</a></li>
|
||||
<li><a href="//example.com/stories/4">Awesome story</a></li>
|
||||
<li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
|
||||
</ul>
|
||||
<script type="application/dart">
|
||||
import 'dart:html';
|
||||
main() {
|
||||
window.postMessage('done', '*');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
14
pkg/polymer/example/component/news/test/test.dart
Executable file
14
pkg/polymer/example/component/news/test/test.dart
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env dart
|
||||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:polymer/testing/content_shell_test.dart';
|
||||
import 'package:unittest/compact_vm_config.dart';
|
||||
|
||||
void main() {
|
||||
useCompactVMConfiguration();
|
||||
// Base directory, input, expected, output:
|
||||
renderTests('..', '.', 'expected', 'out');
|
||||
}
|
24
pkg/polymer/example/component/news/web/index.html
Normal file
24
pkg/polymer/example/component/news/web/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
This example is from
|
||||
https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Simple Web Components Example</title>
|
||||
<link rel="import" href="news-component.html">
|
||||
<script src='packages/polymer/boot.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Simple Web Components Example</h1>
|
||||
<ul is="x-news">
|
||||
<li><a href="//example.com/stories/1">A story</a></li>
|
||||
<li><a href="//example.com/stories/2">Another story</a></li>
|
||||
<li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
|
||||
<li><a href="//example.com/stories/4">Yet another story</a></li>
|
||||
<li><a href="//example.com/stories/4">Awesome story</a></li>
|
||||
<li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
|
||||
</ul>
|
||||
<script type="application/dart">main() {}</script>
|
||||
</body>
|
||||
</html>
|
49
pkg/polymer/example/component/news/web/news-component.html
Normal file
49
pkg/polymer/example/component/news/web/news-component.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
This example is from
|
||||
https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>News Component</title>
|
||||
</head>
|
||||
<body>
|
||||
<polymer-element name="x-news" extends="ul">
|
||||
<template>
|
||||
<style scoped>
|
||||
div.breaking {
|
||||
color: Red;
|
||||
font-size: 20px;
|
||||
border: 1px dashed Purple;
|
||||
}
|
||||
div.other {
|
||||
padding: 2px 0 0 0;
|
||||
border: 1px solid Cyan;
|
||||
}
|
||||
</style>
|
||||
<div class="breaking">
|
||||
<h2>Breaking Stories</h2>
|
||||
<ul>
|
||||
<content select=".breaking"></content>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="other">
|
||||
<h2>Other News</h2>
|
||||
<ul>
|
||||
<content></content>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
|
||||
class XNews extends PolymerElement {}
|
||||
|
||||
@polymerInitMethod
|
||||
_init() {
|
||||
registerPolymerElement('x-news', () => new XNews());
|
||||
}
|
||||
</script>
|
||||
</polymer-element>
|
||||
</body>
|
||||
</html>
|
16
pkg/polymer/example/scoped_style/index.html
Normal file
16
pkg/polymer/example/scoped_style/index.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Simple CSS Test</title>
|
||||
<link rel="import" href="my_test.html">
|
||||
<script src='packages/polymer/boot.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
p { color: black;}
|
||||
</style>
|
||||
<p>outside of element, should be black</p>
|
||||
|
||||
<my-test></my-test>
|
||||
</body>
|
||||
</html>
|
22
pkg/polymer/example/scoped_style/my_test.html
Normal file
22
pkg/polymer/example/scoped_style/my_test.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Compopnent</title>
|
||||
</head>
|
||||
<body>
|
||||
<polymer-element name="my-test" extends="div">
|
||||
<template>
|
||||
<style>
|
||||
p { color: red;}
|
||||
</style>
|
||||
<p>Inside element, should be red</p>
|
||||
</template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
|
||||
@CustomTag('my-test')
|
||||
class MyTest extends PolymerElement {}
|
||||
</script>
|
||||
</polymer-element>
|
||||
</body>
|
||||
</html>
|
188
pkg/polymer/lib/boot.js
Normal file
188
pkg/polymer/lib/boot.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// This script dynamically prepares a set of files to run polymer.dart. It uses
|
||||
// the html_import polyfill to search for all imported files, then
|
||||
// it inlines all <polymer-element> definitions on the top-level page (needed by
|
||||
// registerPolymerElement), and it removes script tags that appear inside
|
||||
// those tags. It finally rewrites the main entrypoint to call an initialization
|
||||
// function on each of the declared <polymer-elements>.
|
||||
//
|
||||
// This script is needed only when running polymer.dart in Dartium. It should be
|
||||
// removed by the polymer deployment commands.
|
||||
|
||||
// As an example, given an input of this form:
|
||||
// <polymer-element name="c1">
|
||||
// <template></template>
|
||||
// <script type="application/dart" src="url0.dart"></script>
|
||||
// </polymer-element>
|
||||
// <element name="c2">
|
||||
// <template></template>
|
||||
// <script type="application/dart">main() => { print('body2'); }</script>
|
||||
// </element>
|
||||
// <c1></c1>
|
||||
// <c2></c2>
|
||||
// <script type="application/dart" src="url2.dart"></script>
|
||||
// <script src="packages/polymer/boot.js"></script>
|
||||
//
|
||||
// This script will simplifies the page as follows:
|
||||
// <polymer-element name="c1">
|
||||
// <template></template>
|
||||
// </polymer-element>
|
||||
// <polymer-element name="c2">
|
||||
// <template></template>
|
||||
// </polymer-element>
|
||||
// <c1></c1>
|
||||
// <c2></c2>
|
||||
// <script type="application/dart">
|
||||
// import 'url0.dart' as i0;
|
||||
// import "data:application/dart;base64,CiAgICBtYWluKCkgewogICAgICBwcmludCgnYm9keTInKTsKICAgIH0KICAgIA==" as i1;
|
||||
// import 'url2.dart' as i2;
|
||||
// ...
|
||||
// main() {
|
||||
// // code that checks which libraries have a 'main' and invokes them.
|
||||
// // practically equivalent to: i0._init(); i1._init(); i2.main();
|
||||
// }
|
||||
// </script>
|
||||
|
||||
|
||||
(function() {
|
||||
// Only run in Dartium.
|
||||
if (!navigator.webkitStartDart) {
|
||||
// TODO(sigmund): rephrase when we split build.dart in two: analysis vs
|
||||
// deploy pieces.
|
||||
console.warn('boot.js only works in Dartium. Run the build.dart' +
|
||||
' tool to compile a depolyable JavaScript version')
|
||||
return;
|
||||
}
|
||||
document.write(
|
||||
'<script src="packages/html_import/html_import.min.js"></script>');
|
||||
|
||||
// Whether [node] is under `<body>` or `<head>` and not nested in an
|
||||
// `<element>` or `<polymer-element>` tag.
|
||||
function isTopLevel(node) {
|
||||
var parent = node.parentNode;
|
||||
if (parent == null || parent == document.body || parent == document.head) {
|
||||
return true;
|
||||
}
|
||||
if (parent.localName && (
|
||||
parent.localName == 'element' ||
|
||||
parent.localName == 'polymer-element')) {
|
||||
return false;
|
||||
}
|
||||
return isTopLevel(parent);
|
||||
}
|
||||
|
||||
// Extract a Dart import URL from a script tag, which is the 'src' attribute
|
||||
// of the script tag, or a data-url with the script contents for inlined code.
|
||||
function getScriptUrl(script) {
|
||||
var url = script.src;
|
||||
if (url) {
|
||||
// Normalize package: urls
|
||||
var index = url.indexOf('packages/');
|
||||
if (index == 0 || (index > 0 && url[index - 1] == '/')) {
|
||||
url = "package:" + url.slice(index + 9);
|
||||
}
|
||||
|
||||
// TODO(sigmund): remove the timestamp. We added this to work around
|
||||
// a caching bug in dartium (see http://dartbug.com/12074). Note this
|
||||
// doesn't fix caching problems with other libraries imported further in,
|
||||
// and it can also introduce canonicalization problems if the files under
|
||||
// these urls are being imported from other libraries.
|
||||
var time = new Date().getTime();
|
||||
return url + '?' + time;
|
||||
} else {
|
||||
// TODO(sigmund): investigate how to eliminate the warning in Dartium
|
||||
// (changing to text/javascript hides the warning, but seems wrong).
|
||||
return "data:application/dart;base64," + window.btoa(script.textContent);
|
||||
}
|
||||
}
|
||||
|
||||
// Moves <polymer-elements> from imported documents into the top-level page.
|
||||
function inlinePolymerElements(content, ref, seen) {
|
||||
if (!seen) seen = {};
|
||||
var links = content.querySelectorAll('link[rel="import"]');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var link = links[i].import;
|
||||
if (seen[link.href]) continue;
|
||||
seen[link.href] = link;
|
||||
inlinePolymerElements(link.content, ref, seen);
|
||||
}
|
||||
|
||||
if (content != document) { // no need to do anything for the top-level page
|
||||
var elements = content.querySelectorAll('polymer-element');
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
document.body.insertBefore(elements[i], ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a Dart program that imports [urls] and [mainUrl] and invokes the
|
||||
// _init methods of each library in urls (if present) followed by the main
|
||||
// method of [mainUrl].
|
||||
function createMain(urls, mainUrl) {
|
||||
var imports = Array(urls.length + 2);
|
||||
for (var i = 0; i < urls.length; ++i) {
|
||||
imports[i] = 'import "' + urls[i] + '" as i' + i + ';';
|
||||
}
|
||||
imports[urls.length] = 'import "package:polymer/polymer.dart" as polymer;';
|
||||
imports[urls.length + 1 ] = 'import "' + mainUrl + '" as userMain;';
|
||||
var firstArg = urls.length == 0 ? '[]' :
|
||||
('[\n "' + urls.join('",\n "') + '"\n ]');
|
||||
return (imports.join('\n') +
|
||||
'\n\nmain() {\n' +
|
||||
' polymer.initPolymer(' + firstArg + ', userMain.main);\n' +
|
||||
'}\n');
|
||||
}
|
||||
|
||||
// Finds all top-level <script> tags, and <script> tags in custom elements
|
||||
// and merges them into a single entrypoint.
|
||||
function mergeScripts() {
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
var length = scripts.length;
|
||||
|
||||
var dartScripts = []
|
||||
var urls = [];
|
||||
|
||||
// Collect the information we need to replace the script tags
|
||||
for (var i = 0; i < length; ++i) {
|
||||
var script = scripts[i];
|
||||
if (script.type == "application/dart") {
|
||||
dartScripts.push(script);
|
||||
if (isTopLevel(script)) continue;
|
||||
urls.push(getScriptUrl(script));
|
||||
}
|
||||
}
|
||||
|
||||
// Removes all the original script tags under elements, and replace
|
||||
// top-level script tags so we first call each element's _init.
|
||||
for (var i = 0; i < dartScripts.length; ++i) {
|
||||
var script = dartScripts[i];
|
||||
if (isTopLevel(script)) {
|
||||
var newScript = document.createElement('script');
|
||||
newScript.type = "application/dart";
|
||||
newScript.textContent = createMain(urls, getScriptUrl(script));
|
||||
script.parentNode.replaceChild(newScript, script);
|
||||
} else {
|
||||
script.parentNode.removeChild(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var alreadyRan = false;
|
||||
window.addEventListener('HTMLImportsLoaded', function (e) {
|
||||
if (alreadyRan) {
|
||||
console.warn('HTMLImportsLoaded fired again.');
|
||||
return;
|
||||
}
|
||||
alreadyRan = true;
|
||||
var ref = document.body.children[0];
|
||||
inlinePolymerElements(document, ref);
|
||||
mergeScripts();
|
||||
if (!navigator.webkitStartDart()) {
|
||||
document.body.innerHTML = 'This build has expired. Please download a ' +
|
||||
'new Dartium at http://www.dartlang.org/dartium/index.html';
|
||||
}
|
||||
});
|
||||
})();
|
166
pkg/polymer/lib/component_build.dart
Normal file
166
pkg/polymer/lib/component_build.dart
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Common logic to make it easy to create a `build.dart` for your project.
|
||||
*
|
||||
* The `build.dart` script is invoked automatically by the Editor whenever a
|
||||
* file in the project changes. It must be placed in the root of a project
|
||||
* (where pubspec.yaml lives) and should be named exactly 'build.dart'.
|
||||
*
|
||||
* A common `build.dart` would look as follows:
|
||||
*
|
||||
* import 'dart:io';
|
||||
* import 'package:polymer/component_build.dart';
|
||||
*
|
||||
* main() => build(new Options().arguments, ['web/index.html']);
|
||||
*/
|
||||
library build_utils;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:json' as json;
|
||||
import 'package:args/args.dart';
|
||||
|
||||
import 'dwc.dart' as dwc;
|
||||
import 'src/utils.dart';
|
||||
import 'src/compiler_options.dart';
|
||||
|
||||
/**
|
||||
* Set up 'build.dart' to compile with the dart web components compiler every
|
||||
* [entryPoints] listed. On clean commands, the directory where [entryPoints]
|
||||
* live will be scanned for generated files to delete them.
|
||||
*/
|
||||
// TODO(jmesserly): we need a better way to automatically detect input files
|
||||
Future<List<dwc.CompilerResult>> build(List<String> arguments,
|
||||
List<String> entryPoints,
|
||||
{bool printTime: true, bool shouldPrint: true}) {
|
||||
bool useColors = stdioType(stdout) == StdioType.TERMINAL;
|
||||
return asyncTime('Total time', () {
|
||||
var args = _processArgs(arguments);
|
||||
var tasks = new FutureGroup();
|
||||
var lastTask = new Future.value(null);
|
||||
tasks.add(lastTask);
|
||||
|
||||
var changedFiles = args["changed"];
|
||||
var removedFiles = args["removed"];
|
||||
var cleanBuild = args["clean"];
|
||||
var machineFormat = args["machine"];
|
||||
// Also trigger a full build if the script was run from the command line
|
||||
// with no arguments
|
||||
var fullBuild = args["full"] || (!machineFormat && changedFiles.isEmpty &&
|
||||
removedFiles.isEmpty && !cleanBuild);
|
||||
|
||||
var options = CompilerOptions.parse(args.rest, checkUsage: false);
|
||||
|
||||
// [outputOnlyDirs] contains directories known to only have output files.
|
||||
// When outputDir is not specified, we create a new directory which only
|
||||
// contains output files. If options.outputDir is specified, we don't know
|
||||
// if the output directory may also have input files. In which case,
|
||||
// [_handleCleanCommand] and [_isInputFile] are more conservative.
|
||||
//
|
||||
// TODO(sigmund): get rid of this. Instead, use the compiler to understand
|
||||
// which files are input or output files.
|
||||
var outputOnlyDirs = options.outputDir == null ? []
|
||||
: entryPoints.map((e) => _outDir(e)).toList();
|
||||
|
||||
if (cleanBuild) {
|
||||
_handleCleanCommand(outputOnlyDirs);
|
||||
} else if (fullBuild
|
||||
|| changedFiles.any((f) => _isInputFile(f, outputOnlyDirs))
|
||||
|| removedFiles.any((f) => _isInputFile(f, outputOnlyDirs))) {
|
||||
for (var file in entryPoints) {
|
||||
var dwcArgs = new List.from(args.rest);
|
||||
if (machineFormat) dwcArgs.add('--json_format');
|
||||
if (!useColors) dwcArgs.add('--no-colors');
|
||||
// We'll set 'out/' as the out folder, unless an output directory was
|
||||
// already specified in the command line.
|
||||
if (options.outputDir == null) dwcArgs.addAll(['-o', _outDir(file)]);
|
||||
dwcArgs.add(file);
|
||||
// Chain tasks to that we run one at a time.
|
||||
lastTask = lastTask.then((_) => dwc.run(dwcArgs, printTime: printTime,
|
||||
shouldPrint: shouldPrint));
|
||||
if (machineFormat) {
|
||||
lastTask = lastTask.then((res) {
|
||||
appendMessage(Map jsonMessage) {
|
||||
var message = json.stringify([jsonMessage]);
|
||||
if (shouldPrint) print(message);
|
||||
res.messages.add(message);
|
||||
}
|
||||
// Print for the Editor messages about mappings and generated files
|
||||
res.outputs.forEach((out, input) {
|
||||
if (out.endsWith(".html") && input != null) {
|
||||
appendMessage({
|
||||
"method": "mapping",
|
||||
"params": {"from": input, "to": out},
|
||||
});
|
||||
}
|
||||
appendMessage({"method": "generated", "params": {"file": out}});
|
||||
});
|
||||
return res;
|
||||
});
|
||||
}
|
||||
tasks.add(lastTask);
|
||||
}
|
||||
}
|
||||
return tasks.future.then((r) => r.where((v) => v != null));
|
||||
}, printTime: printTime, useColors: useColors);
|
||||
}
|
||||
|
||||
String _outDir(String file) => path.join(path.dirname(file), 'out');
|
||||
|
||||
/** Tell whether [filePath] is a generated file. */
|
||||
bool _isGeneratedFile(String filePath, List<String> outputOnlyDirs) {
|
||||
var dirPrefix = path.dirname(filePath);
|
||||
for (var outDir in outputOnlyDirs) {
|
||||
if (dirPrefix.startsWith(outDir)) return true;
|
||||
}
|
||||
return path.basename(filePath).startsWith('_');
|
||||
}
|
||||
|
||||
/** Tell whether [filePath] is an input file. */
|
||||
bool _isInputFile(String filePath, List<String> outputOnlyDirs) {
|
||||
var ext = path.extension(filePath);
|
||||
return (ext == '.dart' || ext == '.html') &&
|
||||
!_isGeneratedFile(filePath, outputOnlyDirs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all generated files. Currently we only delete files under directories
|
||||
* that are known to contain only generated code.
|
||||
*/
|
||||
void _handleCleanCommand(List<String> outputOnlyDirs) {
|
||||
for (var dirPath in outputOnlyDirs) {
|
||||
var dir = new Directory(dirPath);
|
||||
if (!dir.existsSync()) continue;
|
||||
for (var f in dir.listSync(recursive: false)) {
|
||||
if (f is File && _isGeneratedFile(f.path, outputOnlyDirs)) f.deleteSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Process the command-line arguments. */
|
||||
ArgResults _processArgs(List<String> arguments) {
|
||||
var parser = new ArgParser()
|
||||
..addOption("changed", help: "the file has changed since the last build",
|
||||
allowMultiple: true)
|
||||
..addOption("removed", help: "the file was removed since the last build",
|
||||
allowMultiple: true)
|
||||
..addFlag("clean", negatable: false, help: "remove any build artifacts")
|
||||
..addFlag("full", negatable: false, help: "perform a full build")
|
||||
..addFlag("machine", negatable: false,
|
||||
help: "produce warnings in a machine parseable format")
|
||||
..addFlag("help", abbr: 'h',
|
||||
negatable: false, help: "displays this help and exit");
|
||||
var args = parser.parse(arguments);
|
||||
if (args["help"]) {
|
||||
print('A build script that invokes the web-ui compiler (dwc).');
|
||||
print('Usage: dart build.dart [options] [-- [dwc-options]]');
|
||||
print('\nThese are valid options expected by build.dart:');
|
||||
print(parser.getUsage());
|
||||
print('\nThese are valid options expected by dwc:');
|
||||
dwc.run(['-h']).then((_) => exit(0));
|
||||
}
|
||||
return args;
|
||||
}
|
198
pkg/polymer/lib/dwc.dart
Executable file
198
pkg/polymer/lib/dwc.dart
Executable file
|
@ -0,0 +1,198 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** The entry point to the compiler. Used to implement `bin/dwc.dart`. */
|
||||
library dwc;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:logging/logging.dart' show Level;
|
||||
|
||||
import 'src/compiler.dart';
|
||||
import 'src/file_system.dart';
|
||||
import 'src/file_system/console.dart';
|
||||
import 'src/files.dart';
|
||||
import 'src/messages.dart';
|
||||
import 'src/compiler_options.dart';
|
||||
import 'src/utils.dart';
|
||||
|
||||
FileSystem _fileSystem;
|
||||
|
||||
void main() {
|
||||
run(new Options().arguments).then((result) {
|
||||
exit(result.success ? 0 : 1);
|
||||
});
|
||||
}
|
||||
|
||||
/** Contains the result of a compiler run. */
|
||||
class CompilerResult {
|
||||
final bool success;
|
||||
|
||||
/** Map of output path to source, if there is one */
|
||||
final Map<String, String> outputs;
|
||||
|
||||
/** List of files read during compilation */
|
||||
final List<String> inputs;
|
||||
|
||||
final List<String> messages;
|
||||
String bootstrapFile;
|
||||
|
||||
CompilerResult([this.success = true,
|
||||
this.outputs,
|
||||
this.inputs,
|
||||
this.messages = const [],
|
||||
this.bootstrapFile]);
|
||||
|
||||
factory CompilerResult._(bool success,
|
||||
List<String> messages, List<OutputFile> outputs, List<SourceFile> files) {
|
||||
var file;
|
||||
var outs = new Map<String, String>();
|
||||
for (var out in outputs) {
|
||||
if (path.basename(out.path).endsWith('_bootstrap.dart')) {
|
||||
file = out.path;
|
||||
}
|
||||
outs[out.path] = out.source;
|
||||
}
|
||||
var inputs = files.map((f) => f.path).toList();
|
||||
return new CompilerResult(success, outs, inputs, messages, file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the web components compiler with the command-line options in [args].
|
||||
* See [CompilerOptions] for the definition of valid arguments.
|
||||
*/
|
||||
// TODO(jmesserly): fix this to return a proper exit code
|
||||
// TODO(justinfagnani): return messages in the result
|
||||
Future<CompilerResult> run(List<String> args, {bool printTime,
|
||||
bool shouldPrint: true}) {
|
||||
var options = CompilerOptions.parse(args);
|
||||
if (options == null) return new Future.value(new CompilerResult());
|
||||
if (printTime == null) printTime = options.verbose;
|
||||
|
||||
_fileSystem = new ConsoleFileSystem();
|
||||
var messages = new Messages(options: options, shouldPrint: shouldPrint);
|
||||
|
||||
return asyncTime('Total time spent on ${options.inputFile}', () {
|
||||
var compiler = new Compiler(_fileSystem, options, messages);
|
||||
var res;
|
||||
return compiler.run()
|
||||
.then((_) {
|
||||
var success = messages.messages.every((m) => m.level != Level.SEVERE);
|
||||
var msgs = options.jsonFormat
|
||||
? messages.messages.map((m) => m.toJson())
|
||||
: messages.messages.map((m) => m.toString());
|
||||
res = new CompilerResult._(success, msgs.toList(),
|
||||
compiler.output, compiler.files);
|
||||
})
|
||||
.then((_) => _symlinkPubPackages(res, options, messages))
|
||||
.then((_) => _emitFiles(compiler.output, options.clean))
|
||||
.then((_) => res);
|
||||
}, printTime: printTime, useColors: options.useColors);
|
||||
}
|
||||
|
||||
Future _emitFiles(List<OutputFile> outputs, bool clean) {
|
||||
outputs.forEach((f) => _writeFile(f.path, f.contents, clean));
|
||||
return _fileSystem.flush();
|
||||
}
|
||||
|
||||
void _writeFile(String filePath, String contents, bool clean) {
|
||||
if (clean) {
|
||||
File fileOut = new File(filePath);
|
||||
if (fileOut.existsSync()) {
|
||||
fileOut.deleteSync();
|
||||
}
|
||||
} else {
|
||||
_createIfNeeded(path.dirname(filePath));
|
||||
_fileSystem.writeString(filePath, contents);
|
||||
}
|
||||
}
|
||||
|
||||
void _createIfNeeded(String outdir) {
|
||||
if (outdir.isEmpty) return;
|
||||
var outDirectory = new Directory(outdir);
|
||||
if (!outDirectory.existsSync()) {
|
||||
_createIfNeeded(path.dirname(outdir));
|
||||
outDirectory.createSync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a symlink to the pub packages directory in the output location. The
|
||||
* returned future completes when the symlink was created (or immediately if it
|
||||
* already exists).
|
||||
*/
|
||||
Future _symlinkPubPackages(CompilerResult result, CompilerOptions options,
|
||||
Messages messages) {
|
||||
if (options.outputDir == null || result.bootstrapFile == null
|
||||
|| options.packageRoot != null) {
|
||||
// We don't need to copy the packages directory if the output was generated
|
||||
// in-place where the input lives, if the compiler was called without an
|
||||
// entry-point file, or if the compiler was called with a package-root
|
||||
// option.
|
||||
return new Future.value(null);
|
||||
}
|
||||
|
||||
var linkDir = path.dirname(result.bootstrapFile);
|
||||
_createIfNeeded(linkDir);
|
||||
var linkPath = path.join(linkDir, 'packages');
|
||||
// A resolved symlink works like a directory
|
||||
// TODO(sigmund): replace this with something smarter once we have good
|
||||
// symlink support in dart:io
|
||||
if (new Directory(linkPath).existsSync()) {
|
||||
// Packages directory already exists.
|
||||
return new Future.value(null);
|
||||
}
|
||||
|
||||
// A broken symlink works like a file
|
||||
var toFile = new File(linkPath);
|
||||
if (toFile.existsSync()) {
|
||||
toFile.deleteSync();
|
||||
}
|
||||
|
||||
var targetPath = path.join(path.dirname(options.inputFile), 'packages');
|
||||
// [fullPathSync] will canonicalize the path, resolving any symlinks.
|
||||
// TODO(sigmund): once it's possible in dart:io, we just want to use a full
|
||||
// path, but not necessarily resolve symlinks.
|
||||
var target = new File(targetPath).fullPathSync().toString();
|
||||
return createSymlink(target, linkPath, messages: messages);
|
||||
}
|
||||
|
||||
|
||||
// TODO(jmesserly): this code was taken from Pub's io library.
|
||||
// Added error handling and don't return the file result, to match the code
|
||||
// we had previously. Also "target" and "link" only accept strings. And inlined
|
||||
// the relevant parts of runProcess. Note that it uses "cmd" to get the path
|
||||
// on Windows.
|
||||
/**
|
||||
* Creates a new symlink that creates an alias of [target] at [link], both of
|
||||
* which can be a [String], [File], or [Directory]. Returns a [Future] which
|
||||
* completes to the symlink file (i.e. [link]).
|
||||
*/
|
||||
Future createSymlink(String target, String link, {Messages messages: null}) {
|
||||
messages = messages == null? new Messages.silent() : messages;
|
||||
var command = 'ln';
|
||||
var args = ['-s', target, link];
|
||||
|
||||
if (Platform.operatingSystem == 'windows') {
|
||||
// Call mklink on Windows to create an NTFS junction point. Only works on
|
||||
// Vista or later. (Junction points are available earlier, but the "mklink"
|
||||
// command is not.) I'm using a junction point (/j) here instead of a soft
|
||||
// link (/d) because the latter requires some privilege shenanigans that
|
||||
// I'm not sure how to specify from the command line.
|
||||
command = 'cmd';
|
||||
args = ['/c', 'mklink', '/j', link, target];
|
||||
}
|
||||
|
||||
return Process.run(command, args).then((result) {
|
||||
if (result.exitCode != 0) {
|
||||
var details = 'subprocess stdout:\n${result.stdout}\n'
|
||||
'subprocess stderr:\n${result.stderr}';
|
||||
messages.error(
|
||||
'unable to create symlink\n target: $target\n link:$link\n$details',
|
||||
null);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
124
pkg/polymer/lib/observe.dart
Normal file
124
pkg/polymer/lib/observe.dart
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Helpers for observable objects.
|
||||
* Intended for use with `package:observe`.
|
||||
*/
|
||||
library polymer.observe;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:observe/observe.dart';
|
||||
|
||||
const _VALUE = const Symbol('value');
|
||||
|
||||
/**
|
||||
* Forwards an observable property from one object to another. For example:
|
||||
*
|
||||
* class MyModel extends ObservableBase {
|
||||
* StreamSubscription _sub;
|
||||
* MyOtherModel _otherModel;
|
||||
*
|
||||
* MyModel() {
|
||||
* ...
|
||||
* _sub = bindProperty(_otherModel, const Symbol('value'),
|
||||
* () => notifyProperty(this, const Symbol('prop'));
|
||||
* }
|
||||
*
|
||||
* String get prop => _otherModel.value;
|
||||
* set prop(String value) { _otherModel.value = value; }
|
||||
* }
|
||||
*
|
||||
* See also [notifyProperty].
|
||||
*/
|
||||
StreamSubscription bindProperty(Observable source, Symbol sourceName,
|
||||
void callback()) {
|
||||
return source.changes.listen((records) {
|
||||
for (var record in records) {
|
||||
if (record.changes(sourceName)) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the property change. Shorthand for:
|
||||
*
|
||||
* target.notifyChange(new PropertyChangeRecord(targetName));
|
||||
*/
|
||||
void notifyProperty(Observable target, Symbol targetName) {
|
||||
target.notifyChange(new PropertyChangeRecord(targetName));
|
||||
}
|
||||
|
||||
|
||||
// Inspired by ArrayReduction at:
|
||||
// https://raw.github.com/rafaelw/ChangeSummary/master/util/array_reduction.js
|
||||
// The main difference is we support anything on the rich Dart Iterable API.
|
||||
|
||||
/**
|
||||
* Observes a path starting from each item in the list.
|
||||
*/
|
||||
class ListPathObserver<E, P> extends ChangeNotifierBase {
|
||||
final ObservableList<E> list;
|
||||
final String _itemPath;
|
||||
final List<PathObserver> _observers = <PathObserver>[];
|
||||
final List<StreamSubscription> _subs = <StreamSubscription>[];
|
||||
StreamSubscription _sub;
|
||||
bool _scheduled = false;
|
||||
Iterable<P> _value;
|
||||
|
||||
ListPathObserver(this.list, String path)
|
||||
: _itemPath = path {
|
||||
|
||||
_sub = list.changes.listen((records) {
|
||||
for (var record in records) {
|
||||
if (record is ListChangeRecord) {
|
||||
_observeItems(record.addedCount - record.removedCount);
|
||||
}
|
||||
}
|
||||
_scheduleReduce(null);
|
||||
});
|
||||
|
||||
_observeItems(list.length);
|
||||
_reduce();
|
||||
}
|
||||
|
||||
Iterable<P> get value => _value;
|
||||
|
||||
void dispose() {
|
||||
if (_sub != null) _sub.cancel();
|
||||
_subs.forEach((s) => s.cancel());
|
||||
_subs.clear();
|
||||
}
|
||||
|
||||
void _reduce() {
|
||||
_scheduled = false;
|
||||
_value = _observers.map((o) => o.value);
|
||||
notifyChange(new PropertyChangeRecord(_VALUE));
|
||||
}
|
||||
|
||||
void _scheduleReduce(_) {
|
||||
if (_scheduled) return;
|
||||
_scheduled = true;
|
||||
runAsync(_reduce);
|
||||
}
|
||||
|
||||
void _observeItems(int lengthAdjust) {
|
||||
if (lengthAdjust > 0) {
|
||||
for (int i = 0; i < lengthAdjust; i++) {
|
||||
int len = _observers.length;
|
||||
var pathObs = new PathObserver(list, '$len.$_itemPath');
|
||||
_subs.add(pathObs.changes.listen(_scheduleReduce));
|
||||
_observers.add(pathObs);
|
||||
}
|
||||
} else if (lengthAdjust < 0) {
|
||||
for (int i = 0; i < -lengthAdjust; i++) {
|
||||
_subs.removeLast().cancel();
|
||||
}
|
||||
int len = _observers.length;
|
||||
_observers.removeRange(len + lengthAdjust, len);
|
||||
}
|
||||
}
|
||||
}
|
63
pkg/polymer/lib/observe_html.dart
Normal file
63
pkg/polymer/lib/observe_html.dart
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// TODO(jmesserly): can we handle this more elegantly?
|
||||
// In general, it seems like we want a convenient way to take a Stream plus a
|
||||
// getter and convert this into an Observable.
|
||||
|
||||
/** Helpers for exposing dart:html as observable data. */
|
||||
library polymer.observe_html;
|
||||
|
||||
import 'dart:html';
|
||||
import 'package:observe/observe.dart';
|
||||
|
||||
/** An observable version of [window.location.hash]. */
|
||||
final ObservableLocationHash windowLocation = new ObservableLocationHash._();
|
||||
|
||||
class ObservableLocationHash extends ChangeNotifierBase {
|
||||
ObservableLocationHash._() {
|
||||
// listen on changes to #hash in the URL
|
||||
// Note: listen on both popState and hashChange, because IE9 doesn't support
|
||||
// history API. See http://dartbug.com/5483
|
||||
// TODO(jmesserly): only listen to these if someone is listening to our
|
||||
// changes.
|
||||
window.onHashChange.listen(_notifyHashChange);
|
||||
window.onPopState.listen(_notifyHashChange);
|
||||
}
|
||||
|
||||
String get hash => window.location.hash;
|
||||
|
||||
/**
|
||||
* Pushes a new URL state, similar to the affect of clicking a link.
|
||||
* Has no effect if the [value] already equals [window.location.hash].
|
||||
*/
|
||||
void set hash(String value) {
|
||||
if (value == hash) return;
|
||||
|
||||
window.history.pushState(null, '', value);
|
||||
_notifyHashChange(null);
|
||||
}
|
||||
|
||||
void _notifyHashChange(_) {
|
||||
notifyChange(new PropertyChangeRecord(const Symbol('hash')));
|
||||
}
|
||||
}
|
||||
|
||||
/** Add or remove CSS class [className] based on the [value]. */
|
||||
void updateCssClass(Element element, String className, bool value) {
|
||||
if (value == true) {
|
||||
element.classes.add(className);
|
||||
} else {
|
||||
element.classes.remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
/** Bind a CSS class to the observable [object] and property [path]. */
|
||||
PathObserver bindCssClass(Element element, String className,
|
||||
Observable object, String path) {
|
||||
|
||||
return new PathObserver(object, path)..bindSync((value) {
|
||||
updateCssClass(element, className, value);
|
||||
});
|
||||
}
|
143
pkg/polymer/lib/polymer.dart
Normal file
143
pkg/polymer/lib/polymer.dart
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* This library exports all of the commonly used functions and types for
|
||||
* building UI's.
|
||||
*
|
||||
* See this article for more information:
|
||||
* <http://www.dartlang.org/articles/dart-web-components/>.
|
||||
*/
|
||||
library polymer;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:mdv/mdv.dart' as mdv;
|
||||
import 'package:observe/src/microtask.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'polymer_element.dart' show registerPolymerElement;
|
||||
|
||||
export 'package:custom_element/custom_element.dart';
|
||||
export 'package:observe/observe.dart';
|
||||
export 'package:observe/src/microtask.dart';
|
||||
|
||||
export 'observe.dart';
|
||||
export 'observe_html.dart';
|
||||
export 'polymer_element.dart';
|
||||
export 'safe_html.dart';
|
||||
|
||||
|
||||
/** Annotation used to automatically register polymer elements. */
|
||||
class CustomTag {
|
||||
final String tagName;
|
||||
const CustomTag(this.tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata used to label static or top-level methods that are called
|
||||
* automatically when loading the library of a custom element.
|
||||
*/
|
||||
const polymerInitMethod = const _InitPolymerAnnotation();
|
||||
|
||||
/**
|
||||
* Initializes a polymer application by: setting up polling for observable
|
||||
* changes, initializing MDV, registering and initializing custom elements from
|
||||
* each library in [elementLibraries], and finally invoking [userMain].
|
||||
*
|
||||
* There are two mechanisms by which custom elements can be initialized:
|
||||
* annotating the class that declares a custom element with [CustomTag] or
|
||||
* programatically registering the element in a static or top-level function and
|
||||
* annotating that function with [polymerInitMethod].
|
||||
*
|
||||
* The urls in [elementLibraries] can be absolute or relative to [srcUrl].
|
||||
*/
|
||||
void initPolymer(List<String> elementLibraries, void userMain(), [String srcUrl]) {
|
||||
wrapMicrotask(() {
|
||||
// DOM events don't yet go through microtasks, so we catch those here.
|
||||
new Timer.periodic(new Duration(milliseconds: 125),
|
||||
(_) => performMicrotaskCheckpoint());
|
||||
|
||||
// TODO(jmesserly): mdv should use initMdv instead of mdv.initialize.
|
||||
mdv.initialize();
|
||||
for (var lib in elementLibraries) {
|
||||
_registerPolymerElementsOf(lib, srcUrl);
|
||||
}
|
||||
userMain();
|
||||
})();
|
||||
}
|
||||
|
||||
/** All libraries in the current isolate. */
|
||||
final _libs = currentMirrorSystem().libraries;
|
||||
|
||||
/**
|
||||
* Reads the library at [uriString] (which can be an absolute URI or a relative
|
||||
* URI from [srcUrl]), and:
|
||||
*
|
||||
* * Invokes top-level and static functions marked with the
|
||||
* [polymerInitMethod] annotation.
|
||||
*
|
||||
* * Registers any [PolymerElement] that is marked with the [CustomTag]
|
||||
* annotation.
|
||||
*/
|
||||
void _registerPolymerElementsOf(String uriString, [String srcUrl]) {
|
||||
var uri = Uri.parse(uriString);
|
||||
if (uri.scheme == '' && srcUrl != null) {
|
||||
uri = Uri.parse(path.normalize(path.join(path.dirname(srcUrl), uriString)));
|
||||
}
|
||||
var lib = _libs[uri];
|
||||
if (lib == null) {
|
||||
print('warning: $uri library not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Search top-level functions marked with @polymerInitMethod
|
||||
for (var f in lib.functions.values) {
|
||||
_maybeInvoke(lib, f);
|
||||
}
|
||||
|
||||
for (var c in lib.classes.values) {
|
||||
// Search for @CustomTag on classes
|
||||
for (var m in c.metadata) {
|
||||
var meta = m.reflectee;
|
||||
if (meta is CustomTag) {
|
||||
registerPolymerElement(meta.tagName,
|
||||
() => c.newInstance(const Symbol(''), const []).reflectee);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(sigmund): check also static methods marked with @polymerInitMethod.
|
||||
// This is blocked on two bugs:
|
||||
// - dartbug.com/12133 (static methods are incorrectly listed as top-level
|
||||
// in dart2js, so they end up being called twice)
|
||||
// - dartbug.com/12134 (sometimes "method.metadata" throws an exception,
|
||||
// we could wrap and hide those exceptions, but it's not ideal).
|
||||
}
|
||||
}
|
||||
|
||||
void _maybeInvoke(ObjectMirror obj, MethodMirror method) {
|
||||
var annotationFound = false;
|
||||
for (var meta in method.metadata) {
|
||||
if (identical(meta.reflectee, polymerInitMethod)) {
|
||||
annotationFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!annotationFound) return;
|
||||
if (!method.isStatic) {
|
||||
print("warning: methods marked with @polymerInitMethod should be static,"
|
||||
" ${method.simpleName} is not.");
|
||||
return;
|
||||
}
|
||||
if (!method.parameters.where((p) => !p.isOptional).isEmpty) {
|
||||
print("warning: methods marked with @polymerInitMethod should take no "
|
||||
"arguments, ${method.simpleName} expects some.");
|
||||
return;
|
||||
}
|
||||
obj.invoke(method.simpleName, const []);
|
||||
}
|
||||
|
||||
class _InitPolymerAnnotation {
|
||||
const _InitPolymerAnnotation();
|
||||
}
|
555
pkg/polymer/lib/polymer_element.dart
Normal file
555
pkg/polymer/lib/polymer_element.dart
Normal file
|
@ -0,0 +1,555 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library polymer.polymer_element;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html';
|
||||
import 'dart:mirrors';
|
||||
|
||||
import 'package:custom_element/custom_element.dart';
|
||||
import 'package:js/js.dart' as js;
|
||||
import 'package:mdv/mdv.dart' show NodeBinding;
|
||||
import 'package:observe/observe.dart';
|
||||
import 'package:observe/src/microtask.dart';
|
||||
import 'package:polymer_expressions/polymer_expressions.dart';
|
||||
|
||||
import 'src/utils_observe.dart' show toCamelCase, toHyphenedName;
|
||||
|
||||
/**
|
||||
* Registers a [PolymerElement]. This is similar to [registerCustomElement]
|
||||
* but it is designed to work with the `<element>` element and adds additional
|
||||
* features.
|
||||
*/
|
||||
void registerPolymerElement(String localName, PolymerElement create()) {
|
||||
registerCustomElement(localName, () => create().._initialize(localName));
|
||||
}
|
||||
|
||||
/**
|
||||
* *Warning*: many features of this class are not fully implemented.
|
||||
*
|
||||
* The base class for Polymer elements. It provides convience features on top
|
||||
* of the custom elements web standard.
|
||||
*
|
||||
* Currently it supports publishing attributes via:
|
||||
*
|
||||
* <element name="..." attributes="foo, bar, baz">
|
||||
*
|
||||
* Any attribute published this way can be used in a data binding expression,
|
||||
* and it should contain a corresponding DOM field.
|
||||
*
|
||||
* *Warning*: due to dart2js mirror limititations, the mapping from HTML
|
||||
* attribute to element property is a conversion from `dash-separated-words`
|
||||
* to camelCase, rather than searching for a property with the same name.
|
||||
*/
|
||||
// TODO(jmesserly): fix the dash-separated-words issue. Polymer uses lowercase.
|
||||
class PolymerElement extends CustomElement with _EventsMixin {
|
||||
// This is a partial port of:
|
||||
// https://github.com/Polymer/polymer/blob/stable/src/attrs.js
|
||||
// https://github.com/Polymer/polymer/blob/stable/src/bindProperties.js
|
||||
// https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
|
||||
// https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
|
||||
// TODO(jmesserly): we still need to port more of the functionality
|
||||
|
||||
/// The one syntax to rule them all.
|
||||
static BindingDelegate _polymerSyntax = new PolymerExpressions();
|
||||
// TODO(sigmund): delete. The next line is only added to avoid warnings from
|
||||
// the analyzer (see http://dartbug.com/11672)
|
||||
Element get host => super.host;
|
||||
|
||||
bool get applyAuthorStyles => false;
|
||||
bool get resetStyleInheritance => false;
|
||||
|
||||
/**
|
||||
* The declaration of this polymer-element, used to extract template contents
|
||||
* and other information.
|
||||
*/
|
||||
static Map<String, Element> _declarations = {};
|
||||
static Element getDeclaration(String localName) {
|
||||
if (localName == null) return null;
|
||||
var element = _declarations[localName];
|
||||
if (element == null) {
|
||||
element = document.query('polymer-element[name="$localName"]');
|
||||
_declarations[localName] = element;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
Map<String, PathObserver> _publishedAttrs;
|
||||
Map<String, StreamSubscription> _bindings;
|
||||
final List<String> _localNames = [];
|
||||
|
||||
void _initialize(String localName) {
|
||||
if (localName == null) return;
|
||||
|
||||
var declaration = getDeclaration(localName);
|
||||
if (declaration == null) return;
|
||||
|
||||
if (declaration.attributes['extends'] != null) {
|
||||
var base = declaration.attributes['extends'];
|
||||
// Skip normal tags, only initialize parent custom elements.
|
||||
if (base.contains('-')) _initialize(base);
|
||||
}
|
||||
|
||||
_parseHostEvents(declaration);
|
||||
_parseLocalEvents(declaration);
|
||||
_publishAttributes(declaration);
|
||||
_localNames.add(localName);
|
||||
}
|
||||
|
||||
void _publishAttributes(elementElement) {
|
||||
_bindings = {};
|
||||
_publishedAttrs = {};
|
||||
|
||||
var attrs = elementElement.attributes['attributes'];
|
||||
if (attrs != null) {
|
||||
// attributes='a b c' or attributes='a,b,c'
|
||||
for (var name in attrs.split(attrs.contains(',') ? ',' : ' ')) {
|
||||
name = name.trim();
|
||||
|
||||
// TODO(jmesserly): PathObserver is overkill here; it helps avoid
|
||||
// "new Symbol" and other mirrors-related warnings.
|
||||
_publishedAttrs[name] = new PathObserver(this, toCamelCase(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void created() {
|
||||
// TODO(jmesserly): this breaks until we get some kind of type conversion.
|
||||
// _publishedAttrs.forEach((name, propObserver) {
|
||||
// var value = attributes[name];
|
||||
// if (value != null) propObserver.value = value;
|
||||
// });
|
||||
_initShadowRoot();
|
||||
_addHostListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the document fragment to use for each instance of the custom
|
||||
* element, given the `<template>` node. By default this is equivalent to:
|
||||
*
|
||||
* template.createInstance(this, polymerSyntax);
|
||||
*
|
||||
* Where polymerSyntax is a singleton `PolymerExpressions` instance from the
|
||||
* [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions)
|
||||
* package.
|
||||
*
|
||||
* You can override this method to change the instantiation behavior of the
|
||||
* template, for example to use a different data-binding syntax.
|
||||
*/
|
||||
DocumentFragment instanceTemplate(Element template) =>
|
||||
template.createInstance(this, _polymerSyntax);
|
||||
|
||||
void _initShadowRoot() {
|
||||
for (var localName in _localNames) {
|
||||
var declaration = getDeclaration(localName);
|
||||
var root = createShadowRoot(localName);
|
||||
_addInstanceListeners(root, localName);
|
||||
|
||||
root.applyAuthorStyles = applyAuthorStyles;
|
||||
root.resetStyleInheritance = resetStyleInheritance;
|
||||
|
||||
var templateNode = declaration.children.firstWhere(
|
||||
(n) => n.localName == 'template', orElse: () => null);
|
||||
if (templateNode == null) return;
|
||||
|
||||
// Create the contents of the element's ShadowRoot, and add them.
|
||||
root.nodes.add(instanceTemplate(templateNode));
|
||||
|
||||
var extendsName = declaration.attributes['extends'];
|
||||
_shimCss(root, localName, extendsName);
|
||||
}
|
||||
}
|
||||
|
||||
NodeBinding createBinding(String name, model, String path) {
|
||||
var propObserver = _publishedAttrs[name];
|
||||
if (propObserver != null) {
|
||||
return new _PolymerBinding(this, name, model, path, propObserver);
|
||||
}
|
||||
return super.createBinding(name, model, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Using Polymer's platform/src/ShadowCSS.js passing the style tag's content.
|
||||
*/
|
||||
void _shimCss(ShadowRoot root, String localName, String extendsName) {
|
||||
// TODO(terry): Remove warning, cast js.context to dynamic because of bug
|
||||
// https://code.google.com/p/dart/issues/detail?id=6111. The
|
||||
// js interop package will be patching this until bug is fixed.
|
||||
var platform = (js.context as dynamic).Platform;
|
||||
if (platform == null) return;
|
||||
var shadowCss = platform.ShadowCSS;
|
||||
if (shadowCss == null) return;
|
||||
|
||||
// TODO(terry): Remove calls to shimShadowDOMStyling2 and replace with
|
||||
// shimShadowDOMStyling when we support unwrapping dart:html
|
||||
// Element to a JS DOM node.
|
||||
var shimShadowDOMStyling2 = shadowCss.shimShadowDOMStyling2;
|
||||
if (shimShadowDOMStyling2 == null) return;
|
||||
var style = root.query('style');
|
||||
if (style == null) return;
|
||||
var scopedCSS = shimShadowDOMStyling2(style.text, localName);
|
||||
|
||||
// TODO(terry): Remove when shimShadowDOMStyling is called we don't need to
|
||||
// replace original CSS with scoped CSS shimShadowDOMStyling
|
||||
// does that.
|
||||
style.text = scopedCSS;
|
||||
}
|
||||
}
|
||||
|
||||
class _PolymerBinding extends NodeBinding {
|
||||
final PathObserver _publishedAttr;
|
||||
|
||||
_PolymerBinding(node, property, model, path, PathObserver this._publishedAttr)
|
||||
: super(node, property, model, path);
|
||||
|
||||
void boundValueChanged(newValue) {
|
||||
_publishedAttr.value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Polymer features to handle the syntactic sugar on-* to declare to
|
||||
* automatically map event handlers to instance methods of the [PolymerElement].
|
||||
* This mixin is a port of:
|
||||
* https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
|
||||
* https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
|
||||
*/
|
||||
abstract class _EventsMixin {
|
||||
// TODO(sigmund): implement the Dart equivalent of 'inheritDelegates'
|
||||
// Notes about differences in the implementation below:
|
||||
// - _templateDelegates: polymer stores the template delegates directly on
|
||||
// the template node (see in parseLocalEvents: 't.delegates = {}'). Here we
|
||||
// simply use a separate map, where keys are the name of the
|
||||
// custom-element.
|
||||
// - _listenLocal we return true/false and propagate that up, JS
|
||||
// implementation does't forward the return value.
|
||||
// - we don't keep the side-table (weak hash map) of unhandled events (see
|
||||
// handleIfNotHandled)
|
||||
// - we don't use event.type to dispatch events, instead we save the event
|
||||
// name with the event listeners. We do so to avoid translating back and
|
||||
// forth between Dom and Dart event names.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The following section was ported from:
|
||||
// https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Maps event names and their associated method in the element class. */
|
||||
final Map<String, String> _delegates = {};
|
||||
|
||||
/** Expected events per element node. */
|
||||
// TODO(sigmund): investigate whether we need more than 1 set of local events
|
||||
// per element (why does the js implementation stores 1 per template node?)
|
||||
final Map<String, Set<String>> _templateDelegates =
|
||||
new Map<String, Set<String>>();
|
||||
|
||||
/** [host] is needed by this mixin, but not defined here. */
|
||||
Element get host;
|
||||
|
||||
/** Attribute prefix used for declarative event handlers. */
|
||||
static const _eventPrefix = 'on-';
|
||||
|
||||
/** Whether an attribute declares an event. */
|
||||
static bool _isEvent(String attr) => attr.startsWith(_eventPrefix);
|
||||
|
||||
/** Extracts events from the element tag attributes. */
|
||||
void _parseHostEvents(elementElement) {
|
||||
for (var attr in elementElement.attributes.keys.where(_isEvent)) {
|
||||
_delegates[toCamelCase(attr)] = elementElement.attributes[attr];
|
||||
}
|
||||
}
|
||||
|
||||
/** Extracts events under the element's <template>. */
|
||||
void _parseLocalEvents(elementElement) {
|
||||
var name = elementElement.attributes["name"];
|
||||
if (name == null) return;
|
||||
var events = null;
|
||||
for (var template in elementElement.queryAll('template')) {
|
||||
var content = template.content;
|
||||
if (content != null) {
|
||||
for (var child in content.children) {
|
||||
events = _accumulateEvents(child, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (events != null) {
|
||||
_templateDelegates[name] = events;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns all events names listened by [element] and it's children. */
|
||||
static Set<String> _accumulateEvents(Element element, [Set<String> events]) {
|
||||
events = events == null ? new Set<String>() : events;
|
||||
|
||||
// from: accumulateAttributeEvents, accumulateEvent
|
||||
events.addAll(element.attributes.keys.where(_isEvent).map(toCamelCase));
|
||||
|
||||
// from: accumulateChildEvents
|
||||
for (var child in element.children) {
|
||||
_accumulateEvents(child, events);
|
||||
}
|
||||
|
||||
// from: accumulateTemplatedEvents
|
||||
if (element.isTemplate) {
|
||||
var content = element.content;
|
||||
if (content != null) {
|
||||
for (var child in content.children) {
|
||||
_accumulateEvents(child, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The following section was ported from:
|
||||
// https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Attaches event listeners on the [host] element. */
|
||||
void _addHostListeners() {
|
||||
for (var eventName in _delegates.keys) {
|
||||
_addNodeListener(host, eventName,
|
||||
(e) => _hostEventListener(eventName, e));
|
||||
}
|
||||
}
|
||||
|
||||
void _addNodeListener(node, String onEvent, Function listener) {
|
||||
// If [node] is an element (typically when listening for host events) we
|
||||
// use directly the '.onFoo' event stream of the element instance.
|
||||
if (node is Element) {
|
||||
reflect(node).getField(new Symbol(onEvent)).reflectee.listen(listener);
|
||||
return;
|
||||
}
|
||||
|
||||
// When [node] is not an element, most commonly when [node] is the
|
||||
// shadow-root of the polymer-element, we find the appropriate static event
|
||||
// stream providers and attach it to [node].
|
||||
var eventProvider = _eventStreamProviders[onEvent];
|
||||
if (eventProvider != null) {
|
||||
eventProvider.forTarget(node).listen(listener);
|
||||
return;
|
||||
}
|
||||
|
||||
// When no provider is available, mainly because of custom-events, we use
|
||||
// the underlying event listeners from the DOM.
|
||||
var eventName = onEvent.substring(2).toLowerCase(); // onOneTwo => onetwo
|
||||
// Most events names in Dart match those in JS in lowercase except for some
|
||||
// few events listed in this map. We expect these cases to be handled above,
|
||||
// but just in case we include them as a safety net here.
|
||||
var jsNameFixes = const {
|
||||
'animationend': 'webkitAnimationEnd',
|
||||
'animationiteration': 'webkitAnimationIteration',
|
||||
'animationstart': 'webkitAnimationStart',
|
||||
'doubleclick': 'dblclick',
|
||||
'fullscreenchange': 'webkitfullscreenchange',
|
||||
'fullscreenerror': 'webkitfullscreenerror',
|
||||
'keyadded': 'webkitkeyadded',
|
||||
'keyerror': 'webkitkeyerror',
|
||||
'keymessage': 'webkitkeymessage',
|
||||
'needkey': 'webkitneedkey',
|
||||
'speechchange': 'webkitSpeechChange',
|
||||
};
|
||||
var fixedName = jsNameFixes[eventName];
|
||||
node.on[fixedName != null ? fixedName : eventName].listen(listener);
|
||||
}
|
||||
|
||||
void _addInstanceListeners(ShadowRoot root, String elementName) {
|
||||
var events = _templateDelegates[elementName];
|
||||
if (events == null) return;
|
||||
for (var eventName in events) {
|
||||
_addNodeListener(root, eventName,
|
||||
(e) => _instanceEventListener(eventName, e));
|
||||
}
|
||||
}
|
||||
|
||||
void _hostEventListener(String eventName, Event event) {
|
||||
var method = _delegates[eventName];
|
||||
if (event.bubbles && method != null) {
|
||||
_dispatchMethod(this, method, event, host);
|
||||
}
|
||||
}
|
||||
|
||||
void _dispatchMethod(Object receiver, String methodName, Event event,
|
||||
Node target) {
|
||||
var detail = event is CustomEvent ? (event as CustomEvent).detail : null;
|
||||
var args = [event, detail, target];
|
||||
|
||||
var method = new Symbol(methodName);
|
||||
// TODO(sigmund): consider making event listeners list all arguments
|
||||
// explicitly. Unless VM mirrors are optimized first, this reflectClass call
|
||||
// will be expensive once custom elements extend directly from Element (see
|
||||
// dartbug.com/11108).
|
||||
var methodDecl = reflectClass(receiver.runtimeType).methods[method];
|
||||
if (methodDecl != null) {
|
||||
// This will either truncate the argument list or extend it with extra
|
||||
// null arguments, so it will match the signature.
|
||||
// TODO(sigmund): consider accepting optional arguments when we can tell
|
||||
// them appart from named arguments (see http://dartbug.com/11334)
|
||||
args.length = methodDecl.parameters.where((p) => !p.isOptional).length;
|
||||
}
|
||||
reflect(receiver).invoke(method, args);
|
||||
performMicrotaskCheckpoint();
|
||||
}
|
||||
|
||||
bool _instanceEventListener(String eventName, Event event) {
|
||||
if (event.bubbles) {
|
||||
if (event.path == null || !ShadowRoot.supported) {
|
||||
return _listenLocalNoEventPath(eventName, event);
|
||||
} else {
|
||||
return _listenLocal(eventName, event);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _listenLocal(String eventName, Event event) {
|
||||
var controller = null;
|
||||
for (var target in event.path) {
|
||||
// if we hit host, stop
|
||||
if (target == host) return true;
|
||||
|
||||
// find a controller for the target, unless we already found `host`
|
||||
// as a controller
|
||||
controller = (controller == host) ? controller : _findController(target);
|
||||
|
||||
// if we have a controller, dispatch the event, and stop if the handler
|
||||
// returns true
|
||||
if (controller != null
|
||||
&& handleEvent(controller, eventName, event, target)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(sorvell): remove when ShadowDOM polyfill supports event path.
|
||||
// Note that _findController will not return the expected controller when the
|
||||
// event target is a distributed node. This is because we cannot traverse
|
||||
// from a composed node to a node in shadowRoot.
|
||||
// This will be addressed via an event path api
|
||||
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066
|
||||
bool _listenLocalNoEventPath(String eventName, Event event) {
|
||||
var target = event.target;
|
||||
var controller = null;
|
||||
while (target != null && target != host) {
|
||||
controller = (controller == host) ? controller : _findController(target);
|
||||
if (controller != null
|
||||
&& handleEvent(controller, eventName, event, target)) {
|
||||
return true;
|
||||
}
|
||||
target = target.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(sigmund): investigate if this implementation is correct. Polymer looks
|
||||
// up the shadow-root that contains [node] and uses a weak-hashmap to find the
|
||||
// host associated with that root. This implementation assumes that the
|
||||
// [node] is under [host]'s shadow-root.
|
||||
Element _findController(Node node) => host.xtag;
|
||||
|
||||
bool handleEvent(
|
||||
Element controller, String eventName, Event event, Element element) {
|
||||
// Note: local events are listened only in the shadow root. This dynamic
|
||||
// lookup is used to distinguish determine whether the target actually has a
|
||||
// listener, and if so, to determine lazily what's the target method.
|
||||
var methodName = element.attributes[toHyphenedName(eventName)];
|
||||
if (methodName != null) {
|
||||
_dispatchMethod(controller, methodName, event, element);
|
||||
}
|
||||
return event.bubbles;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Event stream providers per event name. */
|
||||
// TODO(sigmund): after dartbug.com/11108 is fixed, consider eliminating this
|
||||
// table and using reflection instead.
|
||||
const Map<String, EventStreamProvider> _eventStreamProviders = const {
|
||||
'onMouseWheel': Element.mouseWheelEvent,
|
||||
'onTransitionEnd': Element.transitionEndEvent,
|
||||
'onAbort': Element.abortEvent,
|
||||
'onBeforeCopy': Element.beforeCopyEvent,
|
||||
'onBeforeCut': Element.beforeCutEvent,
|
||||
'onBeforePaste': Element.beforePasteEvent,
|
||||
'onBlur': Element.blurEvent,
|
||||
'onChange': Element.changeEvent,
|
||||
'onClick': Element.clickEvent,
|
||||
'onContextMenu': Element.contextMenuEvent,
|
||||
'onCopy': Element.copyEvent,
|
||||
'onCut': Element.cutEvent,
|
||||
'onDoubleClick': Element.doubleClickEvent,
|
||||
'onDrag': Element.dragEvent,
|
||||
'onDragEnd': Element.dragEndEvent,
|
||||
'onDragEnter': Element.dragEnterEvent,
|
||||
'onDragLeave': Element.dragLeaveEvent,
|
||||
'onDragOver': Element.dragOverEvent,
|
||||
'onDragStart': Element.dragStartEvent,
|
||||
'onDrop': Element.dropEvent,
|
||||
'onError': Element.errorEvent,
|
||||
'onFocus': Element.focusEvent,
|
||||
'onInput': Element.inputEvent,
|
||||
'onInvalid': Element.invalidEvent,
|
||||
'onKeyDown': Element.keyDownEvent,
|
||||
'onKeyPress': Element.keyPressEvent,
|
||||
'onKeyUp': Element.keyUpEvent,
|
||||
'onLoad': Element.loadEvent,
|
||||
'onMouseDown': Element.mouseDownEvent,
|
||||
'onMouseMove': Element.mouseMoveEvent,
|
||||
'onMouseOut': Element.mouseOutEvent,
|
||||
'onMouseOver': Element.mouseOverEvent,
|
||||
'onMouseUp': Element.mouseUpEvent,
|
||||
'onPaste': Element.pasteEvent,
|
||||
'onReset': Element.resetEvent,
|
||||
'onScroll': Element.scrollEvent,
|
||||
'onSearch': Element.searchEvent,
|
||||
'onSelect': Element.selectEvent,
|
||||
'onSelectStart': Element.selectStartEvent,
|
||||
'onSubmit': Element.submitEvent,
|
||||
'onTouchCancel': Element.touchCancelEvent,
|
||||
'onTouchEnd': Element.touchEndEvent,
|
||||
'onTouchEnter': Element.touchEnterEvent,
|
||||
'onTouchLeave': Element.touchLeaveEvent,
|
||||
'onTouchMove': Element.touchMoveEvent,
|
||||
'onTouchStart': Element.touchStartEvent,
|
||||
'onFullscreenChange': Element.fullscreenChangeEvent,
|
||||
'onFullscreenError': Element.fullscreenErrorEvent,
|
||||
'onAutocomplete': FormElement.autocompleteEvent,
|
||||
'onAutocompleteError': FormElement.autocompleteErrorEvent,
|
||||
'onSpeechChange': InputElement.speechChangeEvent,
|
||||
'onCanPlay': MediaElement.canPlayEvent,
|
||||
'onCanPlayThrough': MediaElement.canPlayThroughEvent,
|
||||
'onDurationChange': MediaElement.durationChangeEvent,
|
||||
'onEmptied': MediaElement.emptiedEvent,
|
||||
'onEnded': MediaElement.endedEvent,
|
||||
'onLoadStart': MediaElement.loadStartEvent,
|
||||
'onLoadedData': MediaElement.loadedDataEvent,
|
||||
'onLoadedMetadata': MediaElement.loadedMetadataEvent,
|
||||
'onPause': MediaElement.pauseEvent,
|
||||
'onPlay': MediaElement.playEvent,
|
||||
'onPlaying': MediaElement.playingEvent,
|
||||
'onProgress': MediaElement.progressEvent,
|
||||
'onRateChange': MediaElement.rateChangeEvent,
|
||||
'onSeeked': MediaElement.seekedEvent,
|
||||
'onSeeking': MediaElement.seekingEvent,
|
||||
'onShow': MediaElement.showEvent,
|
||||
'onStalled': MediaElement.stalledEvent,
|
||||
'onSuspend': MediaElement.suspendEvent,
|
||||
'onTimeUpdate': MediaElement.timeUpdateEvent,
|
||||
'onVolumeChange': MediaElement.volumeChangeEvent,
|
||||
'onWaiting': MediaElement.waitingEvent,
|
||||
'onKeyAdded': MediaElement.keyAddedEvent,
|
||||
'onKeyError': MediaElement.keyErrorEvent,
|
||||
'onKeyMessage': MediaElement.keyMessageEvent,
|
||||
'onNeedKey': MediaElement.needKeyEvent,
|
||||
'onWebGlContextLost': CanvasElement.webGlContextLostEvent,
|
||||
'onWebGlContextRestored': CanvasElement.webGlContextRestoredEvent,
|
||||
'onPointerLockChange': Document.pointerLockChangeEvent,
|
||||
'onPointerLockError': Document.pointerLockErrorEvent,
|
||||
'onReadyStateChange': Document.readyStateChangeEvent,
|
||||
'onSelectionChange': Document.selectionChangeEvent,
|
||||
'onSecurityPolicyViolation': Document.securityPolicyViolationEvent,
|
||||
};
|
39
pkg/polymer/lib/safe_html.dart
Normal file
39
pkg/polymer/lib/safe_html.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// TODO(sigmund): move this library to a shared package? or make part of
|
||||
// dart:html?
|
||||
library polymer.safe_html;
|
||||
|
||||
/** Declares a string that is a well-formed HTML fragment. */
|
||||
class SafeHtml {
|
||||
|
||||
/** Underlying html string. */
|
||||
final String _html;
|
||||
|
||||
// TODO(sigmund): provide a constructor that does html validation
|
||||
SafeHtml.unsafe(this._html);
|
||||
|
||||
String toString() => _html;
|
||||
|
||||
operator ==(other) => other is SafeHtml && _html == other._html;
|
||||
int get hashCode => _html.hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares a string that is safe to use in a Uri attribute, such as `<a href=`,
|
||||
* to avoid cross-site scripting (XSS) attacks.
|
||||
*/
|
||||
class SafeUri {
|
||||
final String _uri;
|
||||
|
||||
// TODO(sigmund): provide a constructor that takes or creates a Uri and
|
||||
// validates that it is safe (not a javascript: scheme, for example)
|
||||
SafeUri.unsafe(this._uri);
|
||||
|
||||
String toString() => _uri;
|
||||
|
||||
operator ==(other) => other is SafeUri && _uri == other._uri;
|
||||
int get hashCode => _uri.hashCode;
|
||||
}
|
566
pkg/polymer/lib/src/analyzer.dart
Normal file
566
pkg/polymer/lib/src/analyzer.dart
Normal file
|
@ -0,0 +1,566 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Part of the template compilation that concerns with extracting information
|
||||
* from the HTML parse tree.
|
||||
*/
|
||||
library analyzer;
|
||||
|
||||
import 'package:html5lib/dom.dart';
|
||||
import 'package:html5lib/dom_parsing.dart';
|
||||
import 'package:source_maps/span.dart' hide SourceFile;
|
||||
|
||||
import 'custom_tag_name.dart';
|
||||
import 'dart_parser.dart' show parseDartCode;
|
||||
import 'files.dart';
|
||||
import 'info.dart';
|
||||
import 'messages.dart';
|
||||
import 'summary.dart';
|
||||
|
||||
/**
|
||||
* Finds custom elements in this file and the list of referenced files with
|
||||
* component declarations. This is the first pass of analysis on a file.
|
||||
*
|
||||
* Adds emitted error/warning messages to [messages], if [messages] is
|
||||
* supplied.
|
||||
*/
|
||||
FileInfo analyzeDefinitions(GlobalInfo global, UrlInfo inputUrl,
|
||||
Document document, String packageRoot,
|
||||
Messages messages, {bool isEntryPoint: false}) {
|
||||
var result = new FileInfo(inputUrl, isEntryPoint);
|
||||
var loader = new _ElementLoader(global, result, packageRoot, messages);
|
||||
loader.visit(document);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract relevant information from all files found from the root document.
|
||||
*
|
||||
* Adds emitted error/warning messages to [messages], if [messages] is
|
||||
* supplied.
|
||||
*/
|
||||
void analyzeFile(SourceFile file, Map<String, FileInfo> info,
|
||||
Iterator<int> uniqueIds, GlobalInfo global,
|
||||
Messages messages, emulateScopedCss) {
|
||||
var fileInfo = info[file.path];
|
||||
var analyzer = new _Analyzer(fileInfo, uniqueIds, global, messages,
|
||||
emulateScopedCss);
|
||||
analyzer._normalize(fileInfo, info);
|
||||
analyzer.visit(file.document);
|
||||
}
|
||||
|
||||
|
||||
/** A visitor that walks the HTML to extract all the relevant information. */
|
||||
class _Analyzer extends TreeVisitor {
|
||||
final FileInfo _fileInfo;
|
||||
LibraryInfo _currentInfo;
|
||||
Iterator<int> _uniqueIds;
|
||||
GlobalInfo _global;
|
||||
Messages _messages;
|
||||
|
||||
int _generatedClassNumber = 0;
|
||||
|
||||
/**
|
||||
* Whether to keep indentation spaces. Break lines and indentation spaces
|
||||
* within templates are preserved in HTML. When users specify the attribute
|
||||
* 'indentation="remove"' on a template tag, we'll trim those indentation
|
||||
* spaces that occur within that tag and its decendants. If any decendant
|
||||
* specifies 'indentation="preserve"', then we'll switch back to the normal
|
||||
* behavior.
|
||||
*/
|
||||
bool _keepIndentationSpaces = true;
|
||||
|
||||
final bool _emulateScopedCss;
|
||||
|
||||
_Analyzer(this._fileInfo, this._uniqueIds, this._global, this._messages,
|
||||
this._emulateScopedCss) {
|
||||
_currentInfo = _fileInfo;
|
||||
}
|
||||
|
||||
void visitElement(Element node) {
|
||||
if (node.tagName == 'script') {
|
||||
// We already extracted script tags in previous phase.
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.tagName == 'style') {
|
||||
// We've already parsed the CSS.
|
||||
// If this is a component remove the style node.
|
||||
if (_currentInfo is ComponentInfo && _emulateScopedCss) node.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
_bindCustomElement(node);
|
||||
|
||||
var lastInfo = _currentInfo;
|
||||
if (node.tagName == 'polymer-element') {
|
||||
// If element is invalid _ElementLoader already reported an error, but
|
||||
// we skip the body of the element here.
|
||||
var name = node.attributes['name'];
|
||||
if (name == null) return;
|
||||
|
||||
ComponentInfo component = _fileInfo.components[name];
|
||||
if (component == null) return;
|
||||
|
||||
_analyzeComponent(component);
|
||||
|
||||
_currentInfo = component;
|
||||
|
||||
// Remove the <element> tag from the tree
|
||||
node.remove();
|
||||
}
|
||||
|
||||
node.attributes.forEach((name, value) {
|
||||
if (name.startsWith('on')) {
|
||||
_validateEventHandler(node, name, value);
|
||||
} else if (name == 'pseudo' && _currentInfo is ComponentInfo) {
|
||||
// Any component's custom pseudo-element(s) defined?
|
||||
_processPseudoAttribute(node, value.split(' '));
|
||||
}
|
||||
});
|
||||
|
||||
var keepSpaces = _keepIndentationSpaces;
|
||||
if (node.tagName == 'template' &&
|
||||
node.attributes.containsKey('indentation')) {
|
||||
var value = node.attributes['indentation'];
|
||||
if (value != 'remove' && value != 'preserve') {
|
||||
_messages.warning(
|
||||
"Invalid value for 'indentation' ($value). By default we preserve "
|
||||
"the indentation. Valid values are either 'remove' or 'preserve'.",
|
||||
node.sourceSpan);
|
||||
}
|
||||
_keepIndentationSpaces = value != 'remove';
|
||||
}
|
||||
|
||||
// Invoke super to visit children.
|
||||
super.visitElement(node);
|
||||
|
||||
_keepIndentationSpaces = keepSpaces;
|
||||
_currentInfo = lastInfo;
|
||||
|
||||
if (node.tagName == 'body' || node.parent == null) {
|
||||
_fileInfo.body = node;
|
||||
}
|
||||
}
|
||||
|
||||
void _analyzeComponent(ComponentInfo component) {
|
||||
var baseTag = component.extendsTag;
|
||||
component.extendsComponent = baseTag == null ? null
|
||||
: _fileInfo.components[baseTag];
|
||||
if (component.extendsComponent == null && isCustomTag(baseTag)) {
|
||||
_messages.warning(
|
||||
'custom element with tag name ${component.extendsTag} not found.',
|
||||
component.element.sourceSpan);
|
||||
}
|
||||
|
||||
// Now that the component's code has been loaded, we can validate that the
|
||||
// class exists.
|
||||
component.findClassDeclaration(_messages);
|
||||
}
|
||||
|
||||
void _bindCustomElement(Element node) {
|
||||
// <fancy-button>
|
||||
var component = _fileInfo.components[node.tagName];
|
||||
if (component == null) {
|
||||
// TODO(jmesserly): warn for unknown element tags?
|
||||
|
||||
// <button is="fancy-button">
|
||||
var componentName = node.attributes['is'];
|
||||
if (componentName != null) {
|
||||
component = _fileInfo.components[componentName];
|
||||
} else if (isCustomTag(node.tagName)) {
|
||||
componentName = node.tagName;
|
||||
}
|
||||
if (component == null && componentName != null &&
|
||||
componentName != 'polymer-element') {
|
||||
_messages.warning(
|
||||
'custom element with tag name $componentName not found.',
|
||||
node.sourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
if (component != null) {
|
||||
if (!component.hasConflict) {
|
||||
_currentInfo.usedComponents[component] = true;
|
||||
}
|
||||
|
||||
var baseTag = component.baseExtendsTag;
|
||||
var nodeTag = node.tagName;
|
||||
var hasIsAttribute = node.attributes.containsKey('is');
|
||||
|
||||
if (baseTag != null && !hasIsAttribute) {
|
||||
_messages.warning(
|
||||
'custom element "${component.tagName}" extends from "$baseTag", but'
|
||||
' this tag will not include the default properties of "$baseTag". '
|
||||
'To fix this, either write this tag as <$baseTag '
|
||||
'is="${component.tagName}"> or remove the "extends" attribute from '
|
||||
'the custom element declaration.', node.sourceSpan);
|
||||
} else if (hasIsAttribute) {
|
||||
if (baseTag == null) {
|
||||
_messages.warning(
|
||||
'custom element "${component.tagName}" doesn\'t declare any type '
|
||||
'extensions. To fix this, either rewrite this tag as '
|
||||
'<${component.tagName}> or add \'extends="$nodeTag"\' to '
|
||||
'the custom element declaration.', node.sourceSpan);
|
||||
} else if (baseTag != nodeTag) {
|
||||
_messages.warning(
|
||||
'custom element "${component.tagName}" extends from "$baseTag". '
|
||||
'Did you mean to write <$baseTag is="${component.tagName}">?',
|
||||
node.sourceSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _processPseudoAttribute(Node node, List<String> values) {
|
||||
List mangledValues = [];
|
||||
for (var pseudoElement in values) {
|
||||
if (_global.pseudoElements.containsKey(pseudoElement)) continue;
|
||||
|
||||
_uniqueIds.moveNext();
|
||||
var newValue = "${pseudoElement}_${_uniqueIds.current}";
|
||||
_global.pseudoElements[pseudoElement] = newValue;
|
||||
// Mangled name of pseudo-element.
|
||||
mangledValues.add(newValue);
|
||||
|
||||
if (!pseudoElement.startsWith('x-')) {
|
||||
// TODO(terry): The name must start with x- otherwise it's not a custom
|
||||
// pseudo-element. May want to relax since components no
|
||||
// longer need to start with x-. See isse #509 on
|
||||
// pseudo-element prefix.
|
||||
_messages.warning("Custom pseudo-element must be prefixed with 'x-'.",
|
||||
node.sourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the pseudo attribute with the new mangled names.
|
||||
node.attributes['pseudo'] = mangledValues.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for inline event handlers that take expressions.
|
||||
* For example: `on-double-click=myHandler($event, todo)`.
|
||||
*/
|
||||
void _validateEventHandler(Element node, String name, String value) {
|
||||
if (!name.startsWith('on-')) {
|
||||
// TODO(jmesserly): do we need an option to suppress this warning?
|
||||
_messages.warning('Event handler $name will be interpreted as an inline '
|
||||
'JavaScript event handler. Use the form '
|
||||
'on-event-name="handlerName" if you want a Dart handler '
|
||||
'that will automatically update the UI based on model changes.',
|
||||
node.sourceSpan);
|
||||
}
|
||||
|
||||
if (value.contains('.') || value.contains('(')) {
|
||||
// TODO(sigmund): should we allow more if we use fancy-syntax?
|
||||
_messages.warning('Invalid event handler body "$value". Declare a method '
|
||||
'in your custom element "void handlerName(event, detail, target)" '
|
||||
'and use the form on-event-name="handlerName".',
|
||||
node.sourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes references in [info]. On the [analyzeDefinitions] phase, the
|
||||
* analyzer extracted names of files and components. Here we link those names
|
||||
* to actual info classes. In particular:
|
||||
* * we initialize the [FileInfo.components] map in [info] by importing all
|
||||
* [declaredComponents],
|
||||
* * we scan all [info.componentLinks] and import their
|
||||
* [info.declaredComponents], using [files] to map the href to the file
|
||||
* info. Names in [info] will shadow names from imported files.
|
||||
* * we fill [LibraryInfo.externalCode] on each component declared in
|
||||
* [info].
|
||||
*/
|
||||
void _normalize(FileInfo info, Map<String, FileInfo> files) {
|
||||
_attachExtenalScript(info, files);
|
||||
|
||||
for (var component in info.declaredComponents) {
|
||||
_addComponent(info, component);
|
||||
_attachExtenalScript(component, files);
|
||||
}
|
||||
|
||||
for (var link in info.componentLinks) {
|
||||
var file = files[link.resolvedPath];
|
||||
// We already issued an error for missing files.
|
||||
if (file == null) continue;
|
||||
file.declaredComponents.forEach((c) => _addComponent(info, c));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a direct reference in [info] to a dart source file that was loaded
|
||||
* in a script tag with the 'src' attribute.
|
||||
*/
|
||||
void _attachExtenalScript(LibraryInfo info, Map<String, FileInfo> files) {
|
||||
var externalFile = info.externalFile;
|
||||
if (externalFile != null) {
|
||||
info.externalCode = files[externalFile.resolvedPath];
|
||||
if (info.externalCode != null) info.externalCode.htmlFile = info;
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a component's tag name to the names in scope for [fileInfo]. */
|
||||
void _addComponent(FileInfo fileInfo, ComponentSummary component) {
|
||||
var existing = fileInfo.components[component.tagName];
|
||||
if (existing != null) {
|
||||
if (existing == component) {
|
||||
// This is the same exact component as the existing one.
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing is ComponentInfo && component is! ComponentInfo) {
|
||||
// Components declared in [fileInfo] shadow component names declared in
|
||||
// imported files.
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing.hasConflict) {
|
||||
// No need to report a second error for the same name.
|
||||
return;
|
||||
}
|
||||
|
||||
existing.hasConflict = true;
|
||||
|
||||
if (component is ComponentInfo) {
|
||||
_messages.error('duplicate custom element definition for '
|
||||
'"${component.tagName}".', existing.sourceSpan);
|
||||
_messages.error('duplicate custom element definition for '
|
||||
'"${component.tagName}" (second location).', component.sourceSpan);
|
||||
} else {
|
||||
_messages.error('imported duplicate custom element definitions '
|
||||
'for "${component.tagName}".', existing.sourceSpan);
|
||||
_messages.error('imported duplicate custom element definitions '
|
||||
'for "${component.tagName}" (second location).',
|
||||
component.sourceSpan);
|
||||
}
|
||||
} else {
|
||||
fileInfo.components[component.tagName] = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A visitor that finds `<link rel="import">` and `<element>` tags. */
|
||||
class _ElementLoader extends TreeVisitor {
|
||||
final GlobalInfo _global;
|
||||
final FileInfo _fileInfo;
|
||||
LibraryInfo _currentInfo;
|
||||
String _packageRoot;
|
||||
bool _inHead = false;
|
||||
Messages _messages;
|
||||
|
||||
/**
|
||||
* Adds emitted warning/error messages to [_messages]. [_messages]
|
||||
* must not be null.
|
||||
*/
|
||||
_ElementLoader(this._global, this._fileInfo, this._packageRoot,
|
||||
this._messages) {
|
||||
_currentInfo = _fileInfo;
|
||||
}
|
||||
|
||||
void visitElement(Element node) {
|
||||
switch (node.tagName) {
|
||||
case 'link': visitLinkElement(node); break;
|
||||
case 'element':
|
||||
_messages.warning('<element> elements are not supported, use'
|
||||
' <polymer-element> instead', node.sourceSpan);
|
||||
break;
|
||||
case 'polymer-element':
|
||||
visitElementElement(node);
|
||||
break;
|
||||
case 'script': visitScriptElement(node); break;
|
||||
case 'head':
|
||||
var savedInHead = _inHead;
|
||||
_inHead = true;
|
||||
super.visitElement(node);
|
||||
_inHead = savedInHead;
|
||||
break;
|
||||
default: super.visitElement(node); break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process `link rel="import"` as specified in:
|
||||
* <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/components/index.html#link-type-component>
|
||||
*/
|
||||
void visitLinkElement(Element node) {
|
||||
var rel = node.attributes['rel'];
|
||||
if (rel != 'component' && rel != 'components' &&
|
||||
rel != 'import' && rel != 'stylesheet') return;
|
||||
|
||||
if (!_inHead) {
|
||||
_messages.warning('link rel="$rel" only valid in '
|
||||
'head.', node.sourceSpan);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rel == 'component' || rel == 'components') {
|
||||
_messages.warning('import syntax is changing, use '
|
||||
'rel="import" instead of rel="$rel".', node.sourceSpan);
|
||||
}
|
||||
|
||||
var href = node.attributes['href'];
|
||||
if (href == null || href == '') {
|
||||
_messages.warning('link rel="$rel" missing href.',
|
||||
node.sourceSpan);
|
||||
return;
|
||||
}
|
||||
|
||||
bool isStyleSheet = rel == 'stylesheet';
|
||||
var urlInfo = UrlInfo.resolve(href, _fileInfo.inputUrl, node.sourceSpan,
|
||||
_packageRoot, _messages, ignoreAbsolute: isStyleSheet);
|
||||
if (urlInfo == null) return;
|
||||
if (isStyleSheet) {
|
||||
_fileInfo.styleSheetHrefs.add(urlInfo);
|
||||
} else {
|
||||
_fileInfo.componentLinks.add(urlInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void visitElementElement(Element node) {
|
||||
// TODO(jmesserly): what do we do in this case? It seems like an <element>
|
||||
// inside a Shadow DOM should be scoped to that <template> tag, and not
|
||||
// visible from the outside.
|
||||
if (_currentInfo is ComponentInfo) {
|
||||
_messages.error('Nested component definitions are not yet supported.',
|
||||
node.sourceSpan);
|
||||
return;
|
||||
}
|
||||
|
||||
var tagName = node.attributes['name'];
|
||||
var extendsTag = node.attributes['extends'];
|
||||
|
||||
if (tagName == null) {
|
||||
_messages.error('Missing tag name of the component. Please include an '
|
||||
'attribute like \'name="your-tag-name"\'.',
|
||||
node.sourceSpan);
|
||||
return;
|
||||
}
|
||||
|
||||
var component = new ComponentInfo(node, _fileInfo, tagName, extendsTag);
|
||||
_fileInfo.declaredComponents.add(component);
|
||||
_addComponent(component);
|
||||
|
||||
var lastInfo = _currentInfo;
|
||||
_currentInfo = component;
|
||||
super.visitElement(node);
|
||||
_currentInfo = lastInfo;
|
||||
}
|
||||
|
||||
/** Adds a component's tag name to the global list. */
|
||||
void _addComponent(ComponentInfo component) {
|
||||
var existing = _global.components[component.tagName];
|
||||
if (existing != null) {
|
||||
if (existing.hasConflict) {
|
||||
// No need to report a second error for the same name.
|
||||
return;
|
||||
}
|
||||
|
||||
existing.hasConflict = true;
|
||||
|
||||
_messages.error('duplicate custom element definition for '
|
||||
'"${component.tagName}".', existing.sourceSpan);
|
||||
_messages.error('duplicate custom element definition for '
|
||||
'"${component.tagName}" (second location).', component.sourceSpan);
|
||||
} else {
|
||||
_global.components[component.tagName] = component;
|
||||
}
|
||||
}
|
||||
|
||||
void visitScriptElement(Element node) {
|
||||
var scriptType = node.attributes['type'];
|
||||
var src = node.attributes["src"];
|
||||
|
||||
if (scriptType == null) {
|
||||
// Note: in html5 leaving off type= is fine, but it defaults to
|
||||
// text/javascript. Because this might be a common error, we warn about it
|
||||
// in two cases:
|
||||
// * an inline script tag in a web component
|
||||
// * a script src= if the src file ends in .dart (component or not)
|
||||
//
|
||||
// The hope is that neither of these cases should break existing valid
|
||||
// code, but that they'll help component authors avoid having their Dart
|
||||
// code accidentally interpreted as JavaScript by the browser.
|
||||
if (src == null && _currentInfo is ComponentInfo) {
|
||||
_messages.warning('script tag in component with no type will '
|
||||
'be treated as JavaScript. Did you forget type="application/dart"?',
|
||||
node.sourceSpan);
|
||||
}
|
||||
if (src != null && src.endsWith('.dart')) {
|
||||
_messages.warning('script tag with .dart source file but no type will '
|
||||
'be treated as JavaScript. Did you forget type="application/dart"?',
|
||||
node.sourceSpan);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (scriptType != 'application/dart') {
|
||||
if (_currentInfo is ComponentInfo) {
|
||||
// TODO(jmesserly): this warning should not be here, but our compiler
|
||||
// does the wrong thing and it could cause surprising behavior, so let
|
||||
// the user know! See issue #340 for more info.
|
||||
// What we should be doing: leave JS component untouched by compiler.
|
||||
_messages.warning('our custom element implementation does not support '
|
||||
'JavaScript components yet. If this is affecting you please let us '
|
||||
'know at https://github.com/dart-lang/web-ui/issues/340.',
|
||||
node.sourceSpan);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (src != null) {
|
||||
if (!src.endsWith('.dart')) {
|
||||
_messages.warning('"application/dart" scripts should '
|
||||
'use the .dart file extension.',
|
||||
node.sourceSpan);
|
||||
}
|
||||
|
||||
if (node.innerHtml.trim() != '') {
|
||||
_messages.error('script tag has "src" attribute and also has script '
|
||||
'text.', node.sourceSpan);
|
||||
}
|
||||
|
||||
if (_currentInfo.codeAttached) {
|
||||
_tooManyScriptsError(node);
|
||||
} else {
|
||||
_currentInfo.externalFile = UrlInfo.resolve(src, _fileInfo.inputUrl,
|
||||
node.sourceSpan, _packageRoot, _messages);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.nodes.length == 0) return;
|
||||
|
||||
// I don't think the html5 parser will emit a tree with more than
|
||||
// one child of <script>
|
||||
assert(node.nodes.length == 1);
|
||||
Text text = node.nodes[0];
|
||||
|
||||
if (_currentInfo.codeAttached) {
|
||||
_tooManyScriptsError(node);
|
||||
} else if (_currentInfo == _fileInfo && !_fileInfo.isEntryPoint) {
|
||||
_messages.warning('top-level dart code is ignored on '
|
||||
' HTML pages that define components, but are not the entry HTML '
|
||||
'file.', node.sourceSpan);
|
||||
} else {
|
||||
_currentInfo.inlinedCode = parseDartCode(
|
||||
_currentInfo.dartCodeUrl.resolvedPath, text.value,
|
||||
text.sourceSpan.start);
|
||||
if (_currentInfo.userCode.partOf != null) {
|
||||
_messages.error('expected a library, not a part.',
|
||||
node.sourceSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _tooManyScriptsError(Node node) {
|
||||
var location = _currentInfo is ComponentInfo ?
|
||||
'a custom element declaration' : 'the top-level HTML page';
|
||||
|
||||
_messages.error('there should be only one dart script tag in $location.',
|
||||
node.sourceSpan);
|
||||
}
|
||||
}
|
770
pkg/polymer/lib/src/compiler.dart
Normal file
770
pkg/polymer/lib/src/compiler.dart
Normal file
|
@ -0,0 +1,770 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library compiler;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection' show SplayTreeMap;
|
||||
import 'dart:json' as json;
|
||||
|
||||
import 'package:analyzer_experimental/src/generated/ast.dart' show Directive, UriBasedDirective;
|
||||
import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString;
|
||||
import 'package:html5lib/dom.dart';
|
||||
import 'package:html5lib/parser.dart';
|
||||
import 'package:observe/transform.dart' show transformObservables;
|
||||
import 'package:source_maps/span.dart' show Span;
|
||||
import 'package:source_maps/refactor.dart' show TextEditTransaction;
|
||||
import 'package:source_maps/printer.dart';
|
||||
|
||||
import 'analyzer.dart';
|
||||
import 'css_analyzer.dart' show analyzeCss, findUrlsImported,
|
||||
findImportsInStyleSheet, parseCss;
|
||||
import 'css_emitters.dart' show rewriteCssUris,
|
||||
emitComponentStyleSheet, emitOriginalCss, emitStyleSheet;
|
||||
import 'dart_parser.dart';
|
||||
import 'emitters.dart';
|
||||
import 'file_system.dart';
|
||||
import 'files.dart';
|
||||
import 'info.dart';
|
||||
import 'messages.dart';
|
||||
import 'compiler_options.dart';
|
||||
import 'paths.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/**
|
||||
* Parses an HTML file [contents] and returns a DOM-like tree.
|
||||
* Note that [contents] will be a [String] if coming from a browser-based
|
||||
* [FileSystem], or it will be a [List<int>] if running on the command line.
|
||||
*
|
||||
* Adds emitted error/warning to [messages], if [messages] is supplied.
|
||||
*/
|
||||
Document parseHtml(contents, String sourcePath, Messages messages) {
|
||||
var parser = new HtmlParser(contents, generateSpans: true,
|
||||
sourceUrl: sourcePath);
|
||||
var document = parser.parse();
|
||||
|
||||
// Note: errors aren't fatal in HTML (unless strict mode is on).
|
||||
// So just print them as warnings.
|
||||
for (var e in parser.errors) {
|
||||
messages.warning(e.message, e.span);
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
/** Compiles an application written with Dart web components. */
|
||||
class Compiler {
|
||||
final FileSystem fileSystem;
|
||||
final CompilerOptions options;
|
||||
final List<SourceFile> files = <SourceFile>[];
|
||||
final List<OutputFile> output = <OutputFile>[];
|
||||
|
||||
String _mainPath;
|
||||
String _resetCssFile;
|
||||
StyleSheet _cssResetStyleSheet;
|
||||
PathMapper _pathMapper;
|
||||
Messages _messages;
|
||||
|
||||
FutureGroup _tasks;
|
||||
Set _processed;
|
||||
|
||||
/** Information about source [files] given their href. */
|
||||
final Map<String, FileInfo> info = new SplayTreeMap<String, FileInfo>();
|
||||
final _edits = new Map<DartCodeInfo, TextEditTransaction>();
|
||||
|
||||
final GlobalInfo global = new GlobalInfo();
|
||||
|
||||
/** Creates a compiler with [options] using [fileSystem]. */
|
||||
Compiler(this.fileSystem, this.options, this._messages) {
|
||||
_mainPath = options.inputFile;
|
||||
var mainDir = path.dirname(_mainPath);
|
||||
var baseDir = options.baseDir != null ? options.baseDir : mainDir;
|
||||
var outputDir = options.outputDir != null ? options.outputDir : mainDir;
|
||||
var packageRoot = options.packageRoot != null ? options.packageRoot
|
||||
: path.join(path.dirname(_mainPath), 'packages');
|
||||
|
||||
if (options.resetCssFile != null) {
|
||||
_resetCssFile = options.resetCssFile;
|
||||
if (path.isRelative(_resetCssFile)) {
|
||||
// If CSS reset file path is relative from our current path.
|
||||
_resetCssFile = path.resolve(_resetCssFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize paths - all should be relative or absolute paths.
|
||||
if (path.isAbsolute(_mainPath) || path.isAbsolute(baseDir) ||
|
||||
path.isAbsolute(outputDir) || path.isAbsolute(packageRoot)) {
|
||||
if (path.isRelative(_mainPath)) _mainPath = path.resolve(_mainPath);
|
||||
if (path.isRelative(baseDir)) baseDir = path.resolve(baseDir);
|
||||
if (path.isRelative(outputDir)) outputDir = path.resolve(outputDir);
|
||||
if (path.isRelative(packageRoot)) {
|
||||
packageRoot = path.resolve(packageRoot);
|
||||
}
|
||||
}
|
||||
_pathMapper = new PathMapper(
|
||||
baseDir, outputDir, packageRoot, options.forceMangle,
|
||||
options.rewriteUrls);
|
||||
}
|
||||
|
||||
/** Compile the application starting from the given input file. */
|
||||
Future run() {
|
||||
if (path.basename(_mainPath).endsWith('.dart')) {
|
||||
_messages.error("Please provide an HTML file as your entry point.",
|
||||
null);
|
||||
return new Future.value(null);
|
||||
}
|
||||
return _parseAndDiscover(_mainPath).then((_) {
|
||||
_analyze();
|
||||
|
||||
// Analyze all CSS files.
|
||||
_time('Analyzed Style Sheets', '', () =>
|
||||
analyzeCss(_pathMapper.packageRoot, files, info,
|
||||
global.pseudoElements, _messages,
|
||||
warningsAsErrors: options.warningsAsErrors));
|
||||
|
||||
// TODO(jmesserly): need to go through our errors, and figure out if some
|
||||
// of them should be warnings instead.
|
||||
if (_messages.hasErrors || options.analysisOnly) return;
|
||||
_transformDart();
|
||||
_emit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously parse [inputFile] and transitively discover web components
|
||||
* to load and parse. Returns a future that completes when all files are
|
||||
* processed.
|
||||
*/
|
||||
Future _parseAndDiscover(String inputFile) {
|
||||
_tasks = new FutureGroup();
|
||||
_processed = new Set();
|
||||
_processed.add(inputFile);
|
||||
_tasks.add(_parseHtmlFile(new UrlInfo(inputFile, inputFile, null)));
|
||||
return _tasks.future;
|
||||
}
|
||||
|
||||
void _processHtmlFile(UrlInfo inputUrl, SourceFile file) {
|
||||
if (file == null) return;
|
||||
|
||||
bool isEntryPoint = _processed.length == 1;
|
||||
|
||||
files.add(file);
|
||||
|
||||
var fileInfo = _time('Analyzed definitions', inputUrl.url, () {
|
||||
return analyzeDefinitions(global, inputUrl, file.document,
|
||||
_pathMapper.packageRoot, _messages, isEntryPoint: isEntryPoint);
|
||||
});
|
||||
info[inputUrl.resolvedPath] = fileInfo;
|
||||
|
||||
if (isEntryPoint && _resetCssFile != null) {
|
||||
_processed.add(_resetCssFile);
|
||||
_tasks.add(_parseCssFile(new UrlInfo(_resetCssFile, _resetCssFile,
|
||||
null)));
|
||||
}
|
||||
|
||||
_setOutputFilenames(fileInfo);
|
||||
_processImports(fileInfo);
|
||||
|
||||
// Load component files referenced by [file].
|
||||
for (var link in fileInfo.componentLinks) {
|
||||
_loadFile(link, _parseHtmlFile);
|
||||
}
|
||||
|
||||
// Load stylesheet files referenced by [file].
|
||||
for (var link in fileInfo.styleSheetHrefs) {
|
||||
_loadFile(link, _parseCssFile);
|
||||
}
|
||||
|
||||
// Load .dart files being referenced in the page.
|
||||
_loadFile(fileInfo.externalFile, _parseDartFile);
|
||||
|
||||
// Process any @imports inside of a <style> tag.
|
||||
var urlInfos = findUrlsImported(fileInfo, fileInfo.inputUrl,
|
||||
_pathMapper.packageRoot, file.document, _messages, options);
|
||||
for (var urlInfo in urlInfos) {
|
||||
_loadFile(urlInfo, _parseCssFile);
|
||||
}
|
||||
|
||||
// Load .dart files being referenced in components.
|
||||
for (var component in fileInfo.declaredComponents) {
|
||||
if (component.externalFile != null) {
|
||||
_loadFile(component.externalFile, _parseDartFile);
|
||||
} else if (component.userCode != null) {
|
||||
_processImports(component);
|
||||
}
|
||||
|
||||
// Process any @imports inside of the <style> tag in a component.
|
||||
var urlInfos = findUrlsImported(component,
|
||||
component.declaringFile.inputUrl, _pathMapper.packageRoot,
|
||||
component.element, _messages, options);
|
||||
for (var urlInfo in urlInfos) {
|
||||
_loadFile(urlInfo, _parseCssFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to load [urlInfo] and parse it using [loadAndParse] if it
|
||||
* hasn't been loaded before.
|
||||
*/
|
||||
void _loadFile(UrlInfo urlInfo, Future loadAndParse(UrlInfo inputUrl)) {
|
||||
if (urlInfo == null) return;
|
||||
var resolvedPath = urlInfo.resolvedPath;
|
||||
if (!_processed.contains(resolvedPath)) {
|
||||
_processed.add(resolvedPath);
|
||||
_tasks.add(loadAndParse(urlInfo));
|
||||
}
|
||||
}
|
||||
|
||||
void _setOutputFilenames(FileInfo fileInfo) {
|
||||
var filePath = fileInfo.dartCodeUrl.resolvedPath;
|
||||
fileInfo.outputFilename = _pathMapper.mangle(path.basename(filePath),
|
||||
'.dart', path.extension(filePath) == '.html');
|
||||
for (var component in fileInfo.declaredComponents) {
|
||||
var externalFile = component.externalFile;
|
||||
var name = null;
|
||||
if (externalFile != null) {
|
||||
name = _pathMapper.mangle(
|
||||
path.basename(externalFile.resolvedPath), '.dart');
|
||||
} else {
|
||||
var declaringFile = component.declaringFile;
|
||||
var prefix = path.basename(declaringFile.inputUrl.resolvedPath);
|
||||
if (declaringFile.declaredComponents.length == 1
|
||||
&& !declaringFile.codeAttached && !declaringFile.isEntryPoint) {
|
||||
name = _pathMapper.mangle(prefix, '.dart', true);
|
||||
} else {
|
||||
var componentName = component.tagName.replaceAll('-', '_');
|
||||
name = _pathMapper.mangle('${prefix}_$componentName', '.dart', true);
|
||||
}
|
||||
}
|
||||
component.outputFilename = name;
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse an HTML file. */
|
||||
Future _parseHtmlFile(UrlInfo inputUrl) {
|
||||
if (!_pathMapper.checkInputPath(inputUrl, _messages)) {
|
||||
return new Future<SourceFile>.value(null);
|
||||
}
|
||||
var filePath = inputUrl.resolvedPath;
|
||||
return fileSystem.readTextOrBytes(filePath)
|
||||
.catchError((e) => _readError(e, inputUrl))
|
||||
.then((source) {
|
||||
if (source == null) return;
|
||||
var file = new SourceFile(filePath);
|
||||
file.document = _time('Parsed', filePath,
|
||||
() => parseHtml(source, filePath, _messages));
|
||||
_processHtmlFile(inputUrl, file);
|
||||
});
|
||||
}
|
||||
|
||||
/** Parse a Dart file. */
|
||||
Future _parseDartFile(UrlInfo inputUrl) {
|
||||
if (!_pathMapper.checkInputPath(inputUrl, _messages)) {
|
||||
return new Future<SourceFile>.value(null);
|
||||
}
|
||||
var filePath = inputUrl.resolvedPath;
|
||||
return fileSystem.readText(filePath)
|
||||
.catchError((e) => _readError(e, inputUrl))
|
||||
.then((code) {
|
||||
if (code == null) return;
|
||||
var file = new SourceFile(filePath, type: SourceFile.DART);
|
||||
file.code = code;
|
||||
_processDartFile(inputUrl, file);
|
||||
});
|
||||
}
|
||||
|
||||
/** Parse a stylesheet file. */
|
||||
Future _parseCssFile(UrlInfo inputUrl) {
|
||||
if (!options.emulateScopedCss ||
|
||||
!_pathMapper.checkInputPath(inputUrl, _messages)) {
|
||||
return new Future<SourceFile>.value(null);
|
||||
}
|
||||
var filePath = inputUrl.resolvedPath;
|
||||
return fileSystem.readText(filePath)
|
||||
.catchError((e) => _readError(e, inputUrl, isWarning: true))
|
||||
.then((code) {
|
||||
if (code == null) return;
|
||||
var file = new SourceFile(filePath, type: SourceFile.STYLESHEET);
|
||||
file.code = code;
|
||||
_processCssFile(inputUrl, file);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
SourceFile _readError(error, UrlInfo inputUrl, {isWarning: false}) {
|
||||
var message = 'unable to open file "${inputUrl.resolvedPath}"';
|
||||
if (options.verbose) {
|
||||
message = '$message. original message:\n $error';
|
||||
}
|
||||
if (isWarning) {
|
||||
_messages.warning(message, inputUrl.sourceSpan);
|
||||
} else {
|
||||
_messages.error(message, inputUrl.sourceSpan);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _processDartFile(UrlInfo inputUrl, SourceFile dartFile) {
|
||||
if (dartFile == null) return;
|
||||
|
||||
files.add(dartFile);
|
||||
|
||||
var resolvedPath = inputUrl.resolvedPath;
|
||||
var fileInfo = new FileInfo(inputUrl);
|
||||
info[resolvedPath] = fileInfo;
|
||||
fileInfo.inlinedCode = parseDartCode(resolvedPath, dartFile.code);
|
||||
fileInfo.outputFilename =
|
||||
_pathMapper.mangle(path.basename(resolvedPath), '.dart', false);
|
||||
|
||||
_processImports(fileInfo);
|
||||
}
|
||||
|
||||
void _processImports(LibraryInfo library) {
|
||||
if (library.userCode == null) return;
|
||||
|
||||
for (var directive in library.userCode.directives) {
|
||||
_loadFile(_getDirectiveUrlInfo(library, directive), _parseDartFile);
|
||||
}
|
||||
}
|
||||
|
||||
void _processCssFile(UrlInfo inputUrl, SourceFile cssFile) {
|
||||
if (cssFile == null) return;
|
||||
|
||||
files.add(cssFile);
|
||||
|
||||
var fileInfo = new FileInfo(inputUrl);
|
||||
info[inputUrl.resolvedPath] = fileInfo;
|
||||
|
||||
var styleSheet = parseCss(cssFile.code, _messages, options);
|
||||
if (inputUrl.url == _resetCssFile) {
|
||||
_cssResetStyleSheet = styleSheet;
|
||||
} else if (styleSheet != null) {
|
||||
_resolveStyleSheetImports(inputUrl, cssFile.path, styleSheet);
|
||||
fileInfo.styleSheets.add(styleSheet);
|
||||
}
|
||||
}
|
||||
|
||||
/** Load and parse all style sheets referenced with an @imports. */
|
||||
void _resolveStyleSheetImports(UrlInfo inputUrl, String processingFile,
|
||||
StyleSheet styleSheet) {
|
||||
var urlInfos = _time('CSS imports', processingFile, () =>
|
||||
findImportsInStyleSheet(styleSheet, _pathMapper.packageRoot, inputUrl,
|
||||
_messages));
|
||||
|
||||
for (var urlInfo in urlInfos) {
|
||||
if (urlInfo == null) break;
|
||||
// Load any @imported stylesheet files referenced in this style sheet.
|
||||
_loadFile(urlInfo, _parseCssFile);
|
||||
}
|
||||
}
|
||||
|
||||
String _directiveUri(Directive directive) {
|
||||
var uriDirective = (directive as UriBasedDirective).uri;
|
||||
return (uriDirective as dynamic).value;
|
||||
}
|
||||
|
||||
UrlInfo _getDirectiveUrlInfo(LibraryInfo library, Directive directive) {
|
||||
var uri = _directiveUri(directive);
|
||||
if (uri.startsWith('dart:')) return null;
|
||||
if (uri.startsWith('package:') && uri.startsWith('package:polymer/')) {
|
||||
// Don't process our own package -- we'll implement @observable manually.
|
||||
return null;
|
||||
}
|
||||
|
||||
var span = library.userCode.sourceFile.span(
|
||||
directive.offset, directive.end);
|
||||
return UrlInfo.resolve(uri, library.dartCodeUrl, span,
|
||||
_pathMapper.packageRoot, _messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform Dart source code.
|
||||
* Currently, the only transformation is [transformObservables].
|
||||
* Calls _emitModifiedDartFiles to write the transformed files.
|
||||
*/
|
||||
void _transformDart() {
|
||||
var libraries = _findAllDartLibraries();
|
||||
|
||||
var transformed = [];
|
||||
for (var lib in libraries) {
|
||||
var userCode = lib.userCode;
|
||||
var transaction = transformObservables(userCode.compilationUnit,
|
||||
userCode.sourceFile, userCode.code, _messages);
|
||||
if (transaction != null) {
|
||||
_edits[lib.userCode] = transaction;
|
||||
if (transaction.hasEdits) {
|
||||
transformed.add(lib);
|
||||
} else if (lib.htmlFile != null) {
|
||||
// All web components will be transformed too. Track that.
|
||||
transformed.add(lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_findModifiedDartFiles(libraries, transformed);
|
||||
|
||||
libraries.forEach(_fixImports);
|
||||
|
||||
_emitModifiedDartFiles(libraries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all Dart code libraries.
|
||||
* Each library will have [LibraryInfo.inlinedCode] that is non-null.
|
||||
* Also each inlinedCode will be unique.
|
||||
*/
|
||||
List<LibraryInfo> _findAllDartLibraries() {
|
||||
var libs = <LibraryInfo>[];
|
||||
void _addLibrary(LibraryInfo lib) {
|
||||
if (lib.inlinedCode != null) libs.add(lib);
|
||||
}
|
||||
|
||||
for (var sourceFile in files) {
|
||||
var file = info[sourceFile.path];
|
||||
_addLibrary(file);
|
||||
file.declaredComponents.forEach(_addLibrary);
|
||||
}
|
||||
|
||||
// Assert that each file path is unique.
|
||||
assert(_uniquePaths(libs));
|
||||
return libs;
|
||||
}
|
||||
|
||||
bool _uniquePaths(List<LibraryInfo> libs) {
|
||||
var seen = new Set();
|
||||
for (var lib in libs) {
|
||||
if (seen.contains(lib.inlinedCode)) {
|
||||
throw new StateError('internal error: '
|
||||
'duplicate user code for ${lib.dartCodeUrl.resolvedPath}.'
|
||||
' Files were: $files');
|
||||
}
|
||||
seen.add(lib.inlinedCode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue modified Dart files to be written.
|
||||
* This will not write files that are handled by [WebComponentEmitter] and
|
||||
* [EntryPointEmitter].
|
||||
*/
|
||||
void _emitModifiedDartFiles(List<LibraryInfo> libraries) {
|
||||
for (var lib in libraries) {
|
||||
// Components will get emitted by WebComponentEmitter, and the
|
||||
// entry point will get emitted by MainPageEmitter.
|
||||
// So we only need to worry about other .dart files.
|
||||
if (lib.modified && lib is FileInfo &&
|
||||
lib.htmlFile == null && !lib.isEntryPoint) {
|
||||
var transaction = _edits[lib.userCode];
|
||||
|
||||
// Save imports that were modified by _fixImports.
|
||||
for (var d in lib.userCode.directives) {
|
||||
transaction.edit(d.offset, d.end, d.toString());
|
||||
}
|
||||
|
||||
if (!lib.userCode.isPart) {
|
||||
var pos = lib.userCode.firstPartOffset;
|
||||
// Note: we use a different prefix than "autogenerated" to make
|
||||
// ChangeRecord unambiguous. Otherwise it would be imported by this
|
||||
// and polymer, resulting in a collision.
|
||||
// TODO(jmesserly): only generate this for libraries that need it.
|
||||
transaction.edit(pos, pos, "\nimport "
|
||||
"'package:observe/observe.dart' as __observe;\n");
|
||||
}
|
||||
_emitFileAndSourceMaps(lib, transaction.commit(), lib.dartCodeUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method computes which Dart files have been modified, starting
|
||||
* from [transformed] and marking recursively through all files that import
|
||||
* the modified files.
|
||||
*/
|
||||
void _findModifiedDartFiles(List<LibraryInfo> libraries,
|
||||
List<FileInfo> transformed) {
|
||||
|
||||
if (transformed.length == 0) return;
|
||||
|
||||
// Compute files that reference each file, then use this information to
|
||||
// flip the modified bit transitively. This is a lot simpler than trying
|
||||
// to compute it the other way because of circular references.
|
||||
for (var lib in libraries) {
|
||||
for (var directive in lib.userCode.directives) {
|
||||
var importPath = _getDirectiveUrlInfo(lib, directive);
|
||||
if (importPath == null) continue;
|
||||
|
||||
var importInfo = info[importPath.resolvedPath];
|
||||
if (importInfo != null) {
|
||||
importInfo.referencedBy.add(lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Propegate the modified bit to anything that references a modified file.
|
||||
void setModified(LibraryInfo library) {
|
||||
if (library.modified) return;
|
||||
library.modified = true;
|
||||
library.referencedBy.forEach(setModified);
|
||||
}
|
||||
transformed.forEach(setModified);
|
||||
|
||||
for (var lib in libraries) {
|
||||
// We don't need this anymore, so free it.
|
||||
lib.referencedBy = null;
|
||||
}
|
||||
}
|
||||
|
||||
void _fixImports(LibraryInfo library) {
|
||||
// Fix imports. Modified files must use the generated path, otherwise
|
||||
// we need to make the path relative to the input.
|
||||
for (var directive in library.userCode.directives) {
|
||||
var importPath = _getDirectiveUrlInfo(library, directive);
|
||||
if (importPath == null) continue;
|
||||
var importInfo = info[importPath.resolvedPath];
|
||||
if (importInfo == null) continue;
|
||||
|
||||
String newUri = null;
|
||||
if (importInfo.modified) {
|
||||
// Use the generated URI for this file.
|
||||
newUri = _pathMapper.importUrlFor(library, importInfo);
|
||||
} else if (options.rewriteUrls) {
|
||||
// Get the relative path to the input file.
|
||||
newUri = _pathMapper.transformUrl(
|
||||
library.dartCodeUrl.resolvedPath, directive.uri.value);
|
||||
}
|
||||
if (newUri != null) {
|
||||
directive.uri = createStringLiteral(newUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Run the analyzer on every input html file. */
|
||||
void _analyze() {
|
||||
var uniqueIds = new IntIterator();
|
||||
for (var file in files) {
|
||||
if (file.isHtml) {
|
||||
_time('Analyzed contents', file.path, () =>
|
||||
analyzeFile(file, info, uniqueIds, global, _messages,
|
||||
options.emulateScopedCss));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Emit the generated code corresponding to each input file. */
|
||||
void _emit() {
|
||||
for (var file in files) {
|
||||
if (file.isDart || file.isStyleSheet) continue;
|
||||
_time('Codegen', file.path, () {
|
||||
var fileInfo = info[file.path];
|
||||
_emitComponents(fileInfo);
|
||||
});
|
||||
}
|
||||
|
||||
var entryPoint = files[0];
|
||||
assert(info[entryPoint.path].isEntryPoint);
|
||||
_emitMainDart(entryPoint);
|
||||
_emitMainHtml(entryPoint);
|
||||
|
||||
assert(_unqiueOutputs());
|
||||
}
|
||||
|
||||
bool _unqiueOutputs() {
|
||||
var seen = new Set();
|
||||
for (var file in output) {
|
||||
if (seen.contains(file.path)) {
|
||||
throw new StateError('internal error: '
|
||||
'duplicate output file ${file.path}. Files were: $output');
|
||||
}
|
||||
seen.add(file.path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Emit the main .dart file. */
|
||||
void _emitMainDart(SourceFile file) {
|
||||
var fileInfo = info[file.path];
|
||||
|
||||
var codeInfo = fileInfo.userCode;
|
||||
if (codeInfo != null) {
|
||||
var printer = new NestedPrinter(0);
|
||||
if (codeInfo.libraryName == null) {
|
||||
printer.addLine('library ${fileInfo.libraryName};');
|
||||
}
|
||||
printer.add(codeInfo.code);
|
||||
_emitFileAndSourceMaps(fileInfo, printer, fileInfo.dartCodeUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jmesserly): refactor this out of Compiler.
|
||||
/** Generate an html file with the (trimmed down) main html page. */
|
||||
void _emitMainHtml(SourceFile file) {
|
||||
var fileInfo = info[file.path];
|
||||
|
||||
var bootstrapName = '${path.basename(file.path)}_bootstrap.dart';
|
||||
var bootstrapPath = path.join(path.dirname(file.path), bootstrapName);
|
||||
var bootstrapOutPath = _pathMapper.outputPath(bootstrapPath, '');
|
||||
var bootstrapOutName = path.basename(bootstrapOutPath);
|
||||
var bootstrapInfo = new FileInfo(new UrlInfo('', bootstrapPath, null));
|
||||
var printer = generateBootstrapCode(bootstrapInfo, fileInfo, global,
|
||||
_pathMapper, options);
|
||||
printer.build(bootstrapOutPath);
|
||||
output.add(new OutputFile(
|
||||
bootstrapOutPath, printer.text, source: file.path));
|
||||
|
||||
var document = file.document;
|
||||
var hasCss = _emitAllCss();
|
||||
transformMainHtml(document, fileInfo, _pathMapper, hasCss,
|
||||
options.rewriteUrls, _messages, global, bootstrapOutName);
|
||||
output.add(new OutputFile(_pathMapper.outputPath(file.path, '.html'),
|
||||
document.outerHtml, source: file.path));
|
||||
}
|
||||
|
||||
// TODO(jmesserly): refactor this and other CSS related transforms out of
|
||||
// Compiler.
|
||||
/**
|
||||
* Generate an CSS file for all style sheets (main and components).
|
||||
* Returns true if a file was generated, otherwise false.
|
||||
*/
|
||||
bool _emitAllCss() {
|
||||
if (!options.emulateScopedCss) return false;
|
||||
|
||||
var buff = new StringBuffer();
|
||||
|
||||
// Emit all linked style sheet files first.
|
||||
for (var file in files) {
|
||||
var css = new StringBuffer();
|
||||
var fileInfo = info[file.path];
|
||||
if (file.isStyleSheet) {
|
||||
for (var styleSheet in fileInfo.styleSheets) {
|
||||
// Translate any URIs in CSS.
|
||||
rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath,
|
||||
options.rewriteUrls, styleSheet);
|
||||
css.write(
|
||||
'/* Auto-generated from style sheet href = ${file.path} */\n'
|
||||
'/* DO NOT EDIT. */\n\n');
|
||||
css.write(emitStyleSheet(styleSheet, fileInfo));
|
||||
css.write('\n\n');
|
||||
}
|
||||
|
||||
// Emit the linked style sheet in the output directory.
|
||||
if (fileInfo.inputUrl.url != _resetCssFile) {
|
||||
var outCss = _pathMapper.outputPath(fileInfo.inputUrl.resolvedPath,
|
||||
'');
|
||||
output.add(new OutputFile(outCss, css.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Emit all CSS for each component (style scoped).
|
||||
for (var file in files) {
|
||||
if (file.isHtml) {
|
||||
var fileInfo = info[file.path];
|
||||
for (var component in fileInfo.declaredComponents) {
|
||||
for (var styleSheet in component.styleSheets) {
|
||||
// Translate any URIs in CSS.
|
||||
rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath,
|
||||
options.rewriteUrls, styleSheet);
|
||||
|
||||
if (buff.isEmpty) {
|
||||
buff.write(
|
||||
'/* Auto-generated from components style tags. */\n'
|
||||
'/* DO NOT EDIT. */\n\n');
|
||||
}
|
||||
buff.write(
|
||||
'/* ==================================================== \n'
|
||||
' Component ${component.tagName} stylesheet \n'
|
||||
' ==================================================== */\n');
|
||||
|
||||
var tagName = component.tagName;
|
||||
if (!component.hasAuthorStyles) {
|
||||
if (_cssResetStyleSheet != null) {
|
||||
// If component doesn't have apply-author-styles then we need to
|
||||
// reset the CSS the styles for the component (if css-reset file
|
||||
// option was passed).
|
||||
buff.write('\n/* Start CSS Reset */\n');
|
||||
var style;
|
||||
if (options.emulateScopedCss) {
|
||||
style = emitComponentStyleSheet(_cssResetStyleSheet, tagName);
|
||||
} else {
|
||||
style = emitOriginalCss(_cssResetStyleSheet);
|
||||
}
|
||||
buff.write(style);
|
||||
buff.write('/* End CSS Reset */\n\n');
|
||||
}
|
||||
}
|
||||
if (options.emulateScopedCss) {
|
||||
buff.write(emitComponentStyleSheet(styleSheet, tagName));
|
||||
} else {
|
||||
buff.write(emitOriginalCss(styleSheet));
|
||||
}
|
||||
buff.write('\n\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buff.isEmpty) return false;
|
||||
|
||||
var cssPath = _pathMapper.outputPath(_mainPath, '.css', true);
|
||||
output.add(new OutputFile(cssPath, buff.toString()));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Emits the Dart code for all components in [fileInfo]. */
|
||||
void _emitComponents(FileInfo fileInfo) {
|
||||
for (var component in fileInfo.declaredComponents) {
|
||||
// TODO(terry): Handle more than one stylesheet per component
|
||||
if (component.styleSheets.length > 1 && options.emulateScopedCss) {
|
||||
var span = component.externalFile != null
|
||||
? component.externalFile.sourceSpan : null;
|
||||
_messages.warning(
|
||||
'Component has more than one stylesheet - first stylesheet used.',
|
||||
span);
|
||||
}
|
||||
var printer = emitPolymerElement(
|
||||
component, _pathMapper, _edits[component.userCode], options);
|
||||
_emitFileAndSourceMaps(component, printer, component.externalFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a file that was created using [NestedPrinter] and it's corresponding
|
||||
* source map file.
|
||||
*/
|
||||
void _emitFileAndSourceMaps(
|
||||
LibraryInfo lib, NestedPrinter printer, UrlInfo dartCodeUrl) {
|
||||
// Bail if we had an error generating the code for the file.
|
||||
if (printer == null) return;
|
||||
|
||||
var libPath = _pathMapper.outputLibraryPath(lib);
|
||||
var dir = path.dirname(libPath);
|
||||
var filename = path.basename(libPath);
|
||||
printer.add('\n//# sourceMappingURL=$filename.map');
|
||||
printer.build(libPath);
|
||||
var sourcePath = dartCodeUrl != null ? dartCodeUrl.resolvedPath : null;
|
||||
output.add(new OutputFile(libPath, printer.text, source: sourcePath));
|
||||
// Fix-up the paths in the source map file
|
||||
var sourceMap = json.parse(printer.map);
|
||||
var urls = sourceMap['sources'];
|
||||
for (int i = 0; i < urls.length; i++) {
|
||||
urls[i] = path.relative(urls[i], from: dir);
|
||||
}
|
||||
output.add(new OutputFile(path.join(dir, '$filename.map'),
|
||||
json.stringify(sourceMap)));
|
||||
}
|
||||
|
||||
_time(String logMessage, String filePath, callback(),
|
||||
{bool printTime: false}) {
|
||||
var message = new StringBuffer();
|
||||
message.write(logMessage);
|
||||
var filename = path.basename(filePath);
|
||||
for (int i = (60 - logMessage.length - filename.length); i > 0 ; i--) {
|
||||
message.write(' ');
|
||||
}
|
||||
message.write(filename);
|
||||
return time(message.toString(), callback,
|
||||
printTime: options.verbose || printTime);
|
||||
}
|
||||
}
|
148
pkg/polymer/lib/src/compiler_options.dart
Normal file
148
pkg/polymer/lib/src/compiler_options.dart
Normal file
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library polymer.src.compiler_options;
|
||||
|
||||
import 'package:args/args.dart';
|
||||
|
||||
class CompilerOptions {
|
||||
/** Report warnings as errors. */
|
||||
final bool warningsAsErrors;
|
||||
|
||||
/** True to show informational messages. The `--verbose` flag. */
|
||||
final bool verbose;
|
||||
|
||||
/** Remove any generated files. */
|
||||
final bool clean;
|
||||
|
||||
/** Whether to use colors to print messages on the terminal. */
|
||||
final bool useColors;
|
||||
|
||||
/** Force mangling any generated name (even when --out is provided). */
|
||||
final bool forceMangle;
|
||||
|
||||
/** Generate component's dart code, but not the main entry point file. */
|
||||
final bool componentsOnly;
|
||||
|
||||
/** File to process by the compiler. */
|
||||
String inputFile;
|
||||
|
||||
/** Directory where all sources are found. */
|
||||
final String baseDir;
|
||||
|
||||
/** Directory where all output will be generated. */
|
||||
final String outputDir;
|
||||
|
||||
/** Directory where to look for 'package:' imports. */
|
||||
final String packageRoot;
|
||||
|
||||
/**
|
||||
* Adjust resource URLs in the output HTML to point back to the original
|
||||
* location in the file system. Commonly this is enabled during development,
|
||||
* but disabled for deployment.
|
||||
*/
|
||||
final bool rewriteUrls;
|
||||
|
||||
/**
|
||||
* Whether to print error messages using the json format understood by the
|
||||
* Dart editor.
|
||||
*/
|
||||
final bool jsonFormat;
|
||||
|
||||
/** Emulate scoped styles using a CSS polyfill. */
|
||||
final bool emulateScopedCss;
|
||||
|
||||
/** Use CSS file for CSS Reset. */
|
||||
final String resetCssFile;
|
||||
|
||||
/** Whether to analyze the input for warnings without generating any code. */
|
||||
final bool analysisOnly;
|
||||
|
||||
// We could make this faster, if it ever matters.
|
||||
factory CompilerOptions() => parse(['']);
|
||||
|
||||
CompilerOptions.fromArgs(ArgResults args)
|
||||
: warningsAsErrors = args['warnings_as_errors'],
|
||||
verbose = args['verbose'],
|
||||
clean = args['clean'],
|
||||
useColors = args['colors'],
|
||||
baseDir = args['basedir'],
|
||||
outputDir = args['out'],
|
||||
packageRoot = args['package-root'],
|
||||
rewriteUrls = args['rewrite-urls'],
|
||||
forceMangle = args['unique_output_filenames'],
|
||||
jsonFormat = args['json_format'],
|
||||
componentsOnly = args['components_only'],
|
||||
emulateScopedCss = args['scoped-css'],
|
||||
resetCssFile = args['css-reset'],
|
||||
analysisOnly = !args['deploy'],
|
||||
inputFile = args.rest.length > 0 ? args.rest[0] : null;
|
||||
|
||||
/**
|
||||
* Returns the compiler options parsed from [arguments]. Set [checkUsage] to
|
||||
* false to suppress checking of correct usage or printing help messages.
|
||||
*/
|
||||
// TODO(sigmund): convert all flags to use dashes instead of underscores
|
||||
static CompilerOptions parse(List<String> arguments,
|
||||
{bool checkUsage: true}) {
|
||||
var parser = new ArgParser()
|
||||
..addFlag('verbose', abbr: 'v')
|
||||
..addFlag('clean', help: 'Remove all generated files',
|
||||
defaultsTo: false, negatable: false)
|
||||
..addFlag('warnings_as_errors', abbr: 'e',
|
||||
help: 'Warnings handled as errors',
|
||||
defaultsTo: false, negatable: false)
|
||||
..addFlag('colors', help: 'Display errors/warnings in colored text',
|
||||
defaultsTo: true)
|
||||
..addFlag('rewrite-urls',
|
||||
help: 'Adjust every resource url to point to the original location in'
|
||||
' the filesystem.\nThis on by default during development and can be'
|
||||
' disabled to make the generated code easier to deploy.',
|
||||
defaultsTo: true)
|
||||
..addFlag('unique_output_filenames', abbr: 'u',
|
||||
help: 'Use unique names for all generated files, so they will not '
|
||||
'have the\nsame name as your input files, even if they are in a'
|
||||
' different directory',
|
||||
defaultsTo: false, negatable: false)
|
||||
..addFlag('json_format',
|
||||
help: 'Print error messsages in a json format easy to parse by tools,'
|
||||
' such as the Dart editor',
|
||||
defaultsTo: false, negatable: false)
|
||||
..addFlag('components_only',
|
||||
help: 'Generate only the code for component classes, do not generate '
|
||||
'HTML files or the main bootstrap code.',
|
||||
defaultsTo: false, negatable: false)
|
||||
..addFlag('scoped-css', help: 'Emulate scoped styles with CSS polyfill',
|
||||
defaultsTo: false)
|
||||
..addOption('css-reset', abbr: 'r', help: 'CSS file used to reset CSS')
|
||||
..addFlag('deploy', help: 'Emit code used for deploying a polymer app,'
|
||||
' if false just show warnings and errors (default)',
|
||||
defaultsTo: false, negatable: false)
|
||||
..addOption('out', abbr: 'o', help: 'Directory where to generate files'
|
||||
' (defaults to the same directory as the source file)')
|
||||
..addOption('basedir', help: 'Base directory where to find all source '
|
||||
'files (defaults to the source file\'s directory)')
|
||||
..addOption('package-root', help: 'Where to find "package:" imports'
|
||||
'(defaults to the "packages/" subdirectory next to the source file)')
|
||||
..addFlag('help', abbr: 'h', help: 'Displays this help message',
|
||||
defaultsTo: false, negatable: false);
|
||||
try {
|
||||
var results = parser.parse(arguments);
|
||||
if (checkUsage && (results['help'] || results.rest.length == 0)) {
|
||||
showUsage(parser);
|
||||
return null;
|
||||
}
|
||||
return new CompilerOptions.fromArgs(results);
|
||||
} on FormatException catch (e) {
|
||||
print(e.message);
|
||||
showUsage(parser);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static showUsage(parser) {
|
||||
print('Usage: dwc [options...] input.html');
|
||||
print(parser.getUsage());
|
||||
}
|
||||
}
|
507
pkg/polymer/lib/src/css_analyzer.dart
Normal file
507
pkg/polymer/lib/src/css_analyzer.dart
Normal file
|
@ -0,0 +1,507 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Portion of the analyzer dealing with CSS sources. */
|
||||
library polymer.src.css_analyzer;
|
||||
|
||||
import 'package:csslib/parser.dart' as css;
|
||||
import 'package:csslib/visitor.dart';
|
||||
import 'package:html5lib/dom.dart';
|
||||
import 'package:html5lib/dom_parsing.dart';
|
||||
|
||||
import 'info.dart';
|
||||
import 'files.dart' show SourceFile;
|
||||
import 'messages.dart';
|
||||
import 'compiler_options.dart';
|
||||
|
||||
void analyzeCss(String packageRoot, List<SourceFile> files,
|
||||
Map<String, FileInfo> info, Map<String, String> pseudoElements,
|
||||
Messages messages, {warningsAsErrors: false}) {
|
||||
var analyzer = new _AnalyzerCss(packageRoot, info, pseudoElements, messages,
|
||||
warningsAsErrors);
|
||||
for (var file in files) analyzer.process(file);
|
||||
analyzer.normalize();
|
||||
}
|
||||
|
||||
class _AnalyzerCss {
|
||||
final String packageRoot;
|
||||
final Map<String, FileInfo> info;
|
||||
final Map<String, String> _pseudoElements;
|
||||
final Messages _messages;
|
||||
final bool _warningsAsErrors;
|
||||
|
||||
Set<StyleSheet> allStyleSheets = new Set<StyleSheet>();
|
||||
|
||||
/**
|
||||
* [_pseudoElements] list of known pseudo attributes found in HTML, any
|
||||
* CSS pseudo-elements 'name::custom-element' is mapped to the manged name
|
||||
* associated with the pseudo-element key.
|
||||
*/
|
||||
_AnalyzerCss(this.packageRoot, this.info, this._pseudoElements,
|
||||
this._messages, this._warningsAsErrors);
|
||||
|
||||
/**
|
||||
* Run the analyzer on every file that is a style sheet or any component that
|
||||
* has a style tag.
|
||||
*/
|
||||
void process(SourceFile file) {
|
||||
var fileInfo = info[file.path];
|
||||
if (file.isStyleSheet || fileInfo.styleSheets.length > 0) {
|
||||
var styleSheets = processVars(fileInfo);
|
||||
|
||||
// Add to list of all style sheets analyzed.
|
||||
allStyleSheets.addAll(styleSheets);
|
||||
}
|
||||
|
||||
// Process any components.
|
||||
for (var component in fileInfo.declaredComponents) {
|
||||
var all = processVars(component);
|
||||
|
||||
// Add to list of all style sheets analyzed.
|
||||
allStyleSheets.addAll(all);
|
||||
}
|
||||
|
||||
processCustomPseudoElements();
|
||||
}
|
||||
|
||||
void normalize() {
|
||||
// Remove all var definitions for all style sheets analyzed.
|
||||
for (var tree in allStyleSheets) new _RemoveVarDefinitions().visitTree(tree);
|
||||
}
|
||||
|
||||
List<StyleSheet> processVars(var libraryInfo) {
|
||||
// Get list of all stylesheet(s) dependencies referenced from this file.
|
||||
var styleSheets = _dependencies(libraryInfo).toList();
|
||||
|
||||
var errors = [];
|
||||
css.analyze(styleSheets, errors: errors, options:
|
||||
[_warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
|
||||
|
||||
// Print errors as warnings.
|
||||
for (var e in errors) {
|
||||
_messages.warning(e.message, e.span);
|
||||
}
|
||||
|
||||
// Build list of all var definitions.
|
||||
Map varDefs = new Map();
|
||||
for (var tree in styleSheets) {
|
||||
var allDefs = (new _VarDefinitions()..visitTree(tree)).found;
|
||||
allDefs.forEach((key, value) {
|
||||
varDefs[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve all definitions to a non-VarUsage (terminal expression).
|
||||
varDefs.forEach((key, value) {
|
||||
for (var expr in (value.expression as Expressions).expressions) {
|
||||
var def = _findTerminalVarDefinition(varDefs, value);
|
||||
varDefs[key] = def;
|
||||
}
|
||||
});
|
||||
|
||||
// Resolve all var usages.
|
||||
for (var tree in styleSheets) new _ResolveVarUsages(varDefs).visitTree(tree);
|
||||
|
||||
return styleSheets;
|
||||
}
|
||||
|
||||
processCustomPseudoElements() {
|
||||
var polyFiller = new _PseudoElementExpander(_pseudoElements);
|
||||
for (var tree in allStyleSheets) {
|
||||
polyFiller.visitTree(tree);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a component or file check if any stylesheets referenced. If so then
|
||||
* return a list of all referenced stylesheet dependencies (@imports or <link
|
||||
* rel="stylesheet" ..>).
|
||||
*/
|
||||
Set<StyleSheet> _dependencies(var libraryInfo, {Set<StyleSheet> seen}) {
|
||||
if (seen == null) seen = new Set();
|
||||
|
||||
// Used to resolve all pathing information.
|
||||
var inputUrl = libraryInfo is FileInfo
|
||||
? libraryInfo.inputUrl
|
||||
: (libraryInfo as ComponentInfo).declaringFile.inputUrl;
|
||||
|
||||
for (var styleSheet in libraryInfo.styleSheets) {
|
||||
if (!seen.contains(styleSheet)) {
|
||||
// TODO(terry): VM uses expandos to implement hashes. Currently, it's a
|
||||
// linear (not constant) time cost (see dartbug.com/5746).
|
||||
// If this bug isn't fixed and performance show's this a
|
||||
// a problem we'll need to implement our own hashCode or
|
||||
// use a different key for better perf.
|
||||
// Add the stylesheet.
|
||||
seen.add(styleSheet);
|
||||
|
||||
// Any other imports in this stylesheet?
|
||||
var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot,
|
||||
inputUrl, _messages);
|
||||
|
||||
// Process other imports in this stylesheets.
|
||||
for (var importSS in urlInfos) {
|
||||
var importInfo = info[importSS.resolvedPath];
|
||||
if (importInfo != null) {
|
||||
// Add all known stylesheets processed.
|
||||
seen.addAll(importInfo.styleSheets);
|
||||
// Find dependencies for stylesheet referenced with a
|
||||
// @import
|
||||
for (var ss in importInfo.styleSheets) {
|
||||
var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl,
|
||||
_messages);
|
||||
for (var url in urls) {
|
||||
_dependencies(info[url.resolvedPath], seen: seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find var- definitions in a style sheet.
|
||||
* [found] list of known definitions.
|
||||
*/
|
||||
class _VarDefinitions extends Visitor {
|
||||
final Map<String, VarDefinition> found = new Map();
|
||||
|
||||
void visitTree(StyleSheet tree) {
|
||||
visitStyleSheet(tree);
|
||||
}
|
||||
|
||||
visitVarDefinition(VarDefinition node) {
|
||||
// Replace with latest variable definition.
|
||||
found[node.definedName] = node;
|
||||
super.visitVarDefinition(node);
|
||||
}
|
||||
|
||||
void visitVarDefinitionDirective(VarDefinitionDirective node) {
|
||||
visitVarDefinition(node.def);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve any CSS expression which contains a var() usage to the ultimate real
|
||||
* CSS expression value e.g.,
|
||||
*
|
||||
* var-one: var(two);
|
||||
* var-two: #ff00ff;
|
||||
*
|
||||
* .test {
|
||||
* color: var(one);
|
||||
* }
|
||||
*
|
||||
* then .test's color would be #ff00ff
|
||||
*/
|
||||
class _ResolveVarUsages extends Visitor {
|
||||
final Map<String, VarDefinition> varDefs;
|
||||
bool inVarDefinition = false;
|
||||
bool inUsage = false;
|
||||
Expressions currentExpressions;
|
||||
|
||||
_ResolveVarUsages(this.varDefs);
|
||||
|
||||
void visitTree(StyleSheet tree) {
|
||||
visitStyleSheet(tree);
|
||||
}
|
||||
|
||||
void visitVarDefinition(VarDefinition varDef) {
|
||||
inVarDefinition = true;
|
||||
super.visitVarDefinition(varDef);
|
||||
inVarDefinition = false;
|
||||
}
|
||||
|
||||
void visitExpressions(Expressions node) {
|
||||
currentExpressions = node;
|
||||
super.visitExpressions(node);
|
||||
currentExpressions = null;
|
||||
}
|
||||
|
||||
void visitVarUsage(VarUsage node) {
|
||||
// Don't process other var() inside of a varUsage. That implies that the
|
||||
// default is a var() too. Also, don't process any var() inside of a
|
||||
// varDefinition (they're just place holders until we've resolved all real
|
||||
// usages.
|
||||
if (!inUsage && !inVarDefinition && currentExpressions != null) {
|
||||
var expressions = currentExpressions.expressions;
|
||||
var index = expressions.indexOf(node);
|
||||
assert(index >= 0);
|
||||
var def = varDefs[node.name];
|
||||
if (def != null) {
|
||||
// Found a VarDefinition use it.
|
||||
_resolveVarUsage(currentExpressions.expressions, index, def);
|
||||
} else if (node.defaultValues.any((e) => e is VarUsage)) {
|
||||
// Don't have a VarDefinition need to use default values resolve all
|
||||
// default values.
|
||||
var terminalDefaults = [];
|
||||
for (var defaultValue in node.defaultValues) {
|
||||
terminalDefaults.addAll(resolveUsageTerminal(defaultValue));
|
||||
}
|
||||
expressions.replaceRange(index, index + 1, terminalDefaults);
|
||||
} else {
|
||||
// No VarDefinition but default value is a terminal expression; use it.
|
||||
expressions.replaceRange(index, index + 1, node.defaultValues);
|
||||
}
|
||||
}
|
||||
|
||||
inUsage = true;
|
||||
super.visitVarUsage(node);
|
||||
inUsage = false;
|
||||
}
|
||||
|
||||
List<Expression> resolveUsageTerminal(VarUsage usage) {
|
||||
var result = [];
|
||||
|
||||
var varDef = varDefs[usage.name];
|
||||
var expressions;
|
||||
if (varDef == null) {
|
||||
// VarDefinition not found try the defaultValues.
|
||||
expressions = usage.defaultValues;
|
||||
} else {
|
||||
// Use the VarDefinition found.
|
||||
expressions = (varDef.expression as Expressions).expressions;
|
||||
}
|
||||
|
||||
for (var expr in expressions) {
|
||||
if (expr is VarUsage) {
|
||||
// Get terminal value.
|
||||
result.addAll(resolveUsageTerminal(expr));
|
||||
}
|
||||
}
|
||||
|
||||
// We're at a terminal just return the VarDefinition expression.
|
||||
if (result.isEmpty && varDef != null) {
|
||||
result = (varDef.expression as Expressions).expressions;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_resolveVarUsage(List<Expressions> expressions, int index,
|
||||
VarDefinition def) {
|
||||
var defExpressions = (def.expression as Expressions).expressions;
|
||||
expressions.replaceRange(index, index + 1, defExpressions);
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove all var definitions. */
|
||||
class _RemoveVarDefinitions extends Visitor {
|
||||
void visitTree(StyleSheet tree) {
|
||||
visitStyleSheet(tree);
|
||||
}
|
||||
|
||||
void visitStyleSheet(StyleSheet ss) {
|
||||
ss.topLevels.removeWhere((e) => e is VarDefinitionDirective);
|
||||
super.visitStyleSheet(ss);
|
||||
}
|
||||
|
||||
void visitDeclarationGroup(DeclarationGroup node) {
|
||||
node.declarations.removeWhere((e) => e is VarDefinition);
|
||||
super.visitDeclarationGroup(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all selectors looking for a pseudo-element in a selector. If the
|
||||
* name is found in our list of known pseudo-elements. Known pseudo-elements
|
||||
* are built when parsing a component looking for an attribute named "pseudo".
|
||||
* The value of the pseudo attribute is the name of the custom pseudo-element.
|
||||
* The name is mangled so Dart/JS can't directly access the pseudo-element only
|
||||
* CSS can access a custom pseudo-element (and see issue #510, querying needs
|
||||
* access to custom pseudo-elements).
|
||||
*
|
||||
* Change the custom pseudo-element to be a child of the pseudo attribute's
|
||||
* mangled custom pseudo element name. e.g,
|
||||
*
|
||||
* .test::x-box
|
||||
*
|
||||
* would become:
|
||||
*
|
||||
* .test > *[pseudo="x-box_2"]
|
||||
*/
|
||||
class _PseudoElementExpander extends Visitor {
|
||||
final Map<String, String> _pseudoElements;
|
||||
|
||||
_PseudoElementExpander(this._pseudoElements);
|
||||
|
||||
void visitTree(StyleSheet tree) => visitStyleSheet(tree);
|
||||
|
||||
visitSelector(Selector node) {
|
||||
var selectors = node.simpleSelectorSequences;
|
||||
for (var index = 0; index < selectors.length; index++) {
|
||||
var selector = selectors[index].simpleSelector;
|
||||
if (selector is PseudoElementSelector) {
|
||||
if (_pseudoElements.containsKey(selector.name)) {
|
||||
// Pseudo Element is a custom element.
|
||||
var mangledName = _pseudoElements[selector.name];
|
||||
|
||||
var span = selectors[index].span;
|
||||
|
||||
var attrSelector = new AttributeSelector(
|
||||
new Identifier('pseudo', span), css.TokenKind.EQUALS,
|
||||
mangledName, span);
|
||||
// The wildcard * namespace selector.
|
||||
var wildCard = new ElementSelector(new Wildcard(span), span);
|
||||
selectors[index] = new SimpleSelectorSequence(wildCard, span,
|
||||
css.TokenKind.COMBINATOR_GREATER);
|
||||
selectors.insert(++index,
|
||||
new SimpleSelectorSequence(attrSelector, span));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet,
|
||||
String packageRoot, UrlInfo inputUrl, Messages messages) {
|
||||
var visitor = new _CssImports(packageRoot, inputUrl, messages);
|
||||
visitor.visitTree(styleSheet);
|
||||
return visitor.urlInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find any imports in the style sheet; normalize the style sheet href and
|
||||
* return a list of all fully qualified CSS files.
|
||||
*/
|
||||
class _CssImports extends Visitor {
|
||||
final String packageRoot;
|
||||
|
||||
/** Input url of the css file, used to normalize relative import urls. */
|
||||
final UrlInfo inputUrl;
|
||||
|
||||
/** List of all imported style sheets. */
|
||||
final List<UrlInfo> urlInfos = [];
|
||||
|
||||
final Messages _messages;
|
||||
|
||||
_CssImports(this.packageRoot, this.inputUrl, this._messages);
|
||||
|
||||
void visitTree(StyleSheet tree) {
|
||||
visitStyleSheet(tree);
|
||||
}
|
||||
|
||||
void visitImportDirective(ImportDirective node) {
|
||||
var urlInfo = UrlInfo.resolve(node.import, inputUrl,
|
||||
node.span, packageRoot, _messages, ignoreAbsolute: true);
|
||||
if (urlInfo == null) return;
|
||||
urlInfos.add(urlInfo);
|
||||
}
|
||||
}
|
||||
|
||||
StyleSheet parseCss(String content, Messages messages,
|
||||
CompilerOptions options) {
|
||||
if (content.trim().isEmpty) return null;
|
||||
|
||||
var errors = [];
|
||||
|
||||
// TODO(terry): Add --checked when fully implemented and error handling.
|
||||
var stylesheet = css.parse(content, errors: errors, options:
|
||||
[options.warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
|
||||
|
||||
// Note: errors aren't fatal in HTML (unless strict mode is on).
|
||||
// So just print them as warnings.
|
||||
for (var e in errors) {
|
||||
messages.warning(e.message, e.span);
|
||||
}
|
||||
|
||||
return stylesheet;
|
||||
}
|
||||
|
||||
/** Find terminal definition (non VarUsage implies real CSS value). */
|
||||
VarDefinition _findTerminalVarDefinition(Map<String, VarDefinition> varDefs,
|
||||
VarDefinition varDef) {
|
||||
var expressions = varDef.expression as Expressions;
|
||||
for (var expr in expressions.expressions) {
|
||||
if (expr is VarUsage) {
|
||||
var usageName = (expr as VarUsage).name;
|
||||
var foundDef = varDefs[usageName];
|
||||
|
||||
// If foundDef is unknown check if defaultValues; if it exist then resolve
|
||||
// to terminal value.
|
||||
if (foundDef == null) {
|
||||
// We're either a VarUsage or terminal definition if in varDefs;
|
||||
// either way replace VarUsage with it's default value because the
|
||||
// VarDefinition isn't found.
|
||||
var defaultValues = (expr as VarUsage).defaultValues;
|
||||
var replaceExprs = expressions.expressions;
|
||||
assert(replaceExprs.length == 1);
|
||||
replaceExprs.replaceRange(0, 1, defaultValues);
|
||||
return varDef;
|
||||
}
|
||||
if (foundDef is VarDefinition) {
|
||||
return _findTerminalVarDefinition(varDefs, foundDef);
|
||||
}
|
||||
} else {
|
||||
// Return real CSS property.
|
||||
return varDef;
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't point to a var definition that existed.
|
||||
return varDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find urls imported inside style tags under [info]. If [info] is a FileInfo
|
||||
* then process only style tags in the body (don't process any style tags in a
|
||||
* component). If [info] is a ComponentInfo only process style tags inside of
|
||||
* the element are processed. For an [info] of type FileInfo [node] is the
|
||||
* file's document and for an [info] of type ComponentInfo then [node] is the
|
||||
* component's element tag.
|
||||
*/
|
||||
List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl,
|
||||
String packageRoot, Node node, Messages messages, CompilerOptions options) {
|
||||
// Process any @imports inside of the <style> tag.
|
||||
var styleProcessor =
|
||||
new _CssStyleTag(packageRoot, info, inputUrl, messages, options);
|
||||
styleProcessor.visit(node);
|
||||
return styleProcessor.imports;
|
||||
}
|
||||
|
||||
/* Process CSS inside of a style tag. */
|
||||
class _CssStyleTag extends TreeVisitor {
|
||||
final String _packageRoot;
|
||||
|
||||
/** Either a FileInfo or ComponentInfo. */
|
||||
final LibraryInfo _info;
|
||||
final Messages _messages;
|
||||
final CompilerOptions _options;
|
||||
|
||||
/**
|
||||
* Path of the declaring file, for a [_info] of type FileInfo it's the file's
|
||||
* path for a type ComponentInfo it's the declaring file path.
|
||||
*/
|
||||
final UrlInfo _inputUrl;
|
||||
|
||||
/** List of @imports found. */
|
||||
List<UrlInfo> imports = [];
|
||||
|
||||
_CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages,
|
||||
this._options);
|
||||
|
||||
void visitElement(Element node) {
|
||||
// Don't process any style tags inside of element if we're processing a
|
||||
// FileInfo. The style tags inside of a component defintion will be
|
||||
// processed when _info is a ComponentInfo.
|
||||
if (node.tagName == 'polymer-element' && _info is FileInfo) return;
|
||||
if (node.tagName == 'style') {
|
||||
// Parse the contents of the scoped style tag.
|
||||
var styleSheet = parseCss(node.nodes.single.value, _messages, _options);
|
||||
if (styleSheet != null) {
|
||||
_info.styleSheets.add(styleSheet);
|
||||
|
||||
// Find all imports return list of @imports in this style tag.
|
||||
var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot,
|
||||
_inputUrl, _messages);
|
||||
imports.addAll(urlInfos);
|
||||
}
|
||||
}
|
||||
super.visitElement(node);
|
||||
}
|
||||
}
|
155
pkg/polymer/lib/src/css_emitters.dart
Normal file
155
pkg/polymer/lib/src/css_emitters.dart
Normal file
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library polymer.src.css_emitters;
|
||||
|
||||
import 'package:csslib/visitor.dart' show Visitor, CssPrinter, ElementSelector,
|
||||
UriTerm, Selector, HostDirective, SimpleSelectorSequence, StyleSheet;
|
||||
|
||||
import 'info.dart';
|
||||
import 'paths.dart' show PathMapper;
|
||||
import 'utils.dart';
|
||||
|
||||
void rewriteCssUris(PathMapper pathMapper, String cssPath, bool rewriteUrls,
|
||||
StyleSheet styleSheet) {
|
||||
new _UriVisitor(pathMapper, cssPath, rewriteUrls).visitTree(styleSheet);
|
||||
}
|
||||
|
||||
/** Compute each CSS URI resource relative from the generated CSS file. */
|
||||
class _UriVisitor extends Visitor {
|
||||
/**
|
||||
* Relative path from the output css file to the location of the original
|
||||
* css file that contained the URI to each resource.
|
||||
*/
|
||||
final String _pathToOriginalCss;
|
||||
|
||||
factory _UriVisitor(PathMapper pathMapper, String cssPath, bool rewriteUrl) {
|
||||
var cssDir = path.dirname(cssPath);
|
||||
var outCssDir = rewriteUrl ? pathMapper.outputDirPath(cssPath)
|
||||
: path.dirname(cssPath);
|
||||
return new _UriVisitor._internal(path.relative(cssDir, from: outCssDir));
|
||||
}
|
||||
|
||||
_UriVisitor._internal(this._pathToOriginalCss);
|
||||
|
||||
void visitUriTerm(UriTerm node) {
|
||||
// Don't touch URIs that have any scheme (http, etc.).
|
||||
var uri = Uri.parse(node.text);
|
||||
if (uri.host != '') return;
|
||||
if (uri.scheme != '' && uri.scheme != 'package') return;
|
||||
|
||||
node.text = pathToUrl(
|
||||
path.normalize(path.join(_pathToOriginalCss, node.text)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Emit the contents of the style tag outside of a component. */
|
||||
String emitStyleSheet(StyleSheet ss, FileInfo file) =>
|
||||
(new _CssEmitter(file.components.keys.toSet())
|
||||
..visitTree(ss, pretty: true)).toString();
|
||||
|
||||
/** Emit a component's style tag content emulating scoped css. */
|
||||
String emitComponentStyleSheet(StyleSheet ss, String tagName) =>
|
||||
(new _ComponentCssEmitter(tagName)..visitTree(ss, pretty: true)).toString();
|
||||
|
||||
String emitOriginalCss(StyleSheet css) =>
|
||||
(new CssPrinter()..visitTree(css)).toString();
|
||||
|
||||
/** Only x-tag name element selectors are emitted as [is="x-"]. */
|
||||
class _CssEmitter extends CssPrinter {
|
||||
final Set _componentsTag;
|
||||
_CssEmitter(this._componentsTag);
|
||||
|
||||
void visitElementSelector(ElementSelector node) {
|
||||
// If element selector is a component's tag name, then change selector to
|
||||
// find element who's is attribute's the component's name.
|
||||
if (_componentsTag.contains(node.name)) {
|
||||
emit('[is="${node.name}"]');
|
||||
return;
|
||||
}
|
||||
super.visitElementSelector(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a css stylesheet applying rules to emulate scoped css. The rules adjust
|
||||
* element selectors to include the component's tag name.
|
||||
*/
|
||||
class _ComponentCssEmitter extends CssPrinter {
|
||||
final String _componentTagName;
|
||||
bool _inHostDirective = false;
|
||||
bool _selectorStartInHostDirective = false;
|
||||
|
||||
_ComponentCssEmitter(this._componentTagName);
|
||||
|
||||
/** Is the element selector an x-tag name. */
|
||||
bool _isSelectorElementXTag(Selector node) {
|
||||
if (node.simpleSelectorSequences.length > 0) {
|
||||
var selector = node.simpleSelectorSequences[0].simpleSelector;
|
||||
return selector is ElementSelector && selector.name == _componentTagName;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void visitSelector(Selector node) {
|
||||
// If the selector starts with an x-tag name don't emit it twice.
|
||||
if (!_isSelectorElementXTag(node)) {
|
||||
if (_inHostDirective) {
|
||||
// Style the element that's hosting the component, therefore don't emit
|
||||
// the descendent combinator (first space after the [is="x-..."]).
|
||||
emit('[is="$_componentTagName"]');
|
||||
// Signal that first simpleSelector must be checked.
|
||||
_selectorStartInHostDirective = true;
|
||||
} else {
|
||||
// Emit its scoped as a descendent (space at end).
|
||||
emit('[is="$_componentTagName"] ');
|
||||
}
|
||||
}
|
||||
super.visitSelector(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* If first simple selector of a ruleset in a @host directive is a wildcard
|
||||
* then don't emit the wildcard.
|
||||
*/
|
||||
void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
|
||||
if (_selectorStartInHostDirective) {
|
||||
_selectorStartInHostDirective = false;
|
||||
if (node.simpleSelector.isWildcard) {
|
||||
// Skip the wildcard if first item in the sequence.
|
||||
return;
|
||||
}
|
||||
assert(node.isCombinatorNone);
|
||||
}
|
||||
|
||||
super.visitSimpleSelectorSequence(node);
|
||||
}
|
||||
|
||||
void visitElementSelector(ElementSelector node) {
|
||||
// If element selector is the component's tag name, then change selector to
|
||||
// find element who's is attribute is the component's name.
|
||||
if (_componentTagName == node.name) {
|
||||
emit('[is="$_componentTagName"]');
|
||||
return;
|
||||
}
|
||||
super.visitElementSelector(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're polyfilling scoped styles the @host directive is stripped. Any
|
||||
* ruleset(s) processed in an @host will fixup the first selector. See
|
||||
* visitSelector and visitSimpleSelectorSequence in this class, they adjust
|
||||
* the selectors so it styles the element hosting the compopnent.
|
||||
*/
|
||||
void visitHostDirective(HostDirective node) {
|
||||
_inHostDirective = true;
|
||||
emit('/* @host */');
|
||||
for (var ruleset in node.rulesets) {
|
||||
ruleset.visit(this);
|
||||
}
|
||||
_inHostDirective = false;
|
||||
emit('/* end of @host */\n');
|
||||
}
|
||||
}
|
27
pkg/polymer/lib/src/custom_tag_name.dart
Normal file
27
pkg/polymer/lib/src/custom_tag_name.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library polymer.src.custom_tag_name;
|
||||
|
||||
/**
|
||||
* Returns true if this is a valid custom element name. See:
|
||||
* <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn-custom-element-name>
|
||||
*/
|
||||
bool isCustomTag(String name) {
|
||||
if (name == null || !name.contains('-')) return false;
|
||||
|
||||
// These names have meaning in SVG or MathML, so they aren't allowed as custom
|
||||
// tags.
|
||||
var invalidNames = const {
|
||||
'annotation-xml': '',
|
||||
'color-profile': '',
|
||||
'font-face': '',
|
||||
'font-face-src': '',
|
||||
'font-face-uri': '',
|
||||
'font-face-format': '',
|
||||
'font-face-name': '',
|
||||
'missing-glyph': '',
|
||||
};
|
||||
return !invalidNames.containsKey(name);
|
||||
}
|
132
pkg/polymer/lib/src/dart_parser.dart
Normal file
132
pkg/polymer/lib/src/dart_parser.dart
Normal file
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Parser for Dart code based on the experimental analyzer.
|
||||
*/
|
||||
library dart_parser;
|
||||
|
||||
import 'package:analyzer_experimental/src/generated/ast.dart';
|
||||
import 'package:analyzer_experimental/src/generated/error.dart';
|
||||
import 'package:analyzer_experimental/src/generated/parser.dart';
|
||||
import 'package:analyzer_experimental/src/generated/scanner.dart';
|
||||
import 'package:source_maps/span.dart' show SourceFile, SourceFileSegment, Location;
|
||||
import 'utils.dart' show escapeDartString;
|
||||
|
||||
/** Information extracted from a source Dart file. */
|
||||
class DartCodeInfo {
|
||||
|
||||
/** Library qualified identifier, if any. */
|
||||
final String libraryName;
|
||||
|
||||
/** Library which the code is part-of, if any. */
|
||||
final String partOf;
|
||||
|
||||
/** Declared imports, exports, and parts. */
|
||||
final List<Directive> directives;
|
||||
|
||||
/** Source file representation used to compute source map information. */
|
||||
final SourceFile sourceFile;
|
||||
|
||||
/** The parsed code. */
|
||||
final CompilationUnit compilationUnit;
|
||||
|
||||
/** The full source code. */
|
||||
final String code;
|
||||
|
||||
DartCodeInfo(this.libraryName, this.partOf, this.directives, code,
|
||||
this.sourceFile, [compilationUnit])
|
||||
: this.code = code,
|
||||
this.compilationUnit = compilationUnit == null
|
||||
? _parseCompilationUnit(code) : compilationUnit;
|
||||
|
||||
bool get isPart =>
|
||||
compilationUnit.directives.any((d) => d is PartOfDirective);
|
||||
|
||||
int get directivesEnd {
|
||||
if (compilationUnit.directives.length == 0) return 0;
|
||||
return compilationUnit.directives.last.end;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position of the first "part" directive. If none is found,
|
||||
* this behaves like [directivesEnd].
|
||||
*/
|
||||
int get firstPartOffset {
|
||||
for (var directive in compilationUnit.directives) {
|
||||
if (directive is PartDirective) return directive.offset;
|
||||
}
|
||||
// No part directives, just return directives end.
|
||||
return directivesEnd;
|
||||
}
|
||||
|
||||
/** Gets the code after the [directives]. */
|
||||
String codeAfterDirectives() => code.substring(directivesEnd);
|
||||
|
||||
ClassDeclaration findClass(String name) {
|
||||
for (var decl in compilationUnit.declarations) {
|
||||
if (decl is ClassDeclaration) {
|
||||
if (decl.name.name == name) return decl;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleStringLiteral createStringLiteral(String contents) {
|
||||
var lexeme = "'${escapeDartString(contents)}'";
|
||||
var token = new StringToken(TokenType.STRING, lexeme, null);
|
||||
return new SimpleStringLiteral.full(token, contents);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse and extract top-level directives from [code].
|
||||
*
|
||||
*/
|
||||
// TODO(sigmund): log emitted error/warning messages
|
||||
DartCodeInfo parseDartCode(String path, String code, [Location offset]) {
|
||||
var unit = _parseCompilationUnit(code);
|
||||
|
||||
// Extract some information from the compilation unit.
|
||||
String libraryName, partName;
|
||||
var directives = [];
|
||||
int directiveEnd = 0;
|
||||
for (var directive in unit.directives) {
|
||||
if (directive is LibraryDirective) {
|
||||
libraryName = directive.name.name;
|
||||
} else if (directive is PartOfDirective) {
|
||||
partName = directive.libraryName.name;
|
||||
} else {
|
||||
assert(directive is UriBasedDirective);
|
||||
// Normalize the library URI.
|
||||
var uriNode = directive.uri;
|
||||
if (uriNode is! SimpleStringLiteral) {
|
||||
String uri = uriNode.accept(new ConstantEvaluator());
|
||||
directive.uri = createStringLiteral(uri);
|
||||
}
|
||||
directives.add(directive);
|
||||
}
|
||||
}
|
||||
|
||||
var sourceFile = offset == null
|
||||
? new SourceFile.text(path, code)
|
||||
: new SourceFileSegment(path, code, offset);
|
||||
|
||||
return new DartCodeInfo(libraryName, partName, directives, code,
|
||||
sourceFile, unit);
|
||||
}
|
||||
|
||||
CompilationUnit _parseCompilationUnit(String code) {
|
||||
var errorListener = new _ErrorCollector();
|
||||
var scanner = new StringScanner(null, code, errorListener);
|
||||
var token = scanner.tokenize();
|
||||
var parser = new Parser(null, errorListener);
|
||||
return parser.parseCompilationUnit(token);
|
||||
}
|
||||
|
||||
class _ErrorCollector extends AnalysisErrorListener {
|
||||
final errors = new List<AnalysisError>();
|
||||
onError(error) => errors.add(error);
|
||||
}
|
246
pkg/polymer/lib/src/emitters.dart
Normal file
246
pkg/polymer/lib/src/emitters.dart
Normal file
|
@ -0,0 +1,246 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Collects several code emitters for the template tool. */
|
||||
library emitters;
|
||||
|
||||
import 'package:html5lib/dom.dart';
|
||||
import 'package:html5lib/dom_parsing.dart' show TreeVisitor;
|
||||
import 'package:html5lib/parser.dart' show parseFragment;
|
||||
import 'package:source_maps/printer.dart';
|
||||
import 'package:source_maps/refactor.dart';
|
||||
|
||||
import 'compiler_options.dart';
|
||||
import 'css_emitters.dart' show emitStyleSheet, emitOriginalCss;
|
||||
import 'html5_utils.dart';
|
||||
import 'info.dart' show ComponentInfo, FileInfo, GlobalInfo;
|
||||
import 'messages.dart';
|
||||
import 'paths.dart' show PathMapper;
|
||||
import 'utils.dart' show escapeDartString, path;
|
||||
|
||||
/** Generates the class corresponding to a single web component. */
|
||||
NestedPrinter emitPolymerElement(ComponentInfo info, PathMapper pathMapper,
|
||||
TextEditTransaction transaction, CompilerOptions options) {
|
||||
if (info.classDeclaration == null) return null;
|
||||
|
||||
var codeInfo = info.userCode;
|
||||
if (transaction == null) {
|
||||
// TODO(sigmund): avoid emitting this file if we don't need to do any
|
||||
// modifications (e.g. no @observable and not adding the libraryName).
|
||||
transaction = new TextEditTransaction(codeInfo.code, codeInfo.sourceFile);
|
||||
}
|
||||
if (codeInfo.libraryName == null) {
|
||||
// For deploy, we need to import the library associated with the component,
|
||||
// so we need to ensure there is a library directive.
|
||||
var libraryName = info.tagName.replaceAll(new RegExp('[-./]'), '_');
|
||||
transaction.edit(0, 0, 'library $libraryName;');
|
||||
}
|
||||
return transaction.commit();
|
||||
}
|
||||
|
||||
/** The code that will be used to bootstrap the application. */
|
||||
NestedPrinter generateBootstrapCode(
|
||||
FileInfo info, FileInfo userMainInfo, GlobalInfo global,
|
||||
PathMapper pathMapper, CompilerOptions options) {
|
||||
|
||||
var printer = new NestedPrinter(0)
|
||||
..addLine('library app_bootstrap;')
|
||||
..addLine('')
|
||||
..addLine("import 'package:polymer/polymer.dart';")
|
||||
..addLine("import 'dart:mirrors' show currentMirrorSystem;");
|
||||
if (userMainInfo.userCode != null) {
|
||||
printer..addLine("import '${pathMapper.importUrlFor(info, userMainInfo)}' "
|
||||
"as userMain;\n");
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (var c in global.components.values) {
|
||||
if (c.hasConflict) continue;
|
||||
printer.addLine("import '${pathMapper.importUrlFor(info, c)}' as i$i;");
|
||||
i++;
|
||||
}
|
||||
|
||||
printer..addLine('')
|
||||
..addLine('void main() {')
|
||||
..indent += 1
|
||||
..addLine("initPolymer([")
|
||||
..indent += 2;
|
||||
|
||||
for (var c in global.components.values) {
|
||||
if (c.hasConflict) continue;
|
||||
printer.addLine("'${pathMapper.importUrlFor(info, c)}',");
|
||||
}
|
||||
|
||||
return printer
|
||||
..indent -= 1
|
||||
..addLine('],')
|
||||
..addLine(userMainInfo.userCode != null ? 'userMain.main,' : '() {},')
|
||||
..addLine(
|
||||
"currentMirrorSystem().findLibrary(const Symbol('app_bootstrap'))")
|
||||
..indent += 2
|
||||
..addLine(".first.uri.toString());")
|
||||
..indent -= 4
|
||||
..addLine('}');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rewrites attributes that contain relative URL (excluding src urls in script
|
||||
* and link tags which are already rewritten by other parts of the compiler).
|
||||
*/
|
||||
class _AttributeUrlTransform extends TreeVisitor {
|
||||
final String filePath;
|
||||
final PathMapper pathMapper;
|
||||
|
||||
_AttributeUrlTransform(this.filePath, this.pathMapper);
|
||||
|
||||
visitElement(Element node) {
|
||||
if (node.tagName == 'script') return;
|
||||
if (node.tagName == 'link') return;
|
||||
|
||||
for (var key in node.attributes.keys) {
|
||||
if (urlAttributes.contains(key)) {
|
||||
node.attributes[key] =
|
||||
pathMapper.transformUrl(filePath, node.attributes[key]);
|
||||
}
|
||||
}
|
||||
super.visitElement(node);
|
||||
}
|
||||
}
|
||||
|
||||
final _shadowDomJS = new RegExp(r'shadowdom\..*\.js', caseSensitive: false);
|
||||
final _bootJS = new RegExp(r'.*/polymer/boot.js', caseSensitive: false);
|
||||
|
||||
/** Trim down the html for the main html page. */
|
||||
void transformMainHtml(Document document, FileInfo fileInfo,
|
||||
PathMapper pathMapper, bool hasCss, bool rewriteUrls,
|
||||
Messages messages, GlobalInfo global, String bootstrapOutName) {
|
||||
var filePath = fileInfo.inputUrl.resolvedPath;
|
||||
|
||||
var dartLoaderTag = null;
|
||||
bool shadowDomFound = false;
|
||||
for (var tag in document.queryAll('script')) {
|
||||
var src = tag.attributes['src'];
|
||||
if (src != null) {
|
||||
var last = src.split('/').last;
|
||||
if (last == 'dart.js' || last == 'testing.js') {
|
||||
dartLoaderTag = tag;
|
||||
} else if (_shadowDomJS.hasMatch(last)) {
|
||||
shadowDomFound = true;
|
||||
}
|
||||
}
|
||||
if (tag.attributes['type'] == 'application/dart') {
|
||||
tag.remove();
|
||||
} else if (src != null) {
|
||||
if (_bootJS.hasMatch(src)) {
|
||||
tag.remove();
|
||||
} else if (rewriteUrls) {
|
||||
tag.attributes["src"] = pathMapper.transformUrl(filePath, src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var tag in document.queryAll('link')) {
|
||||
var href = tag.attributes['href'];
|
||||
var rel = tag.attributes['rel'];
|
||||
if (rel == 'component' || rel == 'components' || rel == 'import') {
|
||||
tag.remove();
|
||||
} else if (href != null && rewriteUrls && !hasCss) {
|
||||
// Only rewrite URL if rewrite on and we're not CSS polyfilling.
|
||||
tag.attributes['href'] = pathMapper.transformUrl(filePath, href);
|
||||
}
|
||||
}
|
||||
|
||||
if (rewriteUrls) {
|
||||
// Transform any element's attribute which is a relative URL.
|
||||
new _AttributeUrlTransform(filePath, pathMapper).visit(document);
|
||||
}
|
||||
|
||||
if (hasCss) {
|
||||
var newCss = pathMapper.mangle(path.basename(filePath), '.css', true);
|
||||
var linkElem = new Element.html(
|
||||
'<link rel="stylesheet" type="text/css" href="$newCss">');
|
||||
document.head.insertBefore(linkElem, null);
|
||||
}
|
||||
|
||||
var styles = document.queryAll('style');
|
||||
if (styles.length > 0) {
|
||||
var allCss = new StringBuffer();
|
||||
fileInfo.styleSheets.forEach((styleSheet) =>
|
||||
allCss.write(emitStyleSheet(styleSheet, fileInfo)));
|
||||
styles[0].nodes.clear();
|
||||
styles[0].nodes.add(new Text(allCss.toString()));
|
||||
for (var i = styles.length - 1; i > 0 ; i--) {
|
||||
styles[i].remove();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jmesserly): put this in the global CSS file?
|
||||
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#css-additions
|
||||
document.head.nodes.insert(0, parseFragment(
|
||||
'<style>template { display: none; }</style>'));
|
||||
|
||||
// Move all <element> declarations to the main HTML file
|
||||
// TODO(sigmund): remove this once we have HTMLImports implemented.
|
||||
for (var c in global.components.values) {
|
||||
document.body.nodes.insert(0, new Text('\n'));
|
||||
var fragment = c.element;
|
||||
for (var tag in fragment.queryAll('script')) {
|
||||
// TODO(sigmund): leave script tags around when we start using "boot.js"
|
||||
if (tag.attributes['type'] == 'application/dart') {
|
||||
tag.remove();
|
||||
}
|
||||
}
|
||||
document.body.nodes.insert(0, fragment);
|
||||
}
|
||||
|
||||
if (!shadowDomFound) {
|
||||
// TODO(jmesserly): we probably shouldn't add this automatically.
|
||||
document.body.nodes.add(parseFragment('<script type="text/javascript" '
|
||||
'src="packages/shadow_dom/shadow_dom.debug.js"></script>\n'));
|
||||
|
||||
// JS interop code required for Polymer CSS shimming.
|
||||
document.body.nodes.add(parseFragment('<script type="text/javascript" '
|
||||
'src="packages/browser/interop.js"></script>\n'));
|
||||
}
|
||||
|
||||
var bootstrapScript = parseFragment(
|
||||
'<script type="application/dart" src="$bootstrapOutName"></script>');
|
||||
if (dartLoaderTag == null) {
|
||||
document.body.nodes.add(bootstrapScript);
|
||||
// TODO(jmesserly): turn this warning on.
|
||||
//messages.warning('Missing script to load Dart. '
|
||||
// 'Please add this line to your HTML file: $dartLoader',
|
||||
// document.body.sourceSpan);
|
||||
// TODO(sigmund): switch to 'boot.js'
|
||||
document.body.nodes.add(parseFragment('<script type="text/javascript" '
|
||||
'src="packages/browser/dart.js"></script>\n'));
|
||||
} else if (dartLoaderTag.parent != document.body) {
|
||||
document.body.nodes.add(bootstrapScript);
|
||||
} else {
|
||||
document.body.insertBefore(bootstrapScript, dartLoaderTag);
|
||||
}
|
||||
|
||||
// Insert the "auto-generated" comment after the doctype, otherwise IE will
|
||||
// go into quirks mode.
|
||||
int commentIndex = 0;
|
||||
DocumentType doctype =
|
||||
document.nodes.firstWhere((n) => n is DocumentType, orElse: () => null);
|
||||
if (doctype != null) {
|
||||
commentIndex = document.nodes.indexOf(doctype) + 1;
|
||||
// TODO(jmesserly): the html5lib parser emits a warning for missing
|
||||
// doctype, but it allows you to put it after comments. Presumably they do
|
||||
// this because some comments won't force IE into quirks mode (sigh). See
|
||||
// this link for more info:
|
||||
// http://bugzilla.validator.nu/show_bug.cgi?id=836
|
||||
// For simplicity we emit the warning always, like validator.nu does.
|
||||
if (doctype.tagName != 'html' || commentIndex != 1) {
|
||||
messages.warning('file should start with <!DOCTYPE html> '
|
||||
'to avoid the possibility of it being parsed in quirks mode in IE. '
|
||||
'See http://www.w3.org/TR/html5-diff/#doctype', doctype.sourceSpan);
|
||||
}
|
||||
}
|
||||
document.nodes.insert(commentIndex, parseFragment(
|
||||
'\n<!-- This file was auto-generated from $filePath. -->\n'));
|
||||
}
|
35
pkg/polymer/lib/src/file_system.dart
Normal file
35
pkg/polymer/lib/src/file_system.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Abstraction for file systems and utility functions to manipulate paths. */
|
||||
library file_system;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
/**
|
||||
* Abstraction around file system access to work in a variety of different
|
||||
* environments.
|
||||
*/
|
||||
abstract class FileSystem {
|
||||
/**
|
||||
* Apply all pending writes. Until this method is called, writeString is not
|
||||
* guaranteed to have any observable impact.
|
||||
*/
|
||||
Future flush();
|
||||
|
||||
/**
|
||||
* Reads bytes if possible, but falls back to text if running in a browser.
|
||||
* Return type is either [Future<List<int>>] or [Future<String>].
|
||||
*/
|
||||
Future readTextOrBytes(String path);
|
||||
|
||||
/* Like [readTextOrBytes], but decodes bytes as UTF-8. Used for Dart code. */
|
||||
Future<String> readText(String path);
|
||||
|
||||
/**
|
||||
* Writes [text] to file at [path]. Call flush to insure that changes are
|
||||
* visible.
|
||||
*/
|
||||
void writeString(String path, String text);
|
||||
}
|
47
pkg/polymer/lib/src/file_system/console.dart
Normal file
47
pkg/polymer/lib/src/file_system/console.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library console;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:utf';
|
||||
import 'package:polymer/src/file_system.dart';
|
||||
|
||||
/** File system implementation for console VM (i.e. no browser). */
|
||||
class ConsoleFileSystem implements FileSystem {
|
||||
|
||||
/** Pending futures for file write requests. */
|
||||
final _pending = <String, Future>{};
|
||||
|
||||
Future flush() => Future.wait(_pending.values.toList());
|
||||
|
||||
void writeString(String path, String text) {
|
||||
if(!_pending.containsKey(path)) {
|
||||
_pending[path] = new File(path).open(mode: FileMode.WRITE)
|
||||
.then((file) => file.writeString(text))
|
||||
.then((file) => file.close())
|
||||
.whenComplete(() { _pending.remove(path); });
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jmesserly): even better would be to pass the RandomAccessFile directly
|
||||
// to html5lib. This will require a further restructuring of FileSystem.
|
||||
// Probably it just needs "readHtml" and "readText" methods.
|
||||
Future<List<int>> readTextOrBytes(String path) {
|
||||
return new File(path).open().then(
|
||||
(file) => file.length().then((length) {
|
||||
// TODO(jmesserly): is this guaranteed to read all of the bytes?
|
||||
var buffer = new List<int>(length);
|
||||
return file.readInto(buffer, 0, length)
|
||||
.then((_) => file.close())
|
||||
.then((_) => buffer);
|
||||
}));
|
||||
}
|
||||
|
||||
// TODO(jmesserly): do we support any encoding other than UTF-8 for Dart?
|
||||
Future<String> readText(String path) {
|
||||
return readTextOrBytes(path).then(decodeUtf8);
|
||||
}
|
||||
}
|
47
pkg/polymer/lib/src/files.dart
Normal file
47
pkg/polymer/lib/src/files.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library files;
|
||||
|
||||
import 'package:html5lib/dom.dart';
|
||||
|
||||
/** An input file to process by the template compiler. */
|
||||
class SourceFile {
|
||||
static const int HTML = 1;
|
||||
static const int DART = 2;
|
||||
static const int STYLESHEET = 3;
|
||||
|
||||
final String path;
|
||||
final int type;
|
||||
|
||||
Document document;
|
||||
|
||||
/** Dart code or contents of a linked style sheet. */
|
||||
String code;
|
||||
|
||||
SourceFile(this.path, {this.type: HTML});
|
||||
|
||||
bool get isDart => type == DART;
|
||||
bool get isHtml => type == HTML;
|
||||
bool get isStyleSheet => type == STYLESHEET;
|
||||
|
||||
String toString() => "#<SourceFile $path>";
|
||||
}
|
||||
|
||||
/** An output file to generated by the template compiler. */
|
||||
class OutputFile {
|
||||
final String path;
|
||||
final String contents;
|
||||
|
||||
/**
|
||||
* Path to the source file that was transformed into this OutputFile, `null`
|
||||
* for files that are generated and do not correspond to an input
|
||||
* [SourceFile].
|
||||
*/
|
||||
final String source;
|
||||
|
||||
OutputFile(this.path, this.contents, {this.source});
|
||||
|
||||
String toString() => "#<OutputFile $path>";
|
||||
}
|
29
pkg/polymer/lib/src/html5_utils.dart
Normal file
29
pkg/polymer/lib/src/html5_utils.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// TODO(jmesserly): html5lib might be a better home for this.
|
||||
// But at the moment we only need it here.
|
||||
|
||||
library html5_utils;
|
||||
|
||||
/**
|
||||
* HTML attributes that expect a URL value.
|
||||
* <http://dev.w3.org/html5/spec/section-index.html#attributes-1>
|
||||
*
|
||||
* Every one of these attributes is a URL in every context where it is used in
|
||||
* the DOM. The comments show every DOM element where an attribute can be used.
|
||||
*/
|
||||
const urlAttributes = const [
|
||||
'action', // in form
|
||||
'background', // in body
|
||||
'cite', // in blockquote, del, ins, q
|
||||
'data', // in object
|
||||
'formaction', // in button, input
|
||||
'href', // in a, area, link, base, command
|
||||
'icon', // in command
|
||||
'manifest', // in html
|
||||
'poster', // in video
|
||||
'src', // in audio, embed, iframe, img, input, script, source, track,
|
||||
// video
|
||||
];
|
330
pkg/polymer/lib/src/info.dart
Normal file
330
pkg/polymer/lib/src/info.dart
Normal file
|
@ -0,0 +1,330 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Datatypes holding information extracted by the analyzer and used by later
|
||||
* phases of the compiler.
|
||||
*/
|
||||
library polymer.src.info;
|
||||
|
||||
import 'dart:collection' show SplayTreeMap, LinkedHashMap;
|
||||
|
||||
import 'package:analyzer_experimental/src/generated/ast.dart';
|
||||
import 'package:csslib/visitor.dart';
|
||||
import 'package:html5lib/dom.dart';
|
||||
import 'package:source_maps/span.dart' show Span;
|
||||
|
||||
import 'dart_parser.dart' show DartCodeInfo;
|
||||
import 'messages.dart';
|
||||
import 'summary.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/**
|
||||
* Information that is global. Roughly corresponds to `window` and `document`.
|
||||
*/
|
||||
class GlobalInfo {
|
||||
/**
|
||||
* Pseudo-element names exposed in a component via a pseudo attribute.
|
||||
* The name is only available from CSS (not Dart code) so they're mangled.
|
||||
* The same pseudo-element in different components maps to the same
|
||||
* mangled name (as the pseudo-element is scoped inside of the component).
|
||||
*/
|
||||
final Map<String, String> pseudoElements = <String, String>{};
|
||||
|
||||
/** All components declared in the application. */
|
||||
final Map<String, ComponentInfo> components = new SplayTreeMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Information for any library-like input. We consider each HTML file a library,
|
||||
* and each component declaration a library as well. Hence we use this as a base
|
||||
* class for both [FileInfo] and [ComponentInfo]. Both HTML files and components
|
||||
* can have .dart code provided by the user for top-level user scripts and
|
||||
* component-level behavior code. This code can either be inlined in the HTML
|
||||
* file or included in a script tag with the "src" attribute.
|
||||
*/
|
||||
abstract class LibraryInfo implements LibrarySummary {
|
||||
|
||||
/** Whether there is any code associated with the page/component. */
|
||||
bool get codeAttached => inlinedCode != null || externalFile != null;
|
||||
|
||||
/**
|
||||
* The actual inlined code. Use [userCode] if you want the code from this file
|
||||
* or from an external file.
|
||||
*/
|
||||
DartCodeInfo inlinedCode;
|
||||
|
||||
/**
|
||||
* If this library's code was loaded using a script tag (e.g. in a component),
|
||||
* [externalFile] has the path to such Dart file relative from the compiler's
|
||||
* base directory.
|
||||
*/
|
||||
UrlInfo externalFile;
|
||||
|
||||
/** Info asscociated with [externalFile], if any. */
|
||||
FileInfo externalCode;
|
||||
|
||||
/**
|
||||
* The inverse of [externalCode]. If this .dart file was imported via a script
|
||||
* tag, this refers to the HTML file that imported it.
|
||||
*/
|
||||
LibraryInfo htmlFile;
|
||||
|
||||
/** File where the top-level code was defined. */
|
||||
UrlInfo get dartCodeUrl;
|
||||
|
||||
/**
|
||||
* Name of the file that will hold any generated Dart code for this library
|
||||
* unit. Note this is initialized after parsing.
|
||||
*/
|
||||
String outputFilename;
|
||||
|
||||
/** Parsed cssSource. */
|
||||
List<StyleSheet> styleSheets = [];
|
||||
|
||||
/** This is used in transforming Dart code to track modified files. */
|
||||
bool modified = false;
|
||||
|
||||
/**
|
||||
* This is used in transforming Dart code to compute files that reference
|
||||
* [modified] files.
|
||||
*/
|
||||
List<FileInfo> referencedBy = [];
|
||||
|
||||
/**
|
||||
* Components used within this library unit. For [FileInfo] these are
|
||||
* components used directly in the page. For [ComponentInfo] these are
|
||||
* components used within their shadowed template.
|
||||
*/
|
||||
final Map<ComponentSummary, bool> usedComponents =
|
||||
new LinkedHashMap<ComponentSummary, bool>();
|
||||
|
||||
/**
|
||||
* The actual code, either inlined or from an external file, or `null` if none
|
||||
* was defined.
|
||||
*/
|
||||
DartCodeInfo get userCode =>
|
||||
externalCode != null ? externalCode.inlinedCode : inlinedCode;
|
||||
}
|
||||
|
||||
/** Information extracted at the file-level. */
|
||||
class FileInfo extends LibraryInfo implements HtmlFileSummary {
|
||||
/** Relative path to this file from the compiler's base directory. */
|
||||
final UrlInfo inputUrl;
|
||||
|
||||
/**
|
||||
* Whether this file should be treated as the entry point of the web app, i.e.
|
||||
* the file users navigate to in their browser. This will be true if this file
|
||||
* was passed in the command line to the dwc compiler, and the
|
||||
* `--components_only` flag was omitted.
|
||||
*/
|
||||
final bool isEntryPoint;
|
||||
|
||||
// TODO(terry): Ensure that that the libraryName is a valid identifier:
|
||||
// a..z || A..Z || _ [a..z || A..Z || 0..9 || _]*
|
||||
String get libraryName =>
|
||||
path.basename(inputUrl.resolvedPath).replaceAll('.', '_');
|
||||
|
||||
/** File where the top-level code was defined. */
|
||||
UrlInfo get dartCodeUrl => externalFile != null ? externalFile : inputUrl;
|
||||
|
||||
/**
|
||||
* All custom element definitions in this file. This may contain duplicates.
|
||||
* Normally you should use [components] for lookup.
|
||||
*/
|
||||
final List<ComponentInfo> declaredComponents = new List<ComponentInfo>();
|
||||
|
||||
/**
|
||||
* All custom element definitions defined in this file or imported via
|
||||
*`<link rel='components'>` tag. Maps from the tag name to the component
|
||||
* information. This map is sorted by the tag name.
|
||||
*/
|
||||
final Map<String, ComponentSummary> components =
|
||||
new SplayTreeMap<String, ComponentSummary>();
|
||||
|
||||
/** Files imported with `<link rel="import">` */
|
||||
final List<UrlInfo> componentLinks = <UrlInfo>[];
|
||||
|
||||
/** Files imported with `<link rel="stylesheet">` */
|
||||
final List<UrlInfo> styleSheetHrefs = <UrlInfo>[];
|
||||
|
||||
/** Root is associated with the body node. */
|
||||
Element body;
|
||||
|
||||
FileInfo(this.inputUrl, [this.isEntryPoint = false]);
|
||||
|
||||
/**
|
||||
* Query for an [Element] matching the provided [tag], starting from the
|
||||
* [body].
|
||||
*/
|
||||
Element query(String tag) => body.query(tag);
|
||||
}
|
||||
|
||||
|
||||
/** Information about a web component definition declared locally. */
|
||||
// TODO(sigmund): use a mixin to pull in ComponentSummary.
|
||||
class ComponentInfo extends LibraryInfo implements ComponentSummary {
|
||||
/** The file that declares this component. */
|
||||
final FileInfo declaringFile;
|
||||
|
||||
/** The component tag name, defined with the `name` attribute on `element`. */
|
||||
final String tagName;
|
||||
|
||||
/**
|
||||
* The tag name that this component extends, defined with the `extends`
|
||||
* attribute on `element`.
|
||||
*/
|
||||
final String extendsTag;
|
||||
|
||||
/**
|
||||
* The component info associated with the [extendsTag] name, if any.
|
||||
* This will be `null` if the component extends a built-in HTML tag, or
|
||||
* if the analyzer has not run yet.
|
||||
*/
|
||||
ComponentSummary extendsComponent;
|
||||
|
||||
/** The Dart class containing the component's behavior. */
|
||||
String className;
|
||||
|
||||
/** The Dart class declaration. */
|
||||
ClassDeclaration get classDeclaration => _classDeclaration;
|
||||
ClassDeclaration _classDeclaration;
|
||||
|
||||
/** The declaring `<element>` tag. */
|
||||
final Node element;
|
||||
|
||||
/** File where this component was defined. */
|
||||
UrlInfo get dartCodeUrl => externalFile != null
|
||||
? externalFile : declaringFile.inputUrl;
|
||||
|
||||
/**
|
||||
* True if [tagName] was defined by more than one component. If this happened
|
||||
* we will skip over the component.
|
||||
*/
|
||||
bool hasConflict = false;
|
||||
|
||||
ComponentInfo(this.element, this.declaringFile, this.tagName,
|
||||
this.extendsTag);
|
||||
|
||||
/**
|
||||
* Gets the HTML tag extended by the base of the component hierarchy.
|
||||
* Equivalent to [extendsTag] if this inherits directly from an HTML element,
|
||||
* in other words, if [extendsComponent] is null.
|
||||
*/
|
||||
String get baseExtendsTag =>
|
||||
extendsComponent == null ? extendsTag : extendsComponent.baseExtendsTag;
|
||||
|
||||
Span get sourceSpan => element.sourceSpan;
|
||||
|
||||
/** Is apply-author-styles enabled. */
|
||||
bool get hasAuthorStyles =>
|
||||
element.attributes.containsKey('apply-author-styles');
|
||||
|
||||
/**
|
||||
* Finds the declaring class, and initializes [className] and
|
||||
* [classDeclaration]. Also [userCode] is generated if there was no script.
|
||||
*/
|
||||
void findClassDeclaration(Messages messages) {
|
||||
var constructor = element.attributes['constructor'];
|
||||
className = constructor != null ? constructor :
|
||||
toCamelCase(tagName, startUppercase: true);
|
||||
|
||||
// If we don't have any code, generate a small class definition, and
|
||||
// pretend the user wrote it as inlined code.
|
||||
if (userCode == null) {
|
||||
var superclass = extendsComponent != null ? extendsComponent.className
|
||||
: 'autogenerated.PolymerElement';
|
||||
inlinedCode = new DartCodeInfo(null, null, [],
|
||||
'class $className extends $superclass {\n}', null);
|
||||
}
|
||||
|
||||
var code = userCode.code;
|
||||
_classDeclaration = userCode.findClass(className);
|
||||
if (_classDeclaration == null) {
|
||||
// Check for deprecated x-tags implied constructor.
|
||||
if (tagName.startsWith('x-') && constructor == null) {
|
||||
var oldCtor = toCamelCase(tagName.substring(2), startUppercase: true);
|
||||
_classDeclaration = userCode.findClass(oldCtor);
|
||||
if (_classDeclaration != null) {
|
||||
messages.warning('Implied constructor name for x-tags has changed to '
|
||||
'"$className". You should rename your class or add a '
|
||||
'constructor="$oldCtor" attribute to the element declaration. '
|
||||
'Also custom tags are not required to start with "x-" if their '
|
||||
'name has at least one dash.',
|
||||
element.sourceSpan);
|
||||
className = oldCtor;
|
||||
}
|
||||
}
|
||||
|
||||
if (_classDeclaration == null) {
|
||||
messages.error('please provide a class definition '
|
||||
'for $className:\n $code', element.sourceSpan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String toString() => '#<ComponentInfo $tagName '
|
||||
'${inlinedCode != null ? "inline" : "from ${dartCodeUrl.resolvedPath}"}>';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Information extracted about a URL that refers to another file. This is
|
||||
* mainly introduced to be able to trace back where URLs come from when
|
||||
* reporting errors.
|
||||
*/
|
||||
class UrlInfo {
|
||||
/** Original url. */
|
||||
final String url;
|
||||
|
||||
/** Path that the URL points to. */
|
||||
final String resolvedPath;
|
||||
|
||||
/** Original source location where the URL was extracted from. */
|
||||
final Span sourceSpan;
|
||||
|
||||
UrlInfo(this.url, this.resolvedPath, this.sourceSpan);
|
||||
|
||||
/**
|
||||
* Resolve a path from an [url] found in a file located at [inputUrl].
|
||||
* Returns null for absolute [url]. Unless [ignoreAbsolute] is true, reports
|
||||
* an error message if the url is an absolute url.
|
||||
*/
|
||||
static UrlInfo resolve(String url, UrlInfo inputUrl, Span span,
|
||||
String packageRoot, Messages messages, {bool ignoreAbsolute: false}) {
|
||||
|
||||
var uri = Uri.parse(url);
|
||||
if (uri.host != '' || (uri.scheme != '' && uri.scheme != 'package')) {
|
||||
if (!ignoreAbsolute) {
|
||||
messages.error('absolute paths not allowed here: "$url"', span);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var target;
|
||||
if (url.startsWith('package:')) {
|
||||
target = path.join(packageRoot, url.substring(8));
|
||||
} else if (path.isAbsolute(url)) {
|
||||
if (!ignoreAbsolute) {
|
||||
messages.error('absolute paths not allowed here: "$url"', span);
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
target = path.join(path.dirname(inputUrl.resolvedPath), url);
|
||||
url = pathToUrl(path.normalize(path.join(
|
||||
path.dirname(inputUrl.url), url)));
|
||||
}
|
||||
target = path.normalize(target);
|
||||
|
||||
return new UrlInfo(url, target, span);
|
||||
}
|
||||
|
||||
bool operator ==(UrlInfo other) =>
|
||||
url == other.url && resolvedPath == other.resolvedPath;
|
||||
|
||||
int get hashCode => resolvedPath.hashCode;
|
||||
|
||||
String toString() => "#<UrlInfo url: $url, resolvedPath: $resolvedPath>";
|
||||
}
|
149
pkg/polymer/lib/src/messages.dart
Normal file
149
pkg/polymer/lib/src/messages.dart
Normal file
|
@ -0,0 +1,149 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library messages;
|
||||
|
||||
import 'dart:json' as json;
|
||||
|
||||
import 'package:barback/barback.dart' show TransformLogger;
|
||||
import 'package:source_maps/span.dart' show Span;
|
||||
import 'package:logging/logging.dart' show Level;
|
||||
|
||||
import 'compiler_options.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
/** Map between error levels and their display color. */
|
||||
final Map<Level, String> _ERROR_COLORS = (() {
|
||||
var colorsMap = new Map<Level, String>();
|
||||
colorsMap[Level.SEVERE] = RED_COLOR;
|
||||
colorsMap[Level.WARNING] = MAGENTA_COLOR;
|
||||
colorsMap[Level.INFO] = GREEN_COLOR;
|
||||
return colorsMap;
|
||||
})();
|
||||
|
||||
/** A single message from the compiler. */
|
||||
class Message {
|
||||
final Level level;
|
||||
final String message;
|
||||
final Span span;
|
||||
final bool useColors;
|
||||
|
||||
Message(this.level, this.message, {this.span, this.useColors: false});
|
||||
|
||||
String get kind => level == Level.SEVERE ? 'error' :
|
||||
(level == Level.WARNING ? 'warning' : 'info');
|
||||
|
||||
String toString() {
|
||||
var output = new StringBuffer();
|
||||
bool colors = useColors && _ERROR_COLORS.containsKey(level);
|
||||
var levelColor = _ERROR_COLORS[level];
|
||||
if (colors) output.write(levelColor);
|
||||
output..write(kind)..write(' ');
|
||||
if (colors) output.write(NO_COLOR);
|
||||
|
||||
if (span == null) {
|
||||
output.write(message);
|
||||
} else {
|
||||
output.write(span.getLocationMessage(message, useColors: colors,
|
||||
color: levelColor));
|
||||
}
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
String toJson() {
|
||||
if (span == null) return toString();
|
||||
return json.stringify([{
|
||||
'method': kind,
|
||||
'params': {
|
||||
'file': span.sourceUrl,
|
||||
'message': message,
|
||||
'line': span.start.line + 1,
|
||||
'charStart': span.start.offset,
|
||||
'charEnd': span.end.offset,
|
||||
}
|
||||
}]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class tracks and prints information, warnings, and errors emitted by the
|
||||
* compiler.
|
||||
*/
|
||||
class Messages implements TransformLogger {
|
||||
final CompilerOptions options;
|
||||
final bool shouldPrint;
|
||||
|
||||
final List<Message> messages = <Message>[];
|
||||
|
||||
Messages({CompilerOptions options, this.shouldPrint: true})
|
||||
: options = options != null ? options : new CompilerOptions();
|
||||
|
||||
/**
|
||||
* Creates a new instance of [Messages] which doesn't write messages to
|
||||
* the console.
|
||||
*/
|
||||
Messages.silent(): this(shouldPrint: false);
|
||||
|
||||
/**
|
||||
* True if we have an error that prevents correct codegen.
|
||||
* For example, if we failed to read an input file.
|
||||
*/
|
||||
bool get hasErrors => messages.any((m) => m.level == Level.SEVERE);
|
||||
|
||||
// Convenience methods for testing
|
||||
int get length => messages.length;
|
||||
|
||||
Message operator[](int index) => messages[index];
|
||||
|
||||
void clear() {
|
||||
messages.clear();
|
||||
}
|
||||
|
||||
/** [message] is considered a static compile-time error by the Dart lang. */
|
||||
void error(String message, [Span span]) {
|
||||
var msg = new Message(Level.SEVERE, message, span: span,
|
||||
useColors: options.useColors);
|
||||
|
||||
messages.add(msg);
|
||||
printMessage(msg);
|
||||
}
|
||||
|
||||
/** [message] is considered a type warning by the Dart lang. */
|
||||
void warning(String message, [Span span]) {
|
||||
if (options.warningsAsErrors) {
|
||||
error(message, span);
|
||||
} else {
|
||||
var msg = new Message(Level.WARNING, message,
|
||||
span: span, useColors: options.useColors);
|
||||
|
||||
messages.add(msg);
|
||||
printMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// the list of error messages. Empty list, if there are no error messages.
|
||||
List<Message> get errors =>
|
||||
messages.where((m) => m.level == Level.SEVERE).toList();
|
||||
|
||||
/// the list of warning messages. Empty list if there are no warning messages.
|
||||
List<Message> get warnings =>
|
||||
messages.where((m) => m.level == Level.WARNING).toList();
|
||||
|
||||
/**
|
||||
* [message] at [span] will tell the user about what the compiler
|
||||
* is doing.
|
||||
*/
|
||||
void info(String message, [Span span]) {
|
||||
var msg = new Message(Level.INFO, message, span: span,
|
||||
useColors: options.useColors);
|
||||
|
||||
messages.add(msg);
|
||||
if (options.verbose) printMessage(msg);
|
||||
}
|
||||
|
||||
void printMessage(msg) {
|
||||
if (shouldPrint) print(options.jsonFormat ? msg.toJson() : msg);
|
||||
}
|
||||
}
|
170
pkg/polymer/lib/src/paths.dart
Normal file
170
pkg/polymer/lib/src/paths.dart
Normal file
|
@ -0,0 +1,170 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Holds path information that is used by the WebUI compiler to find files,
|
||||
* compute their output location and relative paths between them.
|
||||
*/
|
||||
library polymer.src.paths;
|
||||
|
||||
import 'info.dart' show UrlInfo;
|
||||
import 'messages.dart';
|
||||
import 'summary.dart';
|
||||
import 'utils.dart' show path, pathToUrl;
|
||||
|
||||
/**
|
||||
* Stores information about paths and computes mappings between input and output
|
||||
* path locations.
|
||||
*/
|
||||
class PathMapper {
|
||||
/**
|
||||
* Common prefix to all input paths that are read from the file system. The
|
||||
* output generated by the compiler will reflect the directory structure
|
||||
* starting from [_baseDir]. For instance, if [_baseDir] is `a/b/c` and
|
||||
* [_outputDir] is `g/h/`, then the corresponding output file for
|
||||
* `a/b/c/e/f.html` will be under `g/h/e/f.html.dart`.
|
||||
*/
|
||||
final String _baseDir;
|
||||
|
||||
/** Base path where all output is generated. */
|
||||
final String _outputDir;
|
||||
|
||||
/** The package root directory. */
|
||||
final String packageRoot;
|
||||
|
||||
/** Whether to add prefixes and to output file names. */
|
||||
final bool _mangleFilenames;
|
||||
|
||||
final bool _rewriteUrls;
|
||||
|
||||
bool get _rewritePackageImports => _rewriteUrls || !_mangleFilenames;
|
||||
|
||||
/** Default prefix added to all filenames. */
|
||||
static const String _DEFAULT_PREFIX = '_';
|
||||
|
||||
PathMapper(String baseDir, String outputDir, this.packageRoot,
|
||||
bool forceMangle, this._rewriteUrls)
|
||||
: _baseDir = baseDir,
|
||||
_outputDir = outputDir,
|
||||
_mangleFilenames = forceMangle || (baseDir == outputDir);
|
||||
|
||||
/** Add a prefix and [suffix] if [_mangleFilenames] is true */
|
||||
String mangle(String name, String suffix, [bool forceSuffix = false]) =>
|
||||
_mangleFilenames ? "$_DEFAULT_PREFIX$name$suffix"
|
||||
: (forceSuffix ? "$name$suffix" : name);
|
||||
|
||||
/**
|
||||
* Checks that `input.resolvedPath` is a valid input path. It must be in
|
||||
* [_baseDir] and must not be in the [_outputDir]. If not, an error message
|
||||
* is added to [messages].
|
||||
*/
|
||||
bool checkInputPath(UrlInfo input, Messages messages) {
|
||||
if (_mangleFilenames) return true;
|
||||
var canonicalized = path.normalize(input.resolvedPath);
|
||||
var parentDir = '..${path.separator}';
|
||||
if (!path.relative(canonicalized, from: _outputDir).startsWith(parentDir)) {
|
||||
messages.error(
|
||||
'The file ${input.resolvedPath} cannot be processed. '
|
||||
'Files cannot be under the output folder (${_outputDir}).',
|
||||
input.sourceSpan);
|
||||
return false;
|
||||
}
|
||||
if (path.relative(canonicalized, from: _baseDir).startsWith(parentDir)) {
|
||||
messages.error(
|
||||
'The file ${input.resolvedPath} cannot be processed. '
|
||||
'All processed files must be under the base folder (${_baseDir}), you'
|
||||
' can specify the base folder using the --basedir flag.',
|
||||
input.sourceSpan);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to the output file corresponding to [input], by adding
|
||||
* [_DEFAULT_PREFIX] and a [suffix] to its file name.
|
||||
*/
|
||||
String outputPath(String input, String suffix, [bool forceSuffix = false]) =>
|
||||
path.join(outputDirPath(input),
|
||||
mangle(path.basename(input), suffix, forceSuffix));
|
||||
|
||||
/** The path to the output file corresponding to [info]. */
|
||||
String outputLibraryPath(LibrarySummary lib) =>
|
||||
path.join(outputDirPath(lib.dartCodeUrl.resolvedPath),
|
||||
lib.outputFilename);
|
||||
|
||||
/** The corresponding output directory for [input]'s directory. */
|
||||
String outputDirPath(String input) {
|
||||
return _rewritePackages(path.normalize(
|
||||
path.join(_outputDir, path.relative(
|
||||
path.dirname(input), from: _baseDir))));
|
||||
}
|
||||
|
||||
/**
|
||||
* We deal with `packages/` directories in a very special way. We assume it
|
||||
* points to resources loaded from other pub packages. If an output directory
|
||||
* is specified, the compiler will create a packages symlink so that
|
||||
* `package:` imports work.
|
||||
*
|
||||
* To make it possible to share components through pub, we allow using tags of
|
||||
* the form `<link rel="import" href="packages/...">`, so that you can
|
||||
* refer to components within the packages symlink. Regardless of whether an
|
||||
* --out option was given to the compiler, we don't want to generate files
|
||||
* inside `packages/` for those components. Instead we will generate such
|
||||
* code in a special directory called `_from_packages/`.
|
||||
*/
|
||||
String _rewritePackages(String outputPath) {
|
||||
// TODO(jmesserly): this should match against packageRoot instead.
|
||||
if (!outputPath.contains('packages')) return outputPath;
|
||||
if (!_rewritePackageImports) return outputPath;
|
||||
var segments = path.split(outputPath);
|
||||
return path.joinAll(
|
||||
segments.map((s) => s == 'packages' ? '_from_packages' : s));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a url to import/export the output library represented by [target]
|
||||
* from the output library of [src]. In other words, a url to import or export
|
||||
* `target.outputFilename` from `src.outputFilename`.
|
||||
*/
|
||||
String importUrlFor(LibrarySummary src, LibrarySummary target) {
|
||||
if (!_rewritePackageImports &&
|
||||
target.dartCodeUrl.url.startsWith('package:')) {
|
||||
return pathToUrl(path.join(path.dirname(target.dartCodeUrl.url),
|
||||
target.outputFilename));
|
||||
}
|
||||
var srcDir = path.dirname(src.dartCodeUrl.resolvedPath);
|
||||
var relDir = path.relative(
|
||||
path.dirname(target.dartCodeUrl.resolvedPath), from: srcDir);
|
||||
return pathToUrl(_rewritePackages(path.normalize(
|
||||
path.join(relDir, target.outputFilename))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a [target] url seen in [src] (e.g. a Dart import, a .css href in
|
||||
* an HTML file, etc) into a corresponding url from the output file associated
|
||||
* with [src]. This will keep 'package:', 'dart:', path-absolute, and absolute
|
||||
* urls intact, but it will fix relative paths to walk from the output
|
||||
* directory back to the input directory. An exception will be thrown if
|
||||
* [target] is not under [_baseDir].
|
||||
*/
|
||||
String transformUrl(String src, String target) {
|
||||
var uri = Uri.parse(target);
|
||||
if (uri.isAbsolute) return target;
|
||||
if (!uri.scheme.isEmpty) return target;
|
||||
if (!uri.host.isEmpty) return target;
|
||||
if (uri.path.isEmpty) return target; // Implies standalone ? or # in URI.
|
||||
if (path.isAbsolute(target)) return target;
|
||||
|
||||
return pathToUrl(path.normalize(path.relative(
|
||||
path.join(path.dirname(src), target), from: outputDirPath(src))));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a "mangled" name, with a prefix and [suffix] depending on the
|
||||
* compiler's settings. [forceSuffix] causes [suffix] to be appended even if
|
||||
* the compiler is not mangling names.
|
||||
*/
|
||||
typedef String NameMangler(String name, String suffix, [bool forceSuffix]);
|
88
pkg/polymer/lib/src/summary.dart
Normal file
88
pkg/polymer/lib/src/summary.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Summary information for components and libraries.
|
||||
*
|
||||
* These classes are used for modular compilation. Summaries are a subset of the
|
||||
* information collected by Info objects (see `info.dart`). When we are
|
||||
* compiling a single file, the information extracted from that file is stored
|
||||
* as info objects, but any information that is needed from other files (like
|
||||
* imported components) is stored as a summary.
|
||||
*/
|
||||
library polymer.src.summary;
|
||||
|
||||
import 'package:source_maps/span.dart' show Span;
|
||||
|
||||
// TODO(sigmund): consider moving UrlInfo out of info.dart
|
||||
import 'info.dart' show UrlInfo;
|
||||
|
||||
/**
|
||||
* Summary information from other library-like objects, which includes HTML
|
||||
* components and dart libraries).
|
||||
*/
|
||||
class LibrarySummary {
|
||||
/** Path to the sources represented by this summary. */
|
||||
final UrlInfo dartCodeUrl;
|
||||
|
||||
/** Name given to this source after it was compiled. */
|
||||
final String outputFilename;
|
||||
|
||||
LibrarySummary(this.dartCodeUrl, this.outputFilename);
|
||||
}
|
||||
|
||||
/** Summary information for an HTML file that defines custom elements. */
|
||||
class HtmlFileSummary extends LibrarySummary {
|
||||
/**
|
||||
* Summary of each component defined either explicitly the HTML file or
|
||||
* included transitively from `<link rel="import">` tags.
|
||||
*/
|
||||
final Map<String, ComponentSummary> components;
|
||||
|
||||
HtmlFileSummary(UrlInfo dartCodeUrl, String outputFilename, this.components)
|
||||
: super(dartCodeUrl, outputFilename);
|
||||
}
|
||||
|
||||
/** Information about a web component definition. */
|
||||
class ComponentSummary extends LibrarySummary {
|
||||
/** The component tag name, defined with the `name` attribute on `element`. */
|
||||
final String tagName;
|
||||
|
||||
/**
|
||||
* The tag name that this component extends, defined with the `extends`
|
||||
* attribute on `element`.
|
||||
*/
|
||||
final String extendsTag;
|
||||
|
||||
/**
|
||||
* The Dart class containing the component's behavior, derived from tagName or
|
||||
* defined in the `constructor` attribute on `element`.
|
||||
*/
|
||||
final String className;
|
||||
|
||||
/** Summary of the base component, if any. */
|
||||
final ComponentSummary extendsComponent;
|
||||
|
||||
/**
|
||||
* True if [tagName] was defined by more than one component. Used internally
|
||||
* by the analyzer. Conflicting component will be skipped by the compiler.
|
||||
*/
|
||||
bool hasConflict;
|
||||
|
||||
/** Original span where this component is declared. */
|
||||
final Span sourceSpan;
|
||||
|
||||
ComponentSummary(UrlInfo dartCodeUrl, String outputFilename,
|
||||
this.tagName, this.extendsTag, this.className, this.extendsComponent,
|
||||
this.sourceSpan, [this.hasConflict = false])
|
||||
: super(dartCodeUrl, outputFilename);
|
||||
|
||||
/**
|
||||
* Gets the HTML tag extended by the base of the component hierarchy.
|
||||
* Equivalent to [extendsTag] if this inherits directly from an HTML element,
|
||||
* in other words, if [extendsComponent] is null.
|
||||
*/
|
||||
String get baseExtendsTag =>
|
||||
extendsComponent == null ? extendsTag : extendsComponent.baseExtendsTag;
|
||||
}
|
177
pkg/polymer/lib/src/utils.dart
Normal file
177
pkg/polymer/lib/src/utils.dart
Normal file
|
@ -0,0 +1,177 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library polymer.src.utils;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:path/path.dart' show Builder;
|
||||
export 'utils_observe.dart' show toCamelCase, toHyphenedName;
|
||||
|
||||
/**
|
||||
* An instance of the pathos library builder. We could just use the default
|
||||
* builder in pathos, but we add this indirection to make it possible to run
|
||||
* unittest for windows paths.
|
||||
*/
|
||||
Builder path = new Builder();
|
||||
|
||||
/** Convert a OS specific path into a url. */
|
||||
String pathToUrl(String relPath) =>
|
||||
(path.separator == '/') ? relPath : path.split(relPath).join('/');
|
||||
|
||||
/**
|
||||
* Invokes [callback], logs how long it took to execute in ms, and returns
|
||||
* whatever [callback] returns. The log message will be printed if [printTime]
|
||||
* is true.
|
||||
*/
|
||||
time(String logMessage, callback(),
|
||||
{bool printTime: false, bool useColors: false}) {
|
||||
final watch = new Stopwatch();
|
||||
watch.start();
|
||||
var result = callback();
|
||||
watch.stop();
|
||||
final duration = watch.elapsedMilliseconds;
|
||||
if (printTime) {
|
||||
_printMessage(logMessage, duration, useColors);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes [callback], logs how long it takes from the moment [callback] is
|
||||
* executed until the future it returns is completed. Returns the future
|
||||
* returned by [callback]. The log message will be printed if [printTime]
|
||||
* is true.
|
||||
*/
|
||||
Future asyncTime(String logMessage, Future callback(),
|
||||
{bool printTime: false, bool useColors: false}) {
|
||||
final watch = new Stopwatch();
|
||||
watch.start();
|
||||
return callback()..then((_) {
|
||||
watch.stop();
|
||||
final duration = watch.elapsedMilliseconds;
|
||||
if (printTime) {
|
||||
_printMessage(logMessage, duration, useColors);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _printMessage(String logMessage, int duration, bool useColors) {
|
||||
var buf = new StringBuffer();
|
||||
buf.write(logMessage);
|
||||
for (int i = logMessage.length; i < 60; i++) buf.write(' ');
|
||||
buf.write(' -- ');
|
||||
if (useColors) {
|
||||
buf.write(GREEN_COLOR);
|
||||
}
|
||||
if (duration < 10) buf.write(' ');
|
||||
if (duration < 100) buf.write(' ');
|
||||
buf..write(duration)..write(' ms');
|
||||
if (useColors) {
|
||||
buf.write(NO_COLOR);
|
||||
}
|
||||
print(buf.toString());
|
||||
}
|
||||
|
||||
// Color constants used for generating messages.
|
||||
final String GREEN_COLOR = '\u001b[32m';
|
||||
final String RED_COLOR = '\u001b[31m';
|
||||
final String MAGENTA_COLOR = '\u001b[35m';
|
||||
final String NO_COLOR = '\u001b[0m';
|
||||
|
||||
/** A future that waits until all added [Future]s complete. */
|
||||
// TODO(sigmund): this should be part of the futures/core libraries.
|
||||
class FutureGroup {
|
||||
static const _FINISHED = -1;
|
||||
|
||||
int _pending = 0;
|
||||
Future _failedTask;
|
||||
final Completer<List> _completer = new Completer<List>();
|
||||
final List results = [];
|
||||
|
||||
/** Gets the task that failed, if any. */
|
||||
Future get failedTask => _failedTask;
|
||||
|
||||
/**
|
||||
* Wait for [task] to complete.
|
||||
*
|
||||
* If this group has already been marked as completed, you'll get a
|
||||
* [StateError].
|
||||
*
|
||||
* If this group has a [failedTask], new tasks will be ignored, because the
|
||||
* error has already been signaled.
|
||||
*/
|
||||
void add(Future task) {
|
||||
if (_failedTask != null) return;
|
||||
if (_pending == _FINISHED) throw new StateError("Future already completed");
|
||||
|
||||
_pending++;
|
||||
var i = results.length;
|
||||
results.add(null);
|
||||
task.then((res) {
|
||||
results[i] = res;
|
||||
if (_failedTask != null) return;
|
||||
_pending--;
|
||||
if (_pending == 0) {
|
||||
_pending = _FINISHED;
|
||||
_completer.complete(results);
|
||||
}
|
||||
}, onError: (e) {
|
||||
if (_failedTask != null) return;
|
||||
_failedTask = task;
|
||||
_completer.completeError(e, getAttachedStackTrace(e));
|
||||
});
|
||||
}
|
||||
|
||||
Future<List> get future => _completer.future;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escapes [text] for use in a Dart string.
|
||||
* [single] specifies single quote `'` vs double quote `"`.
|
||||
* [triple] indicates that a triple-quoted string, such as `'''` or `"""`.
|
||||
*/
|
||||
String escapeDartString(String text, {bool single: true, bool triple: false}) {
|
||||
// Note: don't allocate anything until we know we need it.
|
||||
StringBuffer result = null;
|
||||
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
int code = text.codeUnitAt(i);
|
||||
var replace = null;
|
||||
switch (code) {
|
||||
case 92/*'\\'*/: replace = r'\\'; break;
|
||||
case 36/*r'$'*/: replace = r'\$'; break;
|
||||
case 34/*'"'*/: if (!single) replace = r'\"'; break;
|
||||
case 39/*"'"*/: if (single) replace = r"\'"; break;
|
||||
case 10/*'\n'*/: if (!triple) replace = r'\n'; break;
|
||||
case 13/*'\r'*/: if (!triple) replace = r'\r'; break;
|
||||
|
||||
// Note: we don't escape unicode characters, under the assumption that
|
||||
// writing the file in UTF-8 will take care of this.
|
||||
|
||||
// TODO(jmesserly): do we want to replace any other non-printable
|
||||
// characters (such as \f) for readability?
|
||||
}
|
||||
|
||||
if (replace != null && result == null) {
|
||||
result = new StringBuffer(text.substring(0, i));
|
||||
}
|
||||
|
||||
if (result != null) result.write(replace != null ? replace : text[i]);
|
||||
}
|
||||
|
||||
return result == null ? text : result.toString();
|
||||
}
|
||||
|
||||
/** Iterates through an infinite sequence, starting from zero. */
|
||||
class IntIterator implements Iterator<int> {
|
||||
int _next = -1;
|
||||
|
||||
int get current => _next < 0 ? null : _next;
|
||||
|
||||
bool moveNext() {
|
||||
_next++;
|
||||
return true;
|
||||
}
|
||||
}
|
33
pkg/polymer/lib/src/utils_observe.dart
Normal file
33
pkg/polymer/lib/src/utils_observe.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library polymer.src.utils_observe;
|
||||
|
||||
/**
|
||||
* Converts a string name with hyphens into an identifier, by removing hyphens
|
||||
* and capitalizing the following letter. Optionally [startUppercase] to
|
||||
* captialize the first letter.
|
||||
*/
|
||||
String toCamelCase(String hyphenedName, {bool startUppercase: false}) {
|
||||
var segments = hyphenedName.split('-');
|
||||
int start = startUppercase ? 0 : 1;
|
||||
for (int i = start; i < segments.length; i++) {
|
||||
var segment = segments[i];
|
||||
if (segment.length > 0) {
|
||||
// Character between 'a'..'z' mapped to 'A'..'Z'
|
||||
segments[i] = '${segment[0].toUpperCase()}${segment.substring(1)}';
|
||||
}
|
||||
}
|
||||
return segments.join('');
|
||||
}
|
||||
|
||||
String toHyphenedName(String word) {
|
||||
var sb = new StringBuffer();
|
||||
for (int i = 0; i < word.length; i++) {
|
||||
var lower = word[i].toLowerCase();
|
||||
if (word[i] != lower && i > 0) sb.write('-');
|
||||
sb.write(lower);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
229
pkg/polymer/lib/testing/content_shell_test.dart
Normal file
229
pkg/polymer/lib/testing/content_shell_test.dart
Normal file
|
@ -0,0 +1,229 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Helper library to run tests in content_shell
|
||||
*/
|
||||
library polymer.testing.end2end;
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:args/args.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:polymer/dwc.dart' as dwc;
|
||||
|
||||
|
||||
/**
|
||||
* Compiles [testFile] with the web-ui compiler, and then runs the output as a
|
||||
* unit test in content_shell.
|
||||
*/
|
||||
void endToEndTests(String inputDir, String outDir, {List<String> arguments}) {
|
||||
_testHelper(new _TestOptions(inputDir, inputDir, null, outDir,
|
||||
arguments: arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles [testFile] with the web-ui compiler, and then runs the output as a
|
||||
* render test in content_shell.
|
||||
*/
|
||||
void renderTests(String baseDir, String inputDir, String expectedDir,
|
||||
String outDir, {List<String> arguments, String script, String pattern,
|
||||
bool deleteDir: true}) {
|
||||
_testHelper(new _TestOptions(baseDir, inputDir, expectedDir, outDir,
|
||||
arguments: arguments, script: script, pattern: pattern,
|
||||
deleteDir: deleteDir));
|
||||
}
|
||||
|
||||
void _testHelper(_TestOptions options) {
|
||||
expect(options, isNotNull);
|
||||
|
||||
var paths = new Directory(options.inputDir).listSync()
|
||||
.where((f) => f is File).map((f) => f.path)
|
||||
.where((p) => p.endsWith('_test.html') && options.pattern.hasMatch(p));
|
||||
|
||||
if (paths.isEmpty) return;
|
||||
|
||||
// First clear the output folder. Otherwise we can miss bugs when we fail to
|
||||
// generate a file.
|
||||
var dir = new Directory(options.outDir);
|
||||
if (dir.existsSync() && options.deleteDir) {
|
||||
print('Cleaning old output for ${path.normalize(options.outDir)}');
|
||||
dir.deleteSync(recursive: true);
|
||||
}
|
||||
dir.createSync();
|
||||
|
||||
for (var filePath in paths) {
|
||||
var filename = path.basename(filePath);
|
||||
test('compile $filename', () {
|
||||
var testArgs = ['-o', options.outDir, '--basedir', options.baseDir,
|
||||
'--deploy']
|
||||
..addAll(options.compilerArgs)
|
||||
..add(filePath);
|
||||
expect(dwc.run(testArgs, printTime: false).then((res) {
|
||||
expect(res.messages.length, 0, reason: res.messages.join('\n'));
|
||||
}), completes);
|
||||
});
|
||||
}
|
||||
|
||||
var filenames = paths.map(path.basename).toList();
|
||||
// Sort files to match the order in which run.sh runs diff.
|
||||
filenames.sort();
|
||||
|
||||
// Get the path from "input" relative to "baseDir"
|
||||
var relativeToBase = path.relative(options.inputDir, from: options.baseDir);
|
||||
var finalOutDir = path.join(options.outDir, relativeToBase);
|
||||
|
||||
runTests(String search) {
|
||||
var output;
|
||||
|
||||
for (var filename in filenames) {
|
||||
test('content_shell run $filename$search', () {
|
||||
var args = ['--dump-render-tree',
|
||||
'file://$finalOutDir/$filename$search'];
|
||||
var env = {'DART_FLAGS': '--checked'};
|
||||
expect(Process.run('content_shell', args, environment: env).then((res) {
|
||||
expect(res.exitCode, 0, reason: 'content_shell exit code: '
|
||||
'${res.exitCode}. Contents of stderr: \n${res.stderr}');
|
||||
var outs = res.stdout.split('#EOF\n')
|
||||
.where((s) => !s.trim().isEmpty).toList();
|
||||
expect(outs.length, 1);
|
||||
output = outs.first;
|
||||
}), completes);
|
||||
});
|
||||
|
||||
test('verify $filename $search', () {
|
||||
expect(output, isNotNull, reason:
|
||||
'Output not available, maybe content_shell failed to run.');
|
||||
var outPath = path.join(options.outDir, '$filename.txt');
|
||||
new File(outPath).writeAsStringSync(output);
|
||||
if (options.isRenderTest) {
|
||||
var expectedPath = path.join(options.expectedDir, '$filename.txt');
|
||||
var expected = new File(expectedPath).readAsStringSync();
|
||||
expect(output, expected, reason: 'unexpected output for <$filename>');
|
||||
} else {
|
||||
bool passes = matches(
|
||||
new RegExp('All .* tests passed')).matches(output, {});
|
||||
expect(passes, true, reason: 'unit test failed:\n$output');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool compiled = false;
|
||||
ensureCompileToJs() {
|
||||
if (compiled) return;
|
||||
compiled = true;
|
||||
|
||||
for (var filename in filenames) {
|
||||
test('dart2js $filename', () {
|
||||
// TODO(jmesserly): this depends on DWC's output scheme.
|
||||
// Alternatively we could use html5lib to find the script tag.
|
||||
var inPath = '${filename}_bootstrap.dart';
|
||||
var outPath = '${inPath}.js';
|
||||
|
||||
inPath = path.join(finalOutDir, inPath);
|
||||
outPath = path.join(finalOutDir, outPath);
|
||||
|
||||
expect(Process.run('dart2js', ['-o$outPath', inPath]).then((res) {
|
||||
expect(res.exitCode, 0, reason: 'dart2js exit code: '
|
||||
'${res.exitCode}. Contents of stderr: \n${res.stderr}. '
|
||||
'Contents of stdout: \n${res.stdout}.');
|
||||
expect(new File(outPath).existsSync(), true, reason: 'input file '
|
||||
'$inPath should have been compiled to $outPath.');
|
||||
}), completes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.runAsDart) {
|
||||
runTests('');
|
||||
}
|
||||
if (options.runAsJs) {
|
||||
ensureCompileToJs();
|
||||
runTests('?js=1');
|
||||
}
|
||||
if (options.forcePolyfillShadowDom) {
|
||||
ensureCompileToJs();
|
||||
runTests('?js=1&shadowdomjs=1');
|
||||
}
|
||||
}
|
||||
|
||||
class _TestOptions {
|
||||
final String baseDir;
|
||||
final String inputDir;
|
||||
|
||||
final String expectedDir;
|
||||
bool get isRenderTest => expectedDir != null;
|
||||
|
||||
final String outDir;
|
||||
final bool deleteDir;
|
||||
|
||||
final bool runAsDart;
|
||||
final bool runAsJs;
|
||||
final bool forcePolyfillShadowDom;
|
||||
|
||||
final List<String> compilerArgs;
|
||||
final RegExp pattern;
|
||||
|
||||
factory _TestOptions(String baseDir, String inputDir, String expectedDir,
|
||||
String outDir, {List<String> arguments, String script, String pattern,
|
||||
bool deleteDir: true}) {
|
||||
if (arguments == null) arguments = new Options().arguments;
|
||||
if (script == null) script = new Options().script;
|
||||
|
||||
var args = _parseArgs(arguments, script);
|
||||
if (args == null) return null;
|
||||
var compilerArgs = args.rest;
|
||||
var filePattern;
|
||||
if (pattern != null) {
|
||||
filePattern = new RegExp(pattern);
|
||||
} else if (compilerArgs.length > 0) {
|
||||
filePattern = new RegExp(compilerArgs[0]);
|
||||
compilerArgs = compilerArgs.sublist(1);
|
||||
} else {
|
||||
filePattern = new RegExp('.');
|
||||
}
|
||||
|
||||
var scriptDir = path.absolute(path.dirname(script));
|
||||
baseDir = path.join(scriptDir, baseDir);
|
||||
inputDir = path.join(scriptDir, inputDir);
|
||||
outDir = path.join(scriptDir, outDir);
|
||||
if (expectedDir != null) {
|
||||
expectedDir = path.join(scriptDir, expectedDir);
|
||||
}
|
||||
|
||||
return new _TestOptions._(baseDir, inputDir, expectedDir, outDir, deleteDir,
|
||||
args['dart'] == true, args['js'] == true, args['shadowdom'] == true,
|
||||
compilerArgs, filePattern);
|
||||
}
|
||||
|
||||
_TestOptions._(this.baseDir, this.inputDir, this.expectedDir, this.outDir,
|
||||
this.deleteDir, this.runAsDart, this.runAsJs,
|
||||
this.forcePolyfillShadowDom, this.compilerArgs, this.pattern);
|
||||
}
|
||||
|
||||
ArgResults _parseArgs(List<String> arguments, String script) {
|
||||
var parser = new ArgParser()
|
||||
..addFlag('dart', abbr: 'd', help: 'run on Dart VM', defaultsTo: true)
|
||||
..addFlag('js', abbr: 'j', help: 'run compiled dart2js', defaultsTo: true)
|
||||
..addFlag('shadowdom', abbr: 's',
|
||||
help: 'run dart2js and polyfilled ShadowDOM', defaultsTo: true)
|
||||
..addFlag('help', abbr: 'h', help: 'Displays this help message',
|
||||
defaultsTo: false, negatable: false);
|
||||
|
||||
showUsage() {
|
||||
print('Usage: $script [options...] [test_name_regexp]');
|
||||
print(parser.getUsage());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
var results = parser.parse(arguments);
|
||||
if (results['help']) return showUsage();
|
||||
return results;
|
||||
} on FormatException catch (e) {
|
||||
print(e.message);
|
||||
return showUsage();
|
||||
}
|
||||
}
|
147
pkg/polymer/lib/testing/testing.js
Normal file
147
pkg/polymer/lib/testing/testing.js
Normal file
|
@ -0,0 +1,147 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
(function() {
|
||||
var undoReplaceScripts = [];
|
||||
|
||||
var flags = {};
|
||||
// populate flags from location
|
||||
location.search.slice(1).split('&').forEach(function(o) {
|
||||
o = o.split('=');
|
||||
o[0] && (flags[o[0]] = o[1] || true);
|
||||
});
|
||||
|
||||
// Webkit is migrating from layoutTestController to testRunner, we use
|
||||
// layoutTestController as a fallback until that settles in.
|
||||
var runner = window.testRunner || window.layoutTestController;
|
||||
|
||||
if (runner) {
|
||||
runner.dumpAsText();
|
||||
runner.waitUntilDone();
|
||||
}
|
||||
|
||||
function dumpDOM() {
|
||||
// Undo any scripts that were modified.
|
||||
undoReplaceScripts.forEach(function(undo) { undo(); });
|
||||
|
||||
function expandShadowRoot(node) {
|
||||
for (var n = node.firstChild; n; n = n.nextSibling) {
|
||||
expandShadowRoot(n);
|
||||
}
|
||||
var shadow = node.shadowRoot || node.webkitShadowRoot ||
|
||||
node.olderShadowRoot;
|
||||
|
||||
if (shadow) {
|
||||
expandShadowRoot(shadow);
|
||||
|
||||
var name = 'shadow-root';
|
||||
if (shadow == node.olderShadowRoot) name = 'older-' + name;
|
||||
|
||||
var fakeShadow = document.createElement(name);
|
||||
while (shadow.firstChild) fakeShadow.appendChild(shadow.firstChild);
|
||||
node.insertBefore(fakeShadow, node.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jmesserly): use querySelector to workaround unwrapped "document".
|
||||
expandShadowRoot(document.querySelector('body'));
|
||||
|
||||
// Clean up all of the junk added to the DOM by js-interop and shadow CSS
|
||||
// TODO(jmesserly): it seems like we're leaking lots of dart-port attributes
|
||||
// for the document elemenet
|
||||
function cleanTree(node) {
|
||||
for (var n = node.firstChild; n; n = n.nextSibling) {
|
||||
cleanTree(n);
|
||||
}
|
||||
|
||||
// Remove dart-port attributes
|
||||
if (node.attributes) {
|
||||
for (var i = 0; i < node.attributes.length; i++) {
|
||||
if (node.attributes[i].value.indexOf('dart-port') == 0) {
|
||||
node.removeAttribute(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.tagName == 'script' &&
|
||||
node.textContent.indexOf('_DART_TEMPORARY_ATTACHED') >= 0) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jmesserly): use querySelector to workaround unwrapped "document".
|
||||
cleanTree(document.querySelector('html'));
|
||||
|
||||
var out = document.createElement('pre');
|
||||
out.textContent = document.documentElement.outerHTML;
|
||||
document.body.innerHTML = '';
|
||||
document.body.appendChild(out);
|
||||
}
|
||||
|
||||
function messageHandler(e) {
|
||||
if (e.data == 'done' && runner) {
|
||||
// On success, dump the DOM. Convert shadowRoot contents into
|
||||
// <shadow-root>
|
||||
dumpDOM();
|
||||
runner.notifyDone();
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', messageHandler, false);
|
||||
|
||||
function errorHandler(e) {
|
||||
if (runner) {
|
||||
window.setTimeout(function() { runner.notifyDone(); }, 0);
|
||||
}
|
||||
window.console.log('FAIL');
|
||||
}
|
||||
|
||||
window.addEventListener('error', errorHandler, false);
|
||||
|
||||
if (navigator.webkitStartDart && !flags.js) {
|
||||
// TODO(jmesserly): fix this so we don't need to copy from browser/dart.js
|
||||
if (!navigator.webkitStartDart()) {
|
||||
document.body.innerHTML = 'This build has expired. Please download a new Dartium at http://www.dartlang.org/dartium/index.html';
|
||||
}
|
||||
} else {
|
||||
if (flags.shadowdomjs) {
|
||||
// Allow flags to force polyfill of ShadowDOM so we can test it.
|
||||
window.__forceShadowDomPolyfill = true;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// - Support in-browser compilation.
|
||||
// - Handle inline Dart scripts.
|
||||
window.addEventListener("DOMContentLoaded", function (e) {
|
||||
// Fall back to compiled JS. Run through all the scripts and
|
||||
// replace them if they have a type that indicate that they source
|
||||
// in Dart code.
|
||||
//
|
||||
// <script type="application/dart" src="..."></script>
|
||||
//
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
var length = scripts.length;
|
||||
for (var i = 0; i < length; ++i) {
|
||||
var script = scripts[i];
|
||||
if (script.type == "application/dart") {
|
||||
// Remap foo.dart to foo.dart.js.
|
||||
if (script.src && script.src != '') {
|
||||
var jsScript = document.createElement('script');
|
||||
jsScript.src = script.src.replace(/\.dart(?=\?|$)/, '.dart.js');
|
||||
var parent = script.parentNode;
|
||||
// TODO(vsm): Find a solution for issue 8455 that works with more
|
||||
// than one script.
|
||||
document.currentScript = jsScript;
|
||||
|
||||
undoReplaceScripts.push(function() {
|
||||
parent.replaceChild(script, jsScript);
|
||||
});
|
||||
parent.replaceChild(jsScript, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
})();
|
28
pkg/polymer/pubspec.yaml
Normal file
28
pkg/polymer/pubspec.yaml
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: polymer
|
||||
author: Web UI Authors <web-ui-dev@dartlang.org>
|
||||
description: >
|
||||
Polymer.dart is a new type of library for the web, built on top of Web
|
||||
Components, and designed to leverage the evolving web platform on modern
|
||||
browsers.
|
||||
homepage: https://www.dartlang.org
|
||||
environment:
|
||||
sdk: any
|
||||
dependencies:
|
||||
analyzer_experimental: any
|
||||
args: any
|
||||
barback: any
|
||||
browser: any
|
||||
csslib: any
|
||||
custom_element: any
|
||||
html_import: any
|
||||
html5lib: any
|
||||
js: any
|
||||
logging: any
|
||||
mdv: any
|
||||
observe: any
|
||||
path: any
|
||||
polymer_expressions: any
|
||||
shadow_dom: any
|
||||
source_maps: any
|
||||
# TODO(jmesserly): make this a dev_dependency
|
||||
unittest: any
|
140
pkg/polymer/test/compiler_test.dart
Normal file
140
pkg/polymer/test/compiler_test.dart
Normal file
|
@ -0,0 +1,140 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** End-to-end tests for the [Compiler] API. */
|
||||
library compiler_test;
|
||||
|
||||
import 'package:logging/logging.dart' show Level;
|
||||
import 'package:unittest/compact_vm_config.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:polymer/src/messages.dart';
|
||||
|
||||
import 'testing.dart';
|
||||
|
||||
main() {
|
||||
useCompactVMConfiguration();
|
||||
|
||||
test('recursive dependencies', () {
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler({
|
||||
'index.html': '<head>'
|
||||
'<link rel="import" href="foo.html">'
|
||||
'<link rel="import" href="bar.html">'
|
||||
'<body><x-foo></x-foo><x-bar></x-bar>'
|
||||
'<script type="application/dart">main() {}</script>',
|
||||
'foo.html': '<head><link rel="import" href="bar.html">'
|
||||
'<body><polymer-element name="x-foo" constructor="Foo">'
|
||||
'<template><x-bar>',
|
||||
'bar.html': '<head><link rel="import" href="foo.html">'
|
||||
'<body><polymer-element name="x-bar" constructor="Boo">'
|
||||
'<template><x-foo>',
|
||||
}, messages);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, equals({
|
||||
'index.html': 1,
|
||||
'foo.html': 1,
|
||||
'bar.html': 1
|
||||
}), reason: 'Actual:\n ${fs.readCount}');
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path);
|
||||
expect(outputs, equals([
|
||||
'out/foo.html.dart',
|
||||
'out/foo.html.dart.map',
|
||||
'out/bar.html.dart',
|
||||
'out/bar.html.dart.map',
|
||||
'out/index.html.dart',
|
||||
'out/index.html.dart.map',
|
||||
'out/index.html_bootstrap.dart',
|
||||
'out/index.html',
|
||||
]));
|
||||
}));
|
||||
});
|
||||
|
||||
group('missing files', () {
|
||||
test('main script', () {
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler({
|
||||
'index.html': '<head></head><body>'
|
||||
'<script type="application/dart" src="notfound.dart"></script>'
|
||||
'</body>',
|
||||
}, messages);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
var msgs = messages.messages.where((m) =>
|
||||
m.message.contains('unable')).toList();
|
||||
|
||||
expect(msgs.length, 1);
|
||||
expect(msgs[0].level, Level.SEVERE);
|
||||
expect(msgs[0].message, contains('unable to open file'));
|
||||
expect(msgs[0].span, isNotNull);
|
||||
expect(msgs[0].span.sourceUrl, 'index.html');
|
||||
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, { 'index.html': 1, 'notfound.dart': 1 });
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path.toString());
|
||||
expect(outputs, []);
|
||||
}));
|
||||
});
|
||||
|
||||
test('component html', () {
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler({
|
||||
'index.html': '<head>'
|
||||
'<link rel="import" href="notfound.html">'
|
||||
'<body><x-foo>'
|
||||
'<script type="application/dart">main() {}</script>',
|
||||
}, messages);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
var msgs = messages.messages.where((m) =>
|
||||
m.message.contains('unable')).toList();
|
||||
|
||||
expect(msgs.length, 1);
|
||||
expect(msgs[0].level, Level.SEVERE);
|
||||
expect(msgs[0].message, contains('unable to open file'));
|
||||
expect(msgs[0].span, isNotNull);
|
||||
expect(msgs[0].span.sourceUrl, 'index.html');
|
||||
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, { 'index.html': 1, 'notfound.html': 1 });
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path.toString());
|
||||
expect(outputs, []);
|
||||
}));
|
||||
});
|
||||
|
||||
test('component script', () {
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler({
|
||||
'index.html': '<head>'
|
||||
'<link rel="import" href="foo.html">'
|
||||
'<body><x-foo></x-foo>'
|
||||
'<script type="application/dart">main() {}</script>'
|
||||
'</body>',
|
||||
'foo.html': '<body><polymer-element name="x-foo" constructor="Foo">'
|
||||
'<template></template>'
|
||||
'<script type="application/dart" src="notfound.dart"></script>',
|
||||
}, messages);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
var msgs = messages.messages.where((m) =>
|
||||
m.message.contains('unable')).toList();
|
||||
|
||||
expect(msgs.length, 1);
|
||||
expect(msgs[0].level, Level.SEVERE);
|
||||
expect(msgs[0].message, contains('unable to open file'));
|
||||
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount,
|
||||
{ 'index.html': 1, 'foo.html': 1, 'notfound.dart': 1 });
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path.toString());
|
||||
expect(outputs, []);
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
547
pkg/polymer/test/css_test.dart
Normal file
547
pkg/polymer/test/css_test.dart
Normal file
|
@ -0,0 +1,547 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library css_test;
|
||||
|
||||
import 'package:unittest/compact_vm_config.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:polymer/src/messages.dart';
|
||||
|
||||
import 'testing.dart';
|
||||
|
||||
test_simple_var() {
|
||||
Map createFiles() {
|
||||
return {
|
||||
'index.html':
|
||||
'<!DOCTYPE html>'
|
||||
'<html lang="en">'
|
||||
'<head>'
|
||||
'<meta charset="utf-8">'
|
||||
'</head>'
|
||||
'<body>'
|
||||
'<style>'
|
||||
'@main_color: var(b);'
|
||||
'@b: var(c);'
|
||||
'@c: red;'
|
||||
'</style>'
|
||||
'<style>'
|
||||
'.test { color: var(main_color); }'
|
||||
'</style>'
|
||||
'<script type="application/dart">main() {}</script>'
|
||||
'</body>'
|
||||
'</html>',
|
||||
};
|
||||
}
|
||||
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler(createFiles(), messages, errors: true,
|
||||
scopedCss: true);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, equals({
|
||||
'index.html': 1,
|
||||
}), reason: 'Actual:\n ${fs.readCount}');
|
||||
|
||||
var htmlInfo = compiler.info['index.html'];
|
||||
expect(htmlInfo.styleSheets.length, 2);
|
||||
expect(prettyPrintCss(htmlInfo.styleSheets[0]), '');
|
||||
expect(prettyPrintCss(htmlInfo.styleSheets[1]), '.test { color: red; }');
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path);
|
||||
expect(outputs, equals([
|
||||
'out/index.html.dart',
|
||||
'out/index.html.dart.map',
|
||||
'out/index.html_bootstrap.dart',
|
||||
'out/index.html',
|
||||
]));
|
||||
}));
|
||||
}
|
||||
|
||||
test_var() {
|
||||
Map createFiles() {
|
||||
return {
|
||||
'index.html':
|
||||
'<!DOCTYPE html>'
|
||||
'<html lang="en">'
|
||||
'<head>'
|
||||
'<meta charset="utf-8">'
|
||||
'</head>'
|
||||
'<body>'
|
||||
'<style>'
|
||||
'@main-color: var(b);'
|
||||
'@b: var(c);'
|
||||
'@c: red;'
|
||||
'@d: var(main-color-1, green);'
|
||||
'@border-pen: solid;'
|
||||
'@inset: 5px;'
|
||||
'@frame-color: solid orange;'
|
||||
'@big-border: 2px 2px 2px;'
|
||||
'@border-stuff: 3px dashed var(main-color);'
|
||||
'@border-2: 3px var(big-border) dashed var(main-color-1, green);'
|
||||
'@blue-border: bold var(not-found, 1px 10px blue)'
|
||||
'</style>'
|
||||
'<style>'
|
||||
'.test-1 { color: var(main-color-1, blue); }'
|
||||
'.test-2 { color: var(main-color-1, var(main-color)); }'
|
||||
'.test-3 { color: var(d, yellow); }'
|
||||
'.test-4 { color: var(d-1, yellow); }'
|
||||
'.test-5 { color: var(d-1, var(d)); }'
|
||||
'.test-6 { border: var(inset) var(border-pen) var(d); }'
|
||||
'.test-7 { border: 10px var(border-pen) var(d); }'
|
||||
'.test-8 { border: 20px var(border-pen) yellow; }'
|
||||
'.test-9 { border: 30px dashed var(d); }'
|
||||
'.test-10 { border: 40px var(frame-color);}'
|
||||
'.test-11 { border: 40px var(frame-color-1, blue);}'
|
||||
'.test-12 { border: 40px var(frame-color-1, solid blue);}'
|
||||
'.test-13 {'
|
||||
'border: 40px var(x1, var(x2, var(x3, var(frame-color)));'
|
||||
'}'
|
||||
'.test-14 { border: 40px var(x1, var(frame-color); }'
|
||||
'.test-15 { border: 40px var(x1, solid blue);}'
|
||||
'.test-16 { border: 1px 1px 2px 3px var(frame-color);}'
|
||||
'.test-17 { border: 1px 1px 2px 3px var(x1, solid blue);}'
|
||||
'.test-18 { border: 1px 1px 2px var(border-stuff);}'
|
||||
'.test-19 { border: var(big-border) var(border-stuff);}'
|
||||
'.test-20 { border: var(border-2);}'
|
||||
'.test-21 { border: var(blue-border);}'
|
||||
'</style>'
|
||||
'<script type="application/dart">main() {}</script>'
|
||||
'</body>'
|
||||
'</html>',
|
||||
};
|
||||
}
|
||||
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler(createFiles(), messages, errors: true,
|
||||
scopedCss: true);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, equals({
|
||||
'index.html': 1,
|
||||
}), reason: 'Actual:\n ${fs.readCount}');
|
||||
|
||||
var htmlInfo = compiler.info['index.html'];
|
||||
expect(htmlInfo.styleSheets.length, 2);
|
||||
expect(prettyPrintCss(htmlInfo.styleSheets[0]), '');
|
||||
expect(prettyPrintCss(htmlInfo.styleSheets[1]),
|
||||
'.test-1 { color: blue; } '
|
||||
'.test-2 { color: red; } '
|
||||
'.test-3 { color: green; } '
|
||||
'.test-4 { color: yellow; } '
|
||||
'.test-5 { color: green; } '
|
||||
'.test-6 { border: 5px solid green; } '
|
||||
'.test-7 { border: 10px solid green; } '
|
||||
'.test-8 { border: 20px solid yellow; } '
|
||||
'.test-9 { border: 30px dashed green; } '
|
||||
'.test-10 { border: 40px solid orange; } '
|
||||
'.test-11 { border: 40px blue; } '
|
||||
'.test-12 { border: 40px solid blue; } '
|
||||
'.test-13 { border: 40px solid orange; } '
|
||||
'.test-14 { border: 40px solid orange; } '
|
||||
'.test-15 { border: 40px solid blue; } '
|
||||
'.test-16 { border: 1px 1px 2px 3px solid orange; } '
|
||||
'.test-17 { border: 1px 1px 2px 3px solid blue; } '
|
||||
'.test-18 { border: 1px 1px 2px 3px dashed red; } '
|
||||
'.test-19 { border: 2px 2px 2px 3px dashed red; } '
|
||||
'.test-20 { border: 3px 2px 2px 2px dashed green; } '
|
||||
'.test-21 { border: bold 1px 10px blue; }');
|
||||
var outputs = compiler.output.map((o) => o.path);
|
||||
expect(outputs, equals([
|
||||
'out/index.html.dart',
|
||||
'out/index.html.dart.map',
|
||||
'out/index.html_bootstrap.dart',
|
||||
'out/index.html',
|
||||
]));
|
||||
}));
|
||||
}
|
||||
|
||||
test_simple_import() {
|
||||
Map createFiles() {
|
||||
return {
|
||||
'foo.css': r'''@main_color: var(b);
|
||||
@b: var(c);
|
||||
@c: red;''',
|
||||
'index.html':
|
||||
'<!DOCTYPE html>'
|
||||
'<html lang="en">'
|
||||
'<head>'
|
||||
'<meta charset="utf-8">'
|
||||
'</head>'
|
||||
'<body>'
|
||||
'<style>'
|
||||
'@import "foo.css";'
|
||||
'.test { color: var(main_color); }'
|
||||
'</style>'
|
||||
'<script type="application/dart">main() {}</script>'
|
||||
'</body>'
|
||||
'</html>',
|
||||
};
|
||||
}
|
||||
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler(createFiles(), messages, errors: true,
|
||||
scopedCss: true);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, equals({
|
||||
'foo.css': 1,
|
||||
'index.html': 1,
|
||||
}), reason: 'Actual:\n ${fs.readCount}');
|
||||
|
||||
var cssInfo = compiler.info['foo.css'];
|
||||
expect(cssInfo.styleSheets.length, 1);
|
||||
expect(prettyPrintCss(cssInfo.styleSheets[0]), '');
|
||||
|
||||
var htmlInfo = compiler.info['index.html'];
|
||||
expect(htmlInfo.styleSheets.length, 1);
|
||||
expect(prettyPrintCss(htmlInfo.styleSheets[0]),
|
||||
'@import url(foo.css); .test { color: red; }');
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path);
|
||||
expect(outputs, equals([
|
||||
'out/index.html.dart',
|
||||
'out/index.html.dart.map',
|
||||
'out/index.html_bootstrap.dart',
|
||||
'out/foo.css',
|
||||
'out/index.html',
|
||||
]));
|
||||
}));
|
||||
}
|
||||
|
||||
test_imports() {
|
||||
Map createFiles() {
|
||||
return {
|
||||
'first.css':
|
||||
'@import "third.css";'
|
||||
'@main-width: var(main-width-b);'
|
||||
'@main-width-b: var(main-width-c);'
|
||||
'@main-width-c: var(wide-width);',
|
||||
'second.css':
|
||||
'@import "fourth.css";'
|
||||
'@main-color: var(main-color-b);'
|
||||
'@main-color-b: var(main-color-c);'
|
||||
'@main-color-c: var(color-value);',
|
||||
'third.css':
|
||||
'@wide-width: var(wide-width-b);'
|
||||
'@wide-width-b: var(wide-width-c);'
|
||||
'@wide-width-c: 100px;',
|
||||
'fourth.css':
|
||||
'@color-value: var(color-value-b);'
|
||||
'@color-value-b: var(color-value-c);'
|
||||
'@color-value-c: red;',
|
||||
'index.html':
|
||||
'<!DOCTYPE html>'
|
||||
'<html lang="en">'
|
||||
'<head>'
|
||||
'<meta charset="utf-8">'
|
||||
'<link rel="stylesheet" href="first.css">'
|
||||
'</head>'
|
||||
'<body>'
|
||||
'<style>'
|
||||
'@import "first.css";'
|
||||
'@import "second.css";'
|
||||
'.test-1 { color: var(main-color); }'
|
||||
'.test-2 { width: var(main-width); }'
|
||||
'</style>'
|
||||
'<script type="application/dart">main() {}</script>'
|
||||
'</body>'
|
||||
'</html>',
|
||||
};
|
||||
}
|
||||
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler(createFiles(), messages, errors: true,
|
||||
scopedCss: true);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, equals({
|
||||
'first.css': 1,
|
||||
'second.css': 1,
|
||||
'third.css': 1,
|
||||
'fourth.css': 1,
|
||||
'index.html': 1,
|
||||
}), reason: 'Actual:\n ${fs.readCount}');
|
||||
|
||||
var firstInfo = compiler.info['first.css'];
|
||||
expect(firstInfo.styleSheets.length, 1);
|
||||
expect(prettyPrintCss(firstInfo.styleSheets[0]), '@import url(third.css);');
|
||||
|
||||
var secondInfo = compiler.info['second.css'];
|
||||
expect(secondInfo.styleSheets.length, 1);
|
||||
expect(prettyPrintCss(secondInfo.styleSheets[0]),
|
||||
'@import url(fourth.css);');
|
||||
|
||||
var thirdInfo = compiler.info['third.css'];
|
||||
expect(thirdInfo.styleSheets.length, 1);
|
||||
expect(prettyPrintCss(thirdInfo.styleSheets[0]), '');
|
||||
|
||||
var fourthInfo = compiler.info['fourth.css'];
|
||||
expect(fourthInfo.styleSheets.length, 1);
|
||||
expect(prettyPrintCss(fourthInfo.styleSheets[0]), '');
|
||||
|
||||
var htmlInfo = compiler.info['index.html'];
|
||||
expect(htmlInfo.styleSheets.length, 1);
|
||||
expect(prettyPrintCss(htmlInfo.styleSheets[0]),
|
||||
'@import url(first.css); '
|
||||
'@import url(second.css); '
|
||||
'.test-1 { color: red; } '
|
||||
'.test-2 { width: 100px; }');
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path);
|
||||
expect(outputs, equals([
|
||||
'out/index.html.dart',
|
||||
'out/index.html.dart.map',
|
||||
'out/index.html_bootstrap.dart',
|
||||
'out/first.css',
|
||||
'out/second.css',
|
||||
'out/third.css',
|
||||
'out/fourth.css',
|
||||
'out/index.html',
|
||||
]));
|
||||
}));
|
||||
}
|
||||
|
||||
test_component_var() {
|
||||
Map createFiles() {
|
||||
return {
|
||||
'index.html': '<!DOCTYPE html>'
|
||||
'<html lang="en">'
|
||||
'<head>'
|
||||
'<meta charset="utf-8">'
|
||||
'<link rel="import" href="foo.html">'
|
||||
'</head>'
|
||||
'<body>'
|
||||
'<x-foo></x-foo>'
|
||||
'<script type="application/dart">main() {}</script>'
|
||||
'</body>'
|
||||
'</html>',
|
||||
'foo.html': '<!DOCTYPE html>'
|
||||
'<html lang="en">'
|
||||
'<head>'
|
||||
'<meta charset="utf-8">'
|
||||
'</head>'
|
||||
'<body>'
|
||||
'<polymer-element name="x-foo" constructor="Foo">'
|
||||
'<template>'
|
||||
'<style scoped>'
|
||||
'@import "foo.css";'
|
||||
'.main { color: var(main_color); }'
|
||||
'.test-background { '
|
||||
'background: url(http://www.foo.com/bar.png);'
|
||||
'}'
|
||||
'</style>'
|
||||
'</template>'
|
||||
'</polymer-element>'
|
||||
'</body>'
|
||||
'</html>',
|
||||
'foo.css': r'''@main_color: var(b);
|
||||
@b: var(c);
|
||||
@c: red;
|
||||
|
||||
@one: var(two);
|
||||
@two: var(one);
|
||||
|
||||
@four: var(five);
|
||||
@five: var(six);
|
||||
@six: var(four);
|
||||
|
||||
@def-1: var(def-2);
|
||||
@def-2: var(def-3);
|
||||
@def-3: var(def-2);''',
|
||||
};
|
||||
}
|
||||
|
||||
test('var- and Less @define', () {
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler(createFiles(), messages, errors: true,
|
||||
scopedCss: true);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, equals({
|
||||
'index.html': 1,
|
||||
'foo.html': 1,
|
||||
'foo.css': 1
|
||||
}), reason: 'Actual:\n ${fs.readCount}');
|
||||
|
||||
var cssInfo = compiler.info['foo.css'];
|
||||
expect(cssInfo.styleSheets.length, 1);
|
||||
var htmlInfo = compiler.info['foo.html'];
|
||||
expect(htmlInfo.styleSheets.length, 0);
|
||||
expect(htmlInfo.declaredComponents.length, 1);
|
||||
expect(htmlInfo.declaredComponents[0].styleSheets.length, 1);
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path);
|
||||
expect(outputs, equals([
|
||||
'out/foo.html.dart',
|
||||
'out/foo.html.dart.map',
|
||||
'out/index.html.dart',
|
||||
'out/index.html.dart.map',
|
||||
'out/index.html_bootstrap.dart',
|
||||
'out/foo.css',
|
||||
'out/index.html.css',
|
||||
'out/index.html',
|
||||
]));
|
||||
|
||||
for (var file in compiler.output) {
|
||||
if (file.path == 'out/index.html.css') {
|
||||
expect(file.contents,
|
||||
'/* Auto-generated from components style tags. */\n'
|
||||
'/* DO NOT EDIT. */\n\n'
|
||||
'/* ==================================================== \n'
|
||||
' Component x-foo stylesheet \n'
|
||||
' ==================================================== */\n'
|
||||
'@import "foo.css";\n'
|
||||
'[is="x-foo"] .main {\n'
|
||||
' color: #f00;\n'
|
||||
'}\n'
|
||||
'[is="x-foo"] .test-background {\n'
|
||||
' background: url("http://www.foo.com/bar.png");\n'
|
||||
'}\n\n');
|
||||
} else if (file.path == 'out/foo.css') {
|
||||
expect(file.contents,
|
||||
'/* Auto-generated from style sheet href = foo.css */\n'
|
||||
'/* DO NOT EDIT. */\n\n\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for warning messages about var- cycles.
|
||||
expect(messages.messages.length, 8);
|
||||
|
||||
var errorMessage = messages.messages[0];
|
||||
expect(errorMessage.message, contains('var cycle detected var-def-1'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 11);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@def-1: var(def-2)');
|
||||
|
||||
errorMessage = messages.messages[1];
|
||||
expect(errorMessage.message, contains('var cycle detected var-five'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 8);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@five: var(six)');
|
||||
|
||||
errorMessage = messages.messages[2];
|
||||
expect(errorMessage.message, contains('var cycle detected var-six'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 9);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@six: var(four)');
|
||||
|
||||
errorMessage = messages.messages[3];
|
||||
expect(errorMessage.message, contains('var cycle detected var-def-3'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 13);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@def-3: var(def-2)');
|
||||
|
||||
errorMessage = messages.messages[4];
|
||||
expect(errorMessage.message, contains('var cycle detected var-two'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 5);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@two: var(one)');
|
||||
|
||||
errorMessage = messages.messages[5];
|
||||
expect(errorMessage.message, contains('var cycle detected var-def-2'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 12);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@def-2: var(def-3)');
|
||||
|
||||
errorMessage = messages.messages[6];
|
||||
expect(errorMessage.message, contains('var cycle detected var-one'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 4);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@one: var(two)');
|
||||
|
||||
errorMessage = messages.messages[7];
|
||||
expect(errorMessage.message, contains('var cycle detected var-four'));
|
||||
expect(errorMessage.span, isNotNull);
|
||||
expect(errorMessage.span.start.line, 7);
|
||||
expect(errorMessage.span.start.column, 22);
|
||||
expect(errorMessage.span.text, '@four: var(five)');
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
test_pseudo_element() {
|
||||
var messages = new Messages.silent();
|
||||
var compiler = createCompiler({
|
||||
'index.html': '<head>'
|
||||
'<link rel="import" href="foo.html">'
|
||||
'<style>'
|
||||
'.test::x-foo { background-color: red; }'
|
||||
'.test::x-foo1 { color: blue; }'
|
||||
'.test::x-foo2 { color: green; }'
|
||||
'</style>'
|
||||
'<body>'
|
||||
'<x-foo class=test></x-foo>'
|
||||
'<x-foo></x-foo>'
|
||||
'<script type="application/dart">main() {}</script>',
|
||||
'foo.html': '<head>'
|
||||
'<body><polymer-element name="x-foo" constructor="Foo">'
|
||||
'<template>'
|
||||
'<div pseudo="x-foo">'
|
||||
'<div>Test</div>'
|
||||
'</div>'
|
||||
'<div pseudo="x-foo1 x-foo2">'
|
||||
'<div>Test</div>'
|
||||
'</div>'
|
||||
'</template>',
|
||||
}, messages, scopedCss: true);
|
||||
|
||||
compiler.run().then(expectAsync1((e) {
|
||||
MockFileSystem fs = compiler.fileSystem;
|
||||
expect(fs.readCount, equals({
|
||||
'index.html': 1,
|
||||
'foo.html': 1,
|
||||
}), reason: 'Actual:\n ${fs.readCount}');
|
||||
|
||||
var outputs = compiler.output.map((o) => o.path);
|
||||
expect(outputs, equals([
|
||||
'out/foo.html.dart',
|
||||
'out/foo.html.dart.map',
|
||||
'out/index.html.dart',
|
||||
'out/index.html.dart.map',
|
||||
'out/index.html_bootstrap.dart',
|
||||
'out/index.html',
|
||||
]));
|
||||
expect(compiler.output.last.contents, contains(
|
||||
'<div pseudo="x-foo_0">'
|
||||
'<div>Test</div>'
|
||||
'</div>'
|
||||
'<div pseudo="x-foo1_1 x-foo2_2">'
|
||||
'<div>Test</div>'
|
||||
'</div>'));
|
||||
expect(compiler.output.last.contents, contains(
|
||||
'<style>.test > *[pseudo="x-foo_0"] {\n'
|
||||
' background-color: #f00;\n'
|
||||
'}\n'
|
||||
'.test > *[pseudo="x-foo1_1"] {\n'
|
||||
' color: #00f;\n'
|
||||
'}\n'
|
||||
'.test > *[pseudo="x-foo2_2"] {\n'
|
||||
' color: #008000;\n'
|
||||
'}'
|
||||
'</style>'));
|
||||
}));
|
||||
}
|
||||
|
||||
main() {
|
||||
useCompactVMConfiguration();
|
||||
|
||||
test('test_simple_var', test_simple_var);
|
||||
test('test_var', test_var);
|
||||
test('test_simple_import', test_simple_import);
|
||||
test('test_imports', test_imports);
|
||||
test('test_component_var', test_component_var);
|
||||
test('test_pseudo_element', test_pseudo_element);
|
||||
}
|
142
pkg/polymer/test/data/unit/event_path_test.html
Normal file
142
pkg/polymer/test/data/unit/event_path_test.html
Normal file
|
@ -0,0 +1,142 @@
|
|||
<!doctype html>
|
||||
<!--
|
||||
Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
for details. All rights reserved. Use of this source code is governed by a
|
||||
BSD-style license that can be found in the LICENSE file.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>event path</title>
|
||||
<script src="packages/polymer/testing/testing.js"></script>
|
||||
<script src="packages/unittest/test_controller.js"></script>
|
||||
<!--
|
||||
Test ported from:
|
||||
https://github.com/Polymer/polymer/blob/7936ff8/test/html/event-path.html
|
||||
|
||||
This test actually doesn't test the polymer's event layer. It just ensures
|
||||
that tests are propagated in the right order when using Shadow DOM.
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<polymer-element name="x-selector">
|
||||
<template>
|
||||
<div id="selectorDiv">
|
||||
<content id="selectorContent"></content>
|
||||
</div>
|
||||
</template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
@CustomTag("x-selector")
|
||||
class XSelector extends PolymerElement {}
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
||||
<polymer-element name="x-overlay">
|
||||
<template>
|
||||
<content id="overlayContent"></content>
|
||||
</template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
@CustomTag("x-overlay")
|
||||
class XOverlay extends PolymerElement {}
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
||||
<polymer-element name="x-menu" extends="x-selector">
|
||||
<template>
|
||||
<div id="menuDiv">
|
||||
<shadow id="menuShadow"></shadow>
|
||||
</div>
|
||||
</template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
@CustomTag("x-menu")
|
||||
class XMenu extends PolymerElement {}
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
||||
<polymer-element name="x-menu-button">
|
||||
<template>
|
||||
<div>
|
||||
<x-overlay id="overlay">
|
||||
<div id="menuButtonDiv">
|
||||
<x-menu id="menu">
|
||||
<content id="menuButtonContent"></content>
|
||||
</x-menu>
|
||||
</div>
|
||||
</x-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
@CustomTag("x-menu-button")
|
||||
class XMenuButton extends PolymerElement {}
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
||||
<x-menu-button id="menuButton">
|
||||
<div id="item1"><div id="source"></div>Item1</div>
|
||||
<div id="item2">Item2</div>
|
||||
</x-menu-button>
|
||||
|
||||
|
||||
<script type="application/dart">
|
||||
import 'dart:html';
|
||||
import 'dart:async';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:unittest/html_config.dart';
|
||||
|
||||
main() {
|
||||
useHtmlConfiguration();
|
||||
test('bubbling in the right order', () {
|
||||
// TODO(sigmund): this should change once we port over the
|
||||
// 'WebComponentsReady' event.
|
||||
runAsync(expectAsync0(() {
|
||||
var item1 = query('#item1');
|
||||
var menuButton = query('#menuButton');
|
||||
// Note: polymer uses automatic node finding (menuButton.$.menu)
|
||||
// also note that their node finding code also reachs into the ids
|
||||
// from the parent shadow (menu.$.selectorContent instead of
|
||||
// menu.$.menuShadow.$.selectorContent)
|
||||
var menu = menuButton.shadowRoot.query('#menu');
|
||||
var selector = menu.shadowRoot.query("#menuShadow");
|
||||
var overlay = menuButton.shadowRoot.query('#overlay');
|
||||
var expectedPath = <Node>[
|
||||
item1,
|
||||
menuButton.shadowRoot.query('#menuButtonContent'),
|
||||
selector.olderShadowRoot.query('#selectorContent'),
|
||||
selector.olderShadowRoot.query('#selectorDiv'),
|
||||
menu.shadowRoot.query('#menuShadow').olderShadowRoot,
|
||||
menu.shadowRoot.query('#menuShadow'),
|
||||
menu.shadowRoot.query('#menuDiv'),
|
||||
menu.shadowRoot,
|
||||
menu,
|
||||
menuButton.shadowRoot.query('#menuButtonDiv'),
|
||||
// TODO(sigmund): this test is currently broken because currently
|
||||
// registerElement is sensitive to the order in which each custom
|
||||
// element is registered. When fixed, we should be able to add the
|
||||
// following three targets:
|
||||
// overlay.shadowRoot.query('#overlayContent'),
|
||||
// overlay.shadowRoot,
|
||||
// overlay,
|
||||
menuButton.shadowRoot,
|
||||
menuButton
|
||||
];
|
||||
var x = 0;
|
||||
for (int i = 0; i < expectedPath.length; i++) {
|
||||
var node = expectedPath[i];
|
||||
expect(node, isNotNull, reason: "Should not be null at $i");
|
||||
node.on['x'].listen(expectAsync1((e) {
|
||||
expect(e.currentTarget, node);
|
||||
expect(x++, i);
|
||||
}));
|
||||
}
|
||||
|
||||
item1.dispatchEvent(new Event('x', canBubble: true));
|
||||
}));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
97
pkg/polymer/test/data/unit/events_test.html
Normal file
97
pkg/polymer/test/data/unit/events_test.html
Normal file
|
@ -0,0 +1,97 @@
|
|||
<!doctype html>
|
||||
<!--
|
||||
Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
for details. All rights reserved. Use of this source code is governed by a
|
||||
BSD-style license that can be found in the LICENSE file.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>event path</title>
|
||||
<script src="packages/polymer/testing/testing.js"></script>
|
||||
<script src="packages/unittest/test_controller.js"></script>
|
||||
<!--
|
||||
Test ported from:
|
||||
https://github.com/Polymer/polymer/blob/7936ff8/test/js/events.js
|
||||
|
||||
TODO(sigmund): when we have support for mutation observers, render all of
|
||||
the test in Dart (like events.js does in JS)
|
||||
-->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<polymer-element name="test-a" on-click="clickHandler">
|
||||
<template></template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
|
||||
@CustomTag("test-a")
|
||||
class TestA extends PolymerElement {
|
||||
List clicks = [];
|
||||
void clickHandler() {
|
||||
clicks.add('host click on: $localName (id $id)');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
||||
<polymer-element name="test-b">
|
||||
<template>
|
||||
<div>
|
||||
<span id="b-1">1</span>
|
||||
<span id="b-2" on-click="clickHandler">2</span>
|
||||
</div>
|
||||
</template>
|
||||
<script type="application/dart">
|
||||
import 'package:polymer/polymer.dart';
|
||||
|
||||
@CustomTag("test-b")
|
||||
class TestB extends PolymerElement {
|
||||
List clicks = [];
|
||||
void clickHandler(event, detail, target) {
|
||||
clicks.add('local click under $localName (id $id) on ${target.id}');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</polymer-element>
|
||||
|
||||
<test-a id="a"></test-a>
|
||||
<test-b id="b"></test-b>
|
||||
|
||||
<script type="application/dart">
|
||||
import 'dart:html';
|
||||
import 'dart:async';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:unittest/html_config.dart';
|
||||
|
||||
main() {
|
||||
useHtmlConfiguration();
|
||||
|
||||
test('host event', () {
|
||||
// Note: this test is currently the only event in
|
||||
// polymer/test/js/events.js at commit #7936ff8
|
||||
Timer.run(expectAsync0(() {
|
||||
var testA = query('#a');
|
||||
expect(testA.xtag.clicks, isEmpty);
|
||||
testA.click();
|
||||
expect(testA.xtag.clicks, ['host click on: test-a (id a)']);
|
||||
}));
|
||||
});
|
||||
|
||||
test('local event', () {
|
||||
Timer.run(expectAsync0(() {
|
||||
var testB = query('#b');
|
||||
expect(testB.xtag.clicks, isEmpty);
|
||||
testB.click();
|
||||
expect(testB.xtag.clicks, []);
|
||||
var b1 = testB.shadowRoot.query('#b-1');
|
||||
b1.click();
|
||||
expect(testB.xtag.clicks, []);
|
||||
var b2 = testB.shadowRoot.query('#b-2');
|
||||
b2.click();
|
||||
expect(testB.xtag.clicks, ['local click under test-b (id b) on b-2']);
|
||||
}));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
325
pkg/polymer/test/paths_test.dart
Normal file
325
pkg/polymer/test/paths_test.dart
Normal file
|
@ -0,0 +1,325 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Tests for [PathMapper]. */
|
||||
library path_info_test;
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:unittest/compact_vm_config.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:polymer/src/info.dart';
|
||||
import 'package:polymer/src/paths.dart';
|
||||
import 'package:polymer/src/utils.dart' as utils;
|
||||
|
||||
main() {
|
||||
useCompactVMConfiguration();
|
||||
group('outdir == basedir:', () {
|
||||
group('outputPath', () {
|
||||
test('mangle automatic', () {
|
||||
var pathMapper = _newPathMapper('a', 'a', false);
|
||||
var file = _mockFile('a/b.dart', pathMapper);
|
||||
expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
|
||||
expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
|
||||
'a/_b.dart.dart');
|
||||
});
|
||||
|
||||
test('within packages/', () {
|
||||
var pathMapper = _newPathMapper('a', 'a', false);
|
||||
var file = _mockFile('a/packages/b.dart', pathMapper);
|
||||
expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
|
||||
expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
|
||||
'a/_from_packages/_b.dart.dart');
|
||||
});
|
||||
});
|
||||
|
||||
group('importUrlFor', () {
|
||||
test('simple pathMapper', () {
|
||||
var pathMapper = _newPathMapper('a', 'a', false);
|
||||
var file1 = _mockFile('a/b.dart', pathMapper);
|
||||
var file2 = _mockFile('a/c/d.dart', pathMapper);
|
||||
var file3 = _mockFile('a/e/f.dart', pathMapper);
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3), 'e/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3), '../e/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
|
||||
});
|
||||
|
||||
test('include packages/', () {
|
||||
var pathMapper = _newPathMapper('a', 'a', false);
|
||||
var file1 = _mockFile('a/b.dart', pathMapper);
|
||||
var file2 = _mockFile('a/c/d.dart', pathMapper);
|
||||
var file3 = _mockFile('a/packages/f.dart', pathMapper);
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3),
|
||||
'_from_packages/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3),
|
||||
'../_from_packages/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
|
||||
});
|
||||
|
||||
test('packages, but no rewrite', () {
|
||||
var pathMapper = _newPathMapper('a', 'a', false, rewriteUrls: false);
|
||||
var file1 = _mockFile('a/b.dart', pathMapper);
|
||||
var file2 = _mockFile('a/c/d.dart', pathMapper);
|
||||
var file3 = _mockFile('a/packages/c/f.dart', pathMapper,
|
||||
url: 'package:e/f.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3),
|
||||
'package:e/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3),
|
||||
'package:e/_f.dart.dart');
|
||||
});
|
||||
|
||||
test('windows paths', () {
|
||||
try {
|
||||
utils.path = new path.Builder(style: path.Style.windows);
|
||||
var pathMapper = _newPathMapper('a', 'a', false);
|
||||
var file1 = _mockFile('a\\b.dart', pathMapper);
|
||||
var file2 = _mockFile('a\\c\\d.dart', pathMapper);
|
||||
var file3 = _mockFile('a\\packages\\f.dart', pathMapper);
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3),
|
||||
'_from_packages/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3),
|
||||
'../_from_packages/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
|
||||
} finally {
|
||||
utils.path = new path.Builder();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('transformUrl simple paths', () {
|
||||
var pathMapper = _newPathMapper('a', 'a', false);
|
||||
var file1 = 'a/b.dart';
|
||||
var file2 = 'a/c/d.html';
|
||||
// when the output == input directory, no paths should be rewritten
|
||||
expect(pathMapper.transformUrl(file1, '/a.dart'), '/a.dart');
|
||||
expect(pathMapper.transformUrl(file1, 'c.dart'), 'c.dart');
|
||||
expect(pathMapper.transformUrl(file1, '../c/d.dart'), '../c/d.dart');
|
||||
expect(pathMapper.transformUrl(file1, 'packages/c.dart'),
|
||||
'packages/c.dart');
|
||||
expect(pathMapper.transformUrl(file2, 'e.css'), 'e.css');
|
||||
expect(pathMapper.transformUrl(file2, '../c/e.css'), 'e.css');
|
||||
expect(pathMapper.transformUrl(file2, '../q/e.css'), '../q/e.css');
|
||||
expect(pathMapper.transformUrl(file2, 'packages/c.css'),
|
||||
'packages/c.css');
|
||||
expect(pathMapper.transformUrl(file2, '../packages/c.css'),
|
||||
'../packages/c.css');
|
||||
});
|
||||
|
||||
test('transformUrl with source in packages/', () {
|
||||
var pathMapper = _newPathMapper('a', 'a', false);
|
||||
var file = 'a/packages/e.html';
|
||||
// Even when output == base, files under packages/ are moved to
|
||||
// _from_packages, so all imports are affected:
|
||||
expect(pathMapper.transformUrl(file, 'e.css'), '../packages/e.css');
|
||||
expect(pathMapper.transformUrl(file, '../packages/e.css'),
|
||||
'../packages/e.css');
|
||||
expect(pathMapper.transformUrl(file, '../q/e.css'), '../q/e.css');
|
||||
expect(pathMapper.transformUrl(file, 'packages/c.css'),
|
||||
'../packages/packages/c.css');
|
||||
});
|
||||
});
|
||||
|
||||
group('outdir != basedir:', () {
|
||||
group('outputPath', (){
|
||||
test('no force mangle', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', false);
|
||||
var file = _mockFile('a/b.dart', pathMapper);
|
||||
expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
|
||||
expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
|
||||
'out/b.dart');
|
||||
});
|
||||
|
||||
test('force mangling', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', true);
|
||||
var file = _mockFile('a/b.dart', pathMapper);
|
||||
expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
|
||||
expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
|
||||
'out/_b.dart.dart');
|
||||
});
|
||||
|
||||
test('within packages/, no mangle', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', false);
|
||||
var file = _mockFile('a/packages/b.dart', pathMapper);
|
||||
expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
|
||||
expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
|
||||
'out/_from_packages/b.dart');
|
||||
});
|
||||
|
||||
test('within packages/, mangle', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', true);
|
||||
var file = _mockFile('a/packages/b.dart', pathMapper);
|
||||
expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
|
||||
expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
|
||||
'out/_from_packages/_b.dart.dart');
|
||||
});
|
||||
});
|
||||
|
||||
group('importUrlFor', (){
|
||||
test('simple paths, no mangle', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', false);
|
||||
var file1 = _mockFile('a/b.dart', pathMapper);
|
||||
var file2 = _mockFile('a/c/d.dart', pathMapper);
|
||||
var file3 = _mockFile('a/e/f.dart', pathMapper);
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/d.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3), 'e/f.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../b.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3), '../e/f.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../c/d.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../b.dart');
|
||||
});
|
||||
|
||||
test('simple paths, mangle', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', true);
|
||||
var file1 = _mockFile('a/b.dart', pathMapper);
|
||||
var file2 = _mockFile('a/c/d.dart', pathMapper);
|
||||
var file3 = _mockFile('a/e/f.dart', pathMapper);
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3), 'e/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3), '../e/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
|
||||
});
|
||||
|
||||
test('include packages/, no mangle', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', false);
|
||||
var file1 = _mockFile('a/b.dart', pathMapper);
|
||||
var file2 = _mockFile('a/c/d.dart', pathMapper);
|
||||
var file3 = _mockFile('a/packages/e/f.dart', pathMapper,
|
||||
url: 'package:e/f.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/d.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3),
|
||||
'_from_packages/e/f.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../b.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3),
|
||||
'../_from_packages/e/f.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../../c/d.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../../b.dart');
|
||||
});
|
||||
|
||||
test('include packages/, mangle', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', true);
|
||||
var file1 = _mockFile('a/b.dart', pathMapper);
|
||||
var file2 = _mockFile('a/c/d.dart', pathMapper);
|
||||
var file3 = _mockFile('a/packages/e/f.dart', pathMapper,
|
||||
url: 'package:e/f.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3),
|
||||
'_from_packages/e/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3),
|
||||
'../_from_packages/e/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../../c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../../_b.dart.dart');
|
||||
});
|
||||
|
||||
test('windows paths', () {
|
||||
try {
|
||||
utils.path = new path.Builder(style: path.Style.windows);
|
||||
var pathMapper = _newPathMapper('a', 'out', true);
|
||||
var file1 = _mockFile('a\\b.dart', pathMapper);
|
||||
var file2 = _mockFile('a\\c\\d.dart', pathMapper);
|
||||
var file3 = _mockFile('a\\packages\\f.dart', pathMapper);
|
||||
expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file1, file3),
|
||||
'_from_packages/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file2, file3),
|
||||
'../_from_packages/_f.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
|
||||
expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
|
||||
} finally {
|
||||
utils.path = new path.Builder();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('transformUrl', () {
|
||||
test('simple source, not in packages/', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', false);
|
||||
var file1 = 'a/b.dart';
|
||||
var file2 = 'a/c/d.html';
|
||||
// when the output == input directory, no paths should be rewritten
|
||||
expect(pathMapper.transformUrl(file1, '/a.dart'), '/a.dart');
|
||||
expect(pathMapper.transformUrl(file1, 'c.dart'), '../a/c.dart');
|
||||
|
||||
// reach out from basedir:
|
||||
expect(pathMapper.transformUrl(file1, '../c/d.dart'), '../c/d.dart');
|
||||
|
||||
// reach into packages dir:
|
||||
expect(pathMapper.transformUrl(file1, 'packages/c.dart'),
|
||||
'../a/packages/c.dart');
|
||||
|
||||
expect(pathMapper.transformUrl(file2, 'e.css'), '../../a/c/e.css');
|
||||
|
||||
_checkPath('../../a/c/../c/e.css', '../../a/c/e.css');
|
||||
expect(pathMapper.transformUrl(file2, '../c/e.css'), '../../a/c/e.css');
|
||||
|
||||
_checkPath('../../a/c/../q/e.css', '../../a/q/e.css');
|
||||
expect(pathMapper.transformUrl(file2, '../q/e.css'), '../../a/q/e.css');
|
||||
|
||||
expect(pathMapper.transformUrl(file2, 'packages/c.css'),
|
||||
'../../a/c/packages/c.css');
|
||||
_checkPath('../../a/c/../packages/c.css', '../../a/packages/c.css');
|
||||
expect(pathMapper.transformUrl(file2, '../packages/c.css'),
|
||||
'../../a/packages/c.css');
|
||||
});
|
||||
|
||||
test('input in packages/', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', true);
|
||||
var file = 'a/packages/e.html';
|
||||
expect(pathMapper.transformUrl(file, 'e.css'),
|
||||
'../../a/packages/e.css');
|
||||
expect(pathMapper.transformUrl(file, '../packages/e.css'),
|
||||
'../../a/packages/e.css');
|
||||
expect(pathMapper.transformUrl(file, '../q/e.css'), '../../a/q/e.css');
|
||||
expect(pathMapper.transformUrl(file, 'packages/c.css'),
|
||||
'../../a/packages/packages/c.css');
|
||||
});
|
||||
|
||||
test('src fragments', () {
|
||||
var pathMapper = _newPathMapper('a', 'out', false);
|
||||
var file1 = 'a/b.dart';
|
||||
var file2 = 'a/c/html.html';
|
||||
// when the output == input directory, no paths should be rewritten
|
||||
expect(pathMapper.transformUrl(file1, '#tips'), '#tips');
|
||||
expect(pathMapper.transformUrl(file1,
|
||||
'http://www.w3schools.com/html_links.htm#tips'),
|
||||
'http://www.w3schools.com/html_links.htm#tips');
|
||||
expect(pathMapper.transformUrl(file2,
|
||||
'html_links.html'),
|
||||
'../../a/c/html_links.html');
|
||||
expect(pathMapper.transformUrl(file2,
|
||||
'html_links.html#tips'),
|
||||
'../../a/c/html_links.html#tips');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_newPathMapper(String baseDir, String outDir, bool forceMangle,
|
||||
{bool rewriteUrls: true}) =>
|
||||
new PathMapper(baseDir, outDir, 'packages', forceMangle, rewriteUrls);
|
||||
|
||||
_mockFile(String filePath, PathMapper pathMapper, {String url}) {
|
||||
var file = new FileInfo(new UrlInfo(
|
||||
url == null ? filePath : url, filePath, null));
|
||||
file.outputFilename = pathMapper.mangle(
|
||||
utils.path.basename(filePath), '.dart', false);
|
||||
return file;
|
||||
}
|
||||
|
||||
_checkPath(String filePath, String expected) {
|
||||
expect(utils.path.normalize(filePath), expected);
|
||||
}
|
134
pkg/polymer/test/run.sh
Executable file
134
pkg/polymer/test/run.sh
Executable file
|
@ -0,0 +1,134 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
# for details. All rights reserved. Use of this source code is governed by a
|
||||
# BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
# Usage: call directly in the commandline as test/run.sh ensuring that you have
|
||||
# both 'dart' and 'content_shell' in your path. Filter tests by passing a
|
||||
# pattern as an argument to this script.
|
||||
|
||||
# TODO(sigmund): replace with a real test runner
|
||||
|
||||
# bail on error
|
||||
set -e
|
||||
|
||||
# print commands executed by this script
|
||||
# set -x
|
||||
|
||||
DIR=$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd )
|
||||
export DART_FLAGS="--checked"
|
||||
TEST_PATTERN=$1
|
||||
|
||||
function fail {
|
||||
return 1
|
||||
}
|
||||
|
||||
function show_diff {
|
||||
diff -u -N $1 $2 | \
|
||||
sed -e "s/^\(+.*\)/[32m\1[0m/" |\
|
||||
sed -e "s/^\(-.*\)/[31m\1[0m/"
|
||||
return 1
|
||||
}
|
||||
|
||||
function update {
|
||||
read -p "Would you like to update the expectations? [y/N]: " answer
|
||||
if [[ $answer == 'y' || $answer == 'Y' ]]; then
|
||||
cp $2 $1
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
function pass {
|
||||
echo -e "[32mOK[0m"
|
||||
}
|
||||
|
||||
function compare {
|
||||
# use a standard diff, if they are not identical, format the diff nicely to
|
||||
# see what's the error and prompt to see if they wish to update it. If they
|
||||
# do, continue running more tests.
|
||||
diff -q $1 $2 && pass || show_diff $1 $2 || update $1 $2
|
||||
}
|
||||
|
||||
if [[ ($TEST_PATTERN == "") ]]; then
|
||||
# Note: dartanalyzer needs to be run from the root directory for proper path
|
||||
# canonicalization.
|
||||
pushd $DIR/.. > /dev/null
|
||||
|
||||
echo Analyzing compiler for warnings or type errors
|
||||
dartanalyzer --hints --fatal-warnings --fatal-type-errors bin/dwc.dart
|
||||
|
||||
echo -e "\nAnalyzing runtime for warnings or type errors"
|
||||
dartanalyzer --hints --fatal-warnings --fatal-type-errors lib/polymer.dart
|
||||
|
||||
popd > /dev/null
|
||||
fi
|
||||
|
||||
function compare_all {
|
||||
# TODO(jmesserly): bash and dart regexp might not be 100% the same. Ideally we
|
||||
# could do all the heavy lifting in Dart code, and keep this script as a thin
|
||||
# wrapper that sets `--enable-type-checks --enable-asserts`
|
||||
for input in $DIR/../example/component/news/test/*_test.html \
|
||||
$DIR/../../../samples/third_party/todomvc/test/*_test.html; do
|
||||
if [[ ($TEST_PATTERN == "") || ($input =~ $TEST_PATTERN) ]]; then
|
||||
FILENAME=`basename $input`
|
||||
DIRNAME=`dirname $input`
|
||||
if [[ `basename $DIRNAME` == 'input' ]]; then
|
||||
DIRNAME=`dirname $DIRNAME`
|
||||
fi
|
||||
echo -e -n "Checking diff for $FILENAME "
|
||||
DUMP="$DIRNAME/out/$FILENAME.txt"
|
||||
EXPECTATION="$DIRNAME/expected/$FILENAME.txt"
|
||||
|
||||
compare $EXPECTATION $DUMP
|
||||
fi
|
||||
done
|
||||
echo -e "[31mSome tests failed[0m"
|
||||
fail
|
||||
}
|
||||
|
||||
if [[ -e $DIR/data/input/example ]]; then
|
||||
echo "WARNING: detected old data/input/example symlink."
|
||||
echo "Removing it and rerunning pub install to fix broken example symlinks."
|
||||
echo "See http://dartbug.com/9418 for more information."
|
||||
echo "You should only see this message once."
|
||||
if [[ -e $DIR/packages ]]; then
|
||||
find . -name packages -type l | xargs rm
|
||||
fi
|
||||
rm $DIR/data/input/example
|
||||
pushd $DIR/..
|
||||
pub install
|
||||
popd
|
||||
fi
|
||||
|
||||
# TODO(jmesserly): dart:io fails if we run the Dart scripts with an absolute
|
||||
# path. So use pushd/popd to change the working directory.
|
||||
if [[ ($TEST_PATTERN == "") ]]; then
|
||||
pushd $DIR/.. > /dev/null
|
||||
echo -e "\nTesting build.dart... "
|
||||
dart $DART_FLAGS build.dart
|
||||
# Run it the way the editor does. Hide stdout because it is in noisy machine
|
||||
# format. Show stderr in case something breaks.
|
||||
# NOTE: not using --checked because the editor doesn't use it, and to workaround
|
||||
# http://dartbug.com/9637
|
||||
dart build.dart --machine --clean > /dev/null
|
||||
dart build.dart --machine --full > /dev/null
|
||||
dart build.dart --machine --changed ../../samples/third_party/todomvc/web/index.html > /dev/null
|
||||
popd > /dev/null
|
||||
fi
|
||||
|
||||
pushd $DIR > /dev/null
|
||||
echo -e "\nRunning unit tests... "
|
||||
dart $DART_FLAGS run_all.dart $@ || compare_all
|
||||
popd > /dev/null
|
||||
|
||||
# Run Dart analyzer to check that we're generating warning clean code.
|
||||
# It's a bit slow, so only do this for TodoMVC and html5_utils tests.
|
||||
OUT_PATTERN="$DIR/../../../third_party/samples/todomvc/test/out/test/*$TEST_PATTERN*_bootstrap.dart"
|
||||
if [[ `ls $OUT_PATTERN 2>/dev/null` != "" ]]; then
|
||||
echo -e "\nAnalyzing generated code for warnings or type errors."
|
||||
ls $OUT_PATTERN 2>/dev/null | dartanalyzer --package-root=packages \
|
||||
--fatal-warnings --fatal-type-errors -batch
|
||||
fi
|
||||
|
||||
echo -e "[32mAll tests pass[0m"
|
78
pkg/polymer/test/run_all.dart
Normal file
78
pkg/polymer/test/run_all.dart
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* This is a helper for run.sh. We try to run all of the Dart code in one
|
||||
* instance of the Dart VM to reduce warm-up time.
|
||||
*/
|
||||
library run_impl;
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:unittest/compact_vm_config.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:polymer/testing/content_shell_test.dart';
|
||||
|
||||
import 'css_test.dart' as css_test;
|
||||
import 'compiler_test.dart' as compiler_test;
|
||||
import 'paths_test.dart' as paths_test;
|
||||
import 'utils_test.dart' as utils_test;
|
||||
|
||||
main() {
|
||||
var args = new Options().arguments;
|
||||
var pattern = new RegExp(args.length > 0 ? args[0] : '.');
|
||||
|
||||
useCompactVMConfiguration();
|
||||
|
||||
void addGroup(testFile, testMain) {
|
||||
if (pattern.hasMatch(testFile)) {
|
||||
group(testFile.replaceAll('_test.dart', ':'), testMain);
|
||||
}
|
||||
}
|
||||
|
||||
addGroup('compiler_test.dart', compiler_test.main);
|
||||
addGroup('css_test.dart', css_test.main);
|
||||
addGroup('paths_test.dart', paths_test.main);
|
||||
addGroup('utils_test.dart', utils_test.main);
|
||||
|
||||
endToEndTests('data/unit/', 'data/out');
|
||||
|
||||
// Note: if you're adding more render test suites, make sure to update run.sh
|
||||
// as well for convenient baseline diff/updating.
|
||||
|
||||
// TODO(jmesserly): figure out why this fails in content_shell but works in
|
||||
// Dartium and Firefox when using the ShadowDOM polyfill.
|
||||
exampleTest('../example/component/news', ['--no-shadowdom']..addAll(args));
|
||||
|
||||
exampleTest('../../../samples/third_party/todomvc');
|
||||
}
|
||||
|
||||
void exampleTest(String path, [List<String> args]) {
|
||||
renderTests(path, '$path/test', '$path/test/expected', '$path/test/out',
|
||||
arguments: args);
|
||||
}
|
||||
|
||||
void cssCompileMangleTest(String path, String pattern,
|
||||
[bool deleteDirectory = true]) {
|
||||
renderTests(path, path, '$path/expected', '$path/out',
|
||||
arguments: ['--css-mangle'], pattern: pattern,
|
||||
deleteDir: deleteDirectory);
|
||||
}
|
||||
|
||||
void cssCompilePolyFillTest(String path, String pattern, String cssReset,
|
||||
[bool deleteDirectory = true]) {
|
||||
var args = ['--no-css-mangle'];
|
||||
if (cssReset != null) {
|
||||
args.addAll(['--css-reset', '${path}/${cssReset}']);
|
||||
}
|
||||
renderTests(path, path, '$path/expected', '$path/out',
|
||||
arguments: args, pattern: pattern, deleteDir: deleteDirectory);
|
||||
}
|
||||
|
||||
void cssCompileShadowDOMTest(String path, String pattern,
|
||||
[bool deleteDirectory = true]) {
|
||||
var args = ['--no-css'];
|
||||
renderTests(path, path, '$path/expected', '$path/out',
|
||||
arguments: args, pattern: pattern,
|
||||
deleteDir: deleteDirectory);
|
||||
}
|
111
pkg/polymer/test/testing.dart
Normal file
111
pkg/polymer/test/testing.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Common definitions used for setting up the test environment. */
|
||||
library testing;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:csslib/visitor.dart';
|
||||
import 'package:html5lib/dom.dart';
|
||||
import 'package:html5lib/parser.dart';
|
||||
import 'package:polymer/src/analyzer.dart';
|
||||
import 'package:polymer/src/compiler.dart';
|
||||
import 'package:polymer/src/file_system.dart';
|
||||
import 'package:polymer/src/info.dart';
|
||||
import 'package:polymer/src/messages.dart';
|
||||
import 'package:polymer/src/compiler_options.dart';
|
||||
import 'package:polymer/src/files.dart';
|
||||
import 'package:polymer/src/utils.dart';
|
||||
|
||||
|
||||
Document parseDocument(String html) => parse(html);
|
||||
|
||||
Element parseSubtree(String html) => parseFragment(html).nodes[0];
|
||||
|
||||
FileInfo analyzeDefinitionsInTree(Document doc, Messages messages,
|
||||
{String packageRoot: 'packages'}) {
|
||||
|
||||
return analyzeDefinitions(new GlobalInfo(), new UrlInfo('', '', null),
|
||||
doc, packageRoot, messages);
|
||||
}
|
||||
|
||||
/** Parses files in [fileContents], with [mainHtmlFile] being the main file. */
|
||||
List<SourceFile> parseFiles(Map<String, String> fileContents,
|
||||
[String mainHtmlFile = 'index.html']) {
|
||||
|
||||
var result = <SourceFile>[];
|
||||
fileContents.forEach((filename, contents) {
|
||||
var src = new SourceFile(filename);
|
||||
src.document = parse(contents);
|
||||
result.add(src);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Analyze all files. */
|
||||
Map<String, FileInfo> analyzeFiles(List<SourceFile> files,
|
||||
{Messages messages, String packageRoot: 'packages'}) {
|
||||
messages = messages == null ? new Messages.silent() : messages;
|
||||
var result = new Map<String, FileInfo>();
|
||||
|
||||
// analyze definitions
|
||||
var global = new GlobalInfo();
|
||||
for (var file in files) {
|
||||
var path = file.path;
|
||||
result[path] = analyzeDefinitions(global, new UrlInfo(path, path, null),
|
||||
file.document, packageRoot, messages);
|
||||
}
|
||||
|
||||
// analyze file contents
|
||||
var uniqueIds = new IntIterator();
|
||||
var pseudoElements = new Map();
|
||||
for (var file in files) {
|
||||
analyzeFile(file, result, uniqueIds, pseudoElements, messages, true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Compiler createCompiler(Map files, Messages messages, {bool errors: false,
|
||||
bool scopedCss: false}) {
|
||||
List baseOptions = ['--no-colors', '-o', 'out', '--deploy', 'index.html'];
|
||||
if (errors) baseOptions.insert(0, '--warnings_as_errors');
|
||||
if (scopedCss) baseOptions.insert(0, '--scoped-css');
|
||||
var options = CompilerOptions.parse(baseOptions);
|
||||
var fs = new MockFileSystem(files);
|
||||
return new Compiler(fs, options, messages);
|
||||
}
|
||||
|
||||
String prettyPrintCss(StyleSheet styleSheet) =>
|
||||
((new CssPrinter())..visitTree(styleSheet)).toString();
|
||||
|
||||
/**
|
||||
* Abstraction around file system access to work in a variety of different
|
||||
* environments.
|
||||
*/
|
||||
class MockFileSystem extends FileSystem {
|
||||
final Map _files;
|
||||
final Map readCount = {};
|
||||
|
||||
MockFileSystem(this._files);
|
||||
|
||||
Future readTextOrBytes(String filename) => readText(filename);
|
||||
|
||||
Future<String> readText(String path) {
|
||||
readCount[path] = readCount.putIfAbsent(path, () => 0) + 1;
|
||||
var file = _files[path];
|
||||
if (file != null) {
|
||||
return new Future.value(file);
|
||||
} else {
|
||||
return new Future.error(
|
||||
new FileException('MockFileSystem: $path not found'));
|
||||
}
|
||||
}
|
||||
|
||||
// Compiler doesn't call these
|
||||
void writeString(String outfile, String text) {}
|
||||
Future flush() {}
|
||||
}
|
86
pkg/polymer/test/utils_test.dart
Normal file
86
pkg/polymer/test/utils_test.dart
Normal file
|
@ -0,0 +1,86 @@
|
|||
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/** Tests for some of the utility helper functions used by the compiler. */
|
||||
library utils_test;
|
||||
|
||||
import 'package:unittest/compact_vm_config.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:polymer/src/utils.dart';
|
||||
|
||||
main() {
|
||||
useCompactVMConfiguration();
|
||||
|
||||
for (bool startUppercase in [false, true]) {
|
||||
Matcher caseEquals(String str) {
|
||||
if (startUppercase) str = str[0].toUpperCase() + str.substring(1);
|
||||
return equals(str);
|
||||
}
|
||||
|
||||
camelCase(str) => toCamelCase(str, startUppercase: startUppercase);
|
||||
|
||||
group('toCamelCase startUppercase=$startUppercase', () {
|
||||
test('empty', () {
|
||||
expect(camelCase(''), equals(''));
|
||||
});
|
||||
|
||||
test('single token', () {
|
||||
expect(camelCase('a'), caseEquals('a'));
|
||||
expect(camelCase('ab'), caseEquals('ab'));
|
||||
expect(camelCase('Ab'), caseEquals('Ab'));
|
||||
expect(camelCase('AB'), caseEquals('AB'));
|
||||
expect(camelCase('long_word'), caseEquals('long_word'));
|
||||
});
|
||||
|
||||
test('dashes in the middle', () {
|
||||
expect(camelCase('a-b'), caseEquals('aB'));
|
||||
expect(camelCase('a-B'), caseEquals('aB'));
|
||||
expect(camelCase('A-b'), caseEquals('AB'));
|
||||
expect(camelCase('long-word'), caseEquals('longWord'));
|
||||
});
|
||||
|
||||
test('leading/trailing dashes', () {
|
||||
expect(camelCase('-hi'), caseEquals('Hi'));
|
||||
expect(camelCase('hi-'), caseEquals('hi'));
|
||||
expect(camelCase('hi-friend-'), caseEquals('hiFriend'));
|
||||
});
|
||||
|
||||
test('consecutive dashes', () {
|
||||
expect(camelCase('--hi-friend'), caseEquals('HiFriend'));
|
||||
expect(camelCase('hi--friend'), caseEquals('hiFriend'));
|
||||
expect(camelCase('hi-friend--'), caseEquals('hiFriend'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group('toHyphenedName', () {
|
||||
test('empty', () {
|
||||
expect(toHyphenedName(''), '');
|
||||
});
|
||||
|
||||
test('all lower case', () {
|
||||
expect(toHyphenedName('a'), 'a');
|
||||
expect(toHyphenedName('a-b'), 'a-b');
|
||||
expect(toHyphenedName('aBc'), 'a-bc');
|
||||
expect(toHyphenedName('abC'), 'ab-c');
|
||||
expect(toHyphenedName('abc-d'), 'abc-d');
|
||||
expect(toHyphenedName('long_word'), 'long_word');
|
||||
});
|
||||
|
||||
test('capitalized letters in the middle/end', () {
|
||||
expect(toHyphenedName('aB'), 'a-b');
|
||||
expect(toHyphenedName('longWord'), 'long-word');
|
||||
});
|
||||
|
||||
test('leading capital letters', () {
|
||||
expect(toHyphenedName('Hi'), 'hi');
|
||||
expect(toHyphenedName('Hi-'), 'hi-');
|
||||
expect(toHyphenedName('HiFriend'), 'hi-friend');
|
||||
});
|
||||
|
||||
test('consecutive capital letters', () {
|
||||
expect(toHyphenedName('aBC'), 'a-b-c');
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue