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:
jmesserly@google.com 2013-08-20 22:48:50 +00:00
parent b56d5196b8
commit 068dec6712
49 changed files with 7549 additions and 0 deletions

View file

@ -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
View 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
View 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
View 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']);
}

View file

@ -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>

View 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>

View 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');
}

View 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>

View 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>

View 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>

View 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
View 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';
}
});
})();

View 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
View 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;
});
}

View 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);
}
}
}

View 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);
});
}

View 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();
}

View 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,
};

View 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;
}

View 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);
}
}

View 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);
}
}

View 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());
}
}

View 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);
}
}

View 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');
}
}

View 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);
}

View 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);
}

View 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'));
}

View 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);
}

View 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);
}
}

View 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>";
}

View 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
];

View 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>";
}

View 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);
}
}

View 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]);

View 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;
}

View 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;
}
}

View 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();
}

View 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();
}
}

View 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
View 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

View 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, []);
}));
});
});
}

View 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);
}

View 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>

View 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>

View 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
View 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/^\(+.*\)/\1/" |\
sed -e "s/^\(-.*\)/\1/"
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 "OK"
}
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 "Some tests failed"
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 "All tests pass"

View 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);
}

View 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() {}
}

View 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');
});
});
}