blob: b47fc647a272a54a9b51db50b0584ce9376c7b11 [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 source_file_provider;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math' as math;
import '../compiler.dart' as api show Diagnostic, DiagnosticHandler;
import 'dart2js.dart' show AbortLeg;
import 'colors.dart' as colors;
import 'io/source_file.dart';
import 'filenames.dart';
import 'util/uri_extras.dart';
import 'dart:typed_data';
List<int> readAll(String filename) {
var file = (new File(filename)).openSync();
var length = file.lengthSync();
// +1 to have a 0 terminated list, see [Scanner].
var buffer = new Uint8List(length + 1);
file.readIntoSync(buffer, 0, length);
file.closeSync();
return buffer;
}
abstract class SourceFileProvider {
bool isWindows = (Platform.operatingSystem == 'windows');
Uri cwd = currentDirectory;
Map<Uri, SourceFile> sourceFiles = <Uri, SourceFile>{};
int dartCharactersRead = 0;
Future<String> readStringFromUri(Uri resourceUri) {
return readUtf8BytesFromUri(resourceUri).then(UTF8.decode);
}
Future<List<int>> readUtf8BytesFromUri(Uri resourceUri) {
if (resourceUri.scheme == 'file') {
return _readFromFile(resourceUri);
} else if (resourceUri.scheme == 'http' || resourceUri.scheme == 'https') {
return _readFromHttp(resourceUri);
} else {
throw new ArgumentError("Unknown scheme in uri '$resourceUri'");
}
}
Future<List<int>> _readFromFile(Uri resourceUri) {
assert(resourceUri.scheme == 'file');
List<int> source;
try {
source = readAll(resourceUri.toFilePath());
} on FileSystemException catch (ex) {
OSError ose = ex.osError;
String detail = (ose != null && ose.message != null)
? ' (${ose.message})'
: '';
return new Future.error(
"Error reading '${relativize(cwd, resourceUri, isWindows)}'"
"$detail");
}
dartCharactersRead += source.length;
sourceFiles[resourceUri] =
new CachingUtf8BytesSourceFile(
resourceUri, relativizeUri(resourceUri), source);
return new Future.value(source);
}
Future<List<int>> _readFromHttp(Uri resourceUri) {
assert(resourceUri.scheme == 'http');
HttpClient client = new HttpClient();
return client.getUrl(resourceUri)
.then((HttpClientRequest request) => request.close())
.then((HttpClientResponse response) {
if (response.statusCode != HttpStatus.OK) {
String msg = 'Failure getting $resourceUri: '
'${response.statusCode} ${response.reasonPhrase}';
throw msg;
}
return response.toList();
})
.then((List<List<int>> splitContent) {
int totalLength = splitContent.fold(0, (int old, List list) {
return old + list.length;
});
Uint8List result = new Uint8List(totalLength);
int offset = 0;
for (List<int> contentPart in splitContent) {
result.setRange(
offset, offset + contentPart.length, contentPart);
offset += contentPart.length;
}
dartCharactersRead += totalLength;
sourceFiles[resourceUri] =
new CachingUtf8BytesSourceFile(
resourceUri, resourceUri.toString(), result);
return result;
});
}
Future/*<List<int> | String>*/ call(Uri resourceUri);
relativizeUri(Uri uri) => relativize(cwd, uri, isWindows);
}
class CompilerSourceFileProvider extends SourceFileProvider {
Future<List<int>> call(Uri resourceUri) => readUtf8BytesFromUri(resourceUri);
}
class FormattingDiagnosticHandler {
final SourceFileProvider provider;
bool showWarnings = true;
bool showHints = true;
bool verbose = false;
bool isAborting = false;
bool enableColors = false;
bool throwOnError = false;
int throwOnErrorCount = 0;
api.Diagnostic lastKind = null;
int fatalCount = 0;
final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal;
final int INFO =
api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal;
FormattingDiagnosticHandler([SourceFileProvider provider])
: this.provider =
(provider == null) ? new CompilerSourceFileProvider() : provider;
void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) {
if (!verbose && kind == api.Diagnostic.VERBOSE_INFO) return;
if (enableColors) {
print('${colors.green("Info:")} $message');
} else {
print('Info: $message');
}
}
/// Adds [kind] specific prefix to [message].
String prefixMessage(String message, api.Diagnostic kind) {
switch (kind) {
case api.Diagnostic.ERROR:
return 'Error: $message';
case api.Diagnostic.WARNING:
return 'Warning: $message';
case api.Diagnostic.HINT:
return 'Hint: $message';
case api.Diagnostic.CRASH:
return 'Internal Error: $message';
case api.Diagnostic.INFO:
case api.Diagnostic.VERBOSE_INFO:
return 'Info: $message';
}
throw 'Unexpected diagnostic kind: $kind (${kind.ordinal})';
}
void diagnosticHandler(Uri uri, int begin, int end, String message,
api.Diagnostic kind) {
// TODO(ahe): Remove this when source map is handled differently.
if (identical(kind.name, 'source map')) return;
if (isAborting) return;
isAborting = (kind == api.Diagnostic.CRASH);
bool fatal = (kind.ordinal & FATAL) != 0;
bool isInfo = (kind.ordinal & INFO) != 0;
if (isInfo && uri == null && kind != api.Diagnostic.INFO) {
info(message, kind);
return;
}
message = prefixMessage(message, kind);
// [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 != api.Diagnostic.INFO) {
lastKind = kind;
}
var color;
if (kind == api.Diagnostic.ERROR) {
color = colors.red;
} else if (kind == api.Diagnostic.WARNING) {
if (!showWarnings) return;
color = colors.magenta;
} else if (kind == api.Diagnostic.HINT) {
if (!showHints) return;
color = colors.cyan;
} else if (kind == api.Diagnostic.CRASH) {
color = colors.red;
} else if (kind == api.Diagnostic.INFO) {
if (lastKind == api.Diagnostic.WARNING && !showWarnings) return;
if (lastKind == api.Diagnostic.HINT && !showHints) return;
color = colors.green;
} else {
throw 'Unknown kind: $kind (${kind.ordinal})';
}
if (!enableColors) {
color = (x) => x;
}
if (uri == null) {
print('${color(message)}');
} else {
SourceFile file = provider.sourceFiles[uri];
if (file != null) {
print(file.getLocationMessage(
color(message), begin, end, colorize: color));
} else {
String position = end - begin > 0 ? '@$begin+${end - begin}' : '';
print('${provider.relativizeUri(uri)}$position:\n'
'${color(message)}');
}
}
if (fatal && ++fatalCount >= throwOnErrorCount && throwOnError) {
isAborting = true;
throw new AbortLeg(message);
}
}
void call(Uri uri, int begin, int end, String message, api.Diagnostic kind) {
return diagnosticHandler(uri, begin, end, message, kind);
}
}
typedef void MessageCallback(String message);
class RandomAccessFileOutputProvider {
final Uri out;
final Uri sourceMapOut;
final MessageCallback onInfo;
final MessageCallback onFailure;
int totalCharactersWritten = 0;
List<String> allOutputFiles = new List<String>();
RandomAccessFileOutputProvider(this.out,
this.sourceMapOut,
{this.onInfo,
this.onFailure});
static Uri computePrecompiledUri(Uri out) {
String extension = 'precompiled.js';
String outPath = out.path;
if (outPath.endsWith('.js')) {
outPath = outPath.substring(0, outPath.length - 3);
return out.resolve('$outPath.$extension');
} else {
return out.resolve(extension);
}
}
EventSink<String> call(String name, String extension) {
Uri uri;
bool isPrimaryOutput = false;
// TODO (johnniwinther, sigurdm): Make a better interface for
// output-providers.
if (extension == "deferred_map") {
uri = out.resolve(name);
} else if (name == '') {
if (extension == 'js' || extension == 'dart') {
isPrimaryOutput = true;
uri = out;
} else if (extension == 'precompiled.js') {
uri = computePrecompiledUri(out);
onInfo("File ($uri) is compatible with header"
" \"Content-Security-Policy: script-src 'self'\"");
} else if (extension == 'js.map' || extension == 'dart.map') {
uri = sourceMapOut;
} else if (extension == "info.json") {
String outName = out.path.substring(out.path.lastIndexOf('/') + 1);
uri = out.resolve('$outName.$extension');
} else {
onFailure('Unknown extension: $extension');
}
} else {
uri = out.resolve('$name.$extension');
}
if (uri.scheme != 'file') {
onFailure('Unhandled scheme ${uri.scheme} in $uri.');
}
RandomAccessFile output;
try {
output = new File(uri.toFilePath()).openSync(mode: FileMode.WRITE);
} on FileSystemException catch(e) {
onFailure('$e');
}
allOutputFiles.add(relativize(currentDirectory, uri, Platform.isWindows));
int charactersWritten = 0;
writeStringSync(String data) {
// Write the data in chunks of 8kb, otherwise we risk running OOM.
int chunkSize = 8*1024;
int offset = 0;
while (offset < data.length) {
output.writeStringSync(
data.substring(offset, math.min(offset + chunkSize, data.length)));
offset += chunkSize;
}
charactersWritten += data.length;
}
onDone() {
output.closeSync();
if (isPrimaryOutput) {
totalCharactersWritten += charactersWritten;
}
}
return new EventSinkWrapper(writeStringSync, onDone);
}
}
class EventSinkWrapper extends EventSink<String> {
var onAdd, onClose;
EventSinkWrapper(this.onAdd, this.onClose);
void add(String data) => onAdd(data);
void addError(error, [StackTrace stackTrace]) => throw error;
void close() => onClose();
}