blob: 9405f43d4c3d56f9e8a574deba665ca0158bdb7a [file] [log] [blame]
// 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 pub.dart2js_transformer;
import 'dart:async';
import 'dart:io';
import 'package:analyzer_experimental/analyzer.dart';
import 'package:barback/barback.dart';
import 'package:path/path.dart' as path;
import '../../../../compiler/compiler.dart' as compiler;
import '../../../../compiler/implementation/dart2js.dart'
show AbortLeg;
import '../../../../compiler/implementation/source_file.dart';
import '../barback.dart';
import '../dart.dart' as dart;
import '../io.dart';
import '../package.dart';
import '../package_graph.dart';
/// A [Transformer] that uses dart2js's library API to transform Dart
/// entrypoints in "web" to JavaScript.
class Dart2JSTransformer extends Transformer {
final PackageGraph _graph;
Dart2JSTransformer(this._graph);
/// Only ".dart" files within "web/" are processed.
Future<bool> isPrimary(Asset asset) {
return new Future.value(
asset.id.extension == ".dart" &&
asset.id.path.startsWith("web/"));
}
Future apply(Transform transform) {
var stopwatch = new Stopwatch();
stopwatch.start();
return transform.primaryInput.readAsString().then((code) {
try {
if (!dart.isEntrypoint(parseCompilationUnit(code))) return;
} on AnalyzerErrorGroup catch (e) {
transform.logger.error(e.message);
return;
}
var provider = new _BarbackInputProvider(_graph, transform);
// Create a "path" to the entrypoint script. The entrypoint may not
// actually be on disk, but this gives dart2js a root to resolve
// relative paths against.
var id = transform.primaryInput.id;
var entrypoint = path.join(_graph.packages[id.package].dir, id.path);
var packageRoot = path.join(_graph.entrypoint.root.dir, "packages");
// TODO(rnystrom): Should have more sophisticated error-handling here.
// Need to report compile errors to the user in an easily visible way.
// Need to make sure paths in errors are mapped to the original source
// path so they can understand them.
return dart.compile(entrypoint,
packageRoot: packageRoot,
inputProvider: provider.readStringFromUri,
diagnosticHandler: provider.handleDiagnostic).then((js) {
var id = transform.primaryInput.id.changeExtension(".dart.js");
transform.addOutput(new Asset.fromString(id, js));
stopwatch.stop();
transform.logger.info("Generated $id (${js.length} characters) in "
"${stopwatch.elapsed}");
});
});
}
}
/// Defines methods implementig [CompilerInputProvider] and [DiagnosticHandler]
/// for dart2js to use to load files from Barback and report errors.
///
/// Note that most of the implementation of diagnostic handling here was
/// copied from [FormattingDiagnosticHandler] in dart2js. The primary
/// difference is that it uses barback's logging code and, more importantly, it
/// handles missing source files more gracefully.
class _BarbackInputProvider {
final PackageGraph _graph;
final Transform _transform;
/// The map of previously loaded files.
///
/// Used to show where an error occurred in a source file.
final _sourceFiles = new Map<String, SourceFile>();
// TODO(rnystrom): Make these configurable.
/// Whether or not warnings should be logged.
var _showWarnings = true;
/// Whether or not hints should be logged.
var _showHints = true;
/// Whether or not verbose info messages should be logged.
var _verbose = false;
/// Whether an exception should be thrown on an error to stop compilation.
var _throwOnError = false;
/// This gets set after a fatal error is reported to quash any subsequent
/// errors.
var _isAborting = false;
compiler.Diagnostic _lastKind = null;
static final int _FATAL =
compiler.Diagnostic.CRASH.ordinal |
compiler.Diagnostic.ERROR.ordinal;
static final int _INFO =
compiler.Diagnostic.INFO.ordinal |
compiler.Diagnostic.VERBOSE_INFO.ordinal;
_BarbackInputProvider(this._graph, this._transform);
/// A [CompilerInputProvider] for dart2js.
Future<String> readStringFromUri(Uri resourceUri) {
// We only expect to get absolute "file:" URLs from dart2js.
assert(resourceUri.isAbsolute);
assert(resourceUri.scheme == "file");
var sourcePath = path.fromUri(resourceUri);
return _readResource(resourceUri).then((source) {
_sourceFiles[resourceUri.toString()] =
new SourceFile(path.relative(sourcePath), source);
return source;
});
}
/// A [DiagnosticHandler] for dart2js, loosely based on
/// [FormattingDiagnosticHandler].
void handleDiagnostic(Uri uri, int begin, int end,
String message, compiler.Diagnostic kind) {
// TODO(ahe): Remove this when source map is handled differently.
if (kind.name == "source map") return;
if (_isAborting) return;
_isAborting = (kind == compiler.Diagnostic.CRASH);
var isInfo = (kind.ordinal & _INFO) != 0;
if (isInfo && uri == null && kind != compiler.Diagnostic.INFO) {
if (!_verbose && kind == compiler.Diagnostic.VERBOSE_INFO) return;
_transform.logger.info(message);
return;
}
// [_lastKind] records the previous non-INFO kind we saw.
// This is used to suppress info about a warning when warnings are
// suppressed, and similar for hints.
if (kind != compiler.Diagnostic.INFO) _lastKind = kind;
var logFn;
if (kind == compiler.Diagnostic.ERROR) {
logFn = _transform.logger.error;
} else if (kind == compiler.Diagnostic.WARNING) {
if (!_showWarnings) return;
logFn = _transform.logger.warning;
} else if (kind == compiler.Diagnostic.HINT) {
if (!_showHints) return;
logFn = _transform.logger.warning;
} else if (kind == compiler.Diagnostic.CRASH) {
logFn = _transform.logger.error;
} else if (kind == compiler.Diagnostic.INFO) {
if (_lastKind == compiler.Diagnostic.WARNING && !_showWarnings) return;
if (_lastKind == compiler.Diagnostic.HINT && !_showHints) return;
logFn = _transform.logger.info;
} else {
throw new Exception('Unknown kind: $kind (${kind.ordinal})');
}
var fatal = (kind.ordinal & _FATAL) != 0;
if (uri == null) {
assert(fatal);
logFn(message);
} else {
SourceFile file = _sourceFiles[uri.toString()];
if (file == null) {
// We got a message before loading the file, so just report the message
// itself.
logFn('$uri: $message');
} else {
logFn(file.getLocationMessage(message, begin, end, true, (i) => i));
}
}
if (fatal && _throwOnError) {
_isAborting = true;
throw new AbortLeg(message);
}
}
Future<String> _readResource(Uri url) {
// See if the path is within a package. If so, use Barback so we can use
// generated Dart assets.
var id = _sourceUrlToId(url);
if (id != null) return _transform.readInputAsString(id);
// If we get here, the path doesn't appear to be in a package, so we'll
// skip Barback and just hit the file system. This will occur at the very
// least for dart2js's implementations of the core libraries.
var sourcePath = path.fromUri(url);
return new File(sourcePath).readAsString();
}
AssetId _sourceUrlToId(Uri url) {
// See if it's a special path with "packages" or "assets" in it.
var id = specialUrlToId(url);
if (id != null) return id;
// See if it's a path within the root package.
var rootDir = _graph.entrypoint.root.dir;
var sourcePath = path.fromUri(url);
if (isBeneath(sourcePath, rootDir)) {
var relative = path.relative(sourcePath, from: rootDir);
return new AssetId(_graph.entrypoint.root.name, relative);
}
return null;
}
}