| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library source_file_provider; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:convert'; | |
| 9 import 'dart:io'; | |
| 10 import 'dart:math' as math; | |
| 11 | |
| 12 import '../compiler.dart' as api show Diagnostic, DiagnosticHandler; | |
| 13 import 'dart2js.dart' show AbortLeg; | |
| 14 import 'colors.dart' as colors; | |
| 15 import 'source_file.dart'; | |
| 16 import 'filenames.dart'; | |
| 17 import 'util/uri_extras.dart'; | |
| 18 import 'dart:typed_data'; | |
| 19 | |
| 20 List<int> readAll(String filename) { | |
| 21 var file = (new File(filename)).openSync(); | |
| 22 var length = file.lengthSync(); | |
| 23 // +1 to have a 0 terminated list, see [Scanner]. | |
| 24 var buffer = new Uint8List(length + 1); | |
| 25 var bytes = file.readIntoSync(buffer, 0, length); | |
| 26 file.closeSync(); | |
| 27 return buffer; | |
| 28 } | |
| 29 | |
| 30 abstract class SourceFileProvider { | |
| 31 bool isWindows = (Platform.operatingSystem == 'windows'); | |
| 32 Uri cwd = currentDirectory; | |
| 33 Map<String, SourceFile> sourceFiles = <String, SourceFile>{}; | |
| 34 int dartCharactersRead = 0; | |
| 35 | |
| 36 Future<String> readStringFromUri(Uri resourceUri) { | |
| 37 return readUtf8BytesFromUri(resourceUri).then(UTF8.decode); | |
| 38 } | |
| 39 | |
| 40 Future<List<int>> readUtf8BytesFromUri(Uri resourceUri) { | |
| 41 if (resourceUri.scheme == 'file') { | |
| 42 return _readFromFile(resourceUri); | |
| 43 } else if (resourceUri.scheme == 'http' || resourceUri.scheme == 'https') { | |
| 44 return _readFromHttp(resourceUri); | |
| 45 } else { | |
| 46 throw new ArgumentError("Unknown scheme in uri '$resourceUri'"); | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 Future<List<int>> _readFromFile(Uri resourceUri) { | |
| 51 assert(resourceUri.scheme == 'file'); | |
| 52 List<int> source; | |
| 53 try { | |
| 54 source = readAll(resourceUri.toFilePath()); | |
| 55 } on FileSystemException catch (ex) { | |
| 56 return new Future.error( | |
| 57 "Error reading '${relativize(cwd, resourceUri, isWindows)}' " | |
| 58 "(${ex.osError})"); | |
| 59 } | |
| 60 dartCharactersRead += source.length; | |
| 61 sourceFiles[resourceUri.toString()] = | |
| 62 new CachingUtf8BytesSourceFile(relativizeUri(resourceUri), source); | |
| 63 return new Future.value(source); | |
| 64 } | |
| 65 | |
| 66 Future<List<int>> _readFromHttp(Uri resourceUri) { | |
| 67 assert(resourceUri.scheme == 'http'); | |
| 68 HttpClient client = new HttpClient(); | |
| 69 return client.getUrl(resourceUri) | |
| 70 .then((HttpClientRequest request) => request.close()) | |
| 71 .then((HttpClientResponse response) { | |
| 72 if (response.statusCode != HttpStatus.OK) { | |
| 73 String msg = 'Failure getting $resourceUri: ' | |
| 74 '${response.statusCode} ${response.reasonPhrase}'; | |
| 75 throw msg; | |
| 76 } | |
| 77 return response.toList(); | |
| 78 }) | |
| 79 .then((List<List<int>> splitContent) { | |
| 80 int totalLength = splitContent.fold(0, (int old, List list) { | |
| 81 return old + list.length; | |
| 82 }); | |
| 83 Uint8List result = new Uint8List(totalLength); | |
| 84 int offset = 0; | |
| 85 for (List<int> contentPart in splitContent) { | |
| 86 result.setRange( | |
| 87 offset, offset + contentPart.length, contentPart); | |
| 88 offset += contentPart.length; | |
| 89 } | |
| 90 dartCharactersRead += totalLength; | |
| 91 sourceFiles[resourceUri.toString()] = | |
| 92 new CachingUtf8BytesSourceFile(resourceUri.toString(), result); | |
| 93 return result; | |
| 94 }); | |
| 95 } | |
| 96 | |
| 97 Future/*<List<int> | String>*/ call(Uri resourceUri); | |
| 98 | |
| 99 relativizeUri(Uri uri) => relativize(cwd, uri, isWindows); | |
| 100 } | |
| 101 | |
| 102 class CompilerSourceFileProvider extends SourceFileProvider { | |
| 103 Future<List<int>> call(Uri resourceUri) => readUtf8BytesFromUri(resourceUri); | |
| 104 } | |
| 105 | |
| 106 class FormattingDiagnosticHandler { | |
| 107 final SourceFileProvider provider; | |
| 108 bool showWarnings = true; | |
| 109 bool showHints = true; | |
| 110 bool verbose = false; | |
| 111 bool isAborting = false; | |
| 112 bool enableColors = false; | |
| 113 bool throwOnError = false; | |
| 114 int throwOnErrorCount = 0; | |
| 115 api.Diagnostic lastKind = null; | |
| 116 int fatalCount = 0; | |
| 117 | |
| 118 final int FATAL = api.Diagnostic.CRASH.ordinal | api.Diagnostic.ERROR.ordinal; | |
| 119 final int INFO = | |
| 120 api.Diagnostic.INFO.ordinal | api.Diagnostic.VERBOSE_INFO.ordinal; | |
| 121 | |
| 122 FormattingDiagnosticHandler([SourceFileProvider provider]) | |
| 123 : this.provider = | |
| 124 (provider == null) ? new CompilerSourceFileProvider() : provider; | |
| 125 | |
| 126 void info(var message, [api.Diagnostic kind = api.Diagnostic.VERBOSE_INFO]) { | |
| 127 if (!verbose && kind == api.Diagnostic.VERBOSE_INFO) return; | |
| 128 if (enableColors) { | |
| 129 print('${colors.green("Info:")} $message'); | |
| 130 } else { | |
| 131 print('Info: $message'); | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 /// Adds [kind] specific prefix to [message]. | |
| 136 String prefixMessage(String message, api.Diagnostic kind) { | |
| 137 switch (kind) { | |
| 138 case api.Diagnostic.ERROR: | |
| 139 return 'Error: $message'; | |
| 140 case api.Diagnostic.WARNING: | |
| 141 return 'Warning: $message'; | |
| 142 case api.Diagnostic.HINT: | |
| 143 return 'Hint: $message'; | |
| 144 case api.Diagnostic.CRASH: | |
| 145 return 'Internal Error: $message'; | |
| 146 case api.Diagnostic.INFO: | |
| 147 case api.Diagnostic.VERBOSE_INFO: | |
| 148 return 'Info: $message'; | |
| 149 } | |
| 150 throw 'Unexpected diagnostic kind: $kind (${kind.ordinal})'; | |
| 151 } | |
| 152 | |
| 153 void diagnosticHandler(Uri uri, int begin, int end, String message, | |
| 154 api.Diagnostic kind) { | |
| 155 // TODO(ahe): Remove this when source map is handled differently. | |
| 156 if (identical(kind.name, 'source map')) return; | |
| 157 | |
| 158 if (isAborting) return; | |
| 159 isAborting = (kind == api.Diagnostic.CRASH); | |
| 160 | |
| 161 bool fatal = (kind.ordinal & FATAL) != 0; | |
| 162 bool isInfo = (kind.ordinal & INFO) != 0; | |
| 163 if (isInfo && uri == null && kind != api.Diagnostic.INFO) { | |
| 164 info(message, kind); | |
| 165 return; | |
| 166 } | |
| 167 | |
| 168 message = prefixMessage(message, kind); | |
| 169 | |
| 170 // [previousKind]/[lastKind] records the previous non-INFO kind we saw. | |
| 171 // This is used to suppress info about a warning when warnings are | |
| 172 // suppressed, and similar for hints. | |
| 173 var previousKind = lastKind; | |
| 174 if (kind != api.Diagnostic.INFO) { | |
| 175 lastKind = kind; | |
| 176 } | |
| 177 var color; | |
| 178 if (kind == api.Diagnostic.ERROR) { | |
| 179 color = colors.red; | |
| 180 } else if (kind == api.Diagnostic.WARNING) { | |
| 181 if (!showWarnings) return; | |
| 182 color = colors.magenta; | |
| 183 } else if (kind == api.Diagnostic.HINT) { | |
| 184 if (!showHints) return; | |
| 185 color = colors.cyan; | |
| 186 } else if (kind == api.Diagnostic.CRASH) { | |
| 187 color = colors.red; | |
| 188 } else if (kind == api.Diagnostic.INFO) { | |
| 189 if (lastKind == api.Diagnostic.WARNING && !showWarnings) return; | |
| 190 if (lastKind == api.Diagnostic.HINT && !showHints) return; | |
| 191 color = colors.green; | |
| 192 } else { | |
| 193 throw 'Unknown kind: $kind (${kind.ordinal})'; | |
| 194 } | |
| 195 if (!enableColors) { | |
| 196 color = (x) => x; | |
| 197 } | |
| 198 if (uri == null) { | |
| 199 print('${color(message)}'); | |
| 200 } else { | |
| 201 SourceFile file = provider.sourceFiles[uri.toString()]; | |
| 202 if (file != null) { | |
| 203 print(file.getLocationMessage( | |
| 204 color(message), begin, end, colorize: color)); | |
| 205 } else { | |
| 206 print('${provider.relativizeUri(uri)}@$begin+${end - begin}:' | |
| 207 ' [$kind] ${color(message)}'); | |
| 208 } | |
| 209 } | |
| 210 if (fatal && ++fatalCount >= throwOnErrorCount && throwOnError) { | |
| 211 isAborting = true; | |
| 212 throw new AbortLeg(message); | |
| 213 } | |
| 214 } | |
| 215 | |
| 216 void call(Uri uri, int begin, int end, String message, api.Diagnostic kind) { | |
| 217 return diagnosticHandler(uri, begin, end, message, kind); | |
| 218 } | |
| 219 } | |
| 220 | |
| 221 typedef void MessageCallback(String message); | |
| 222 | |
| 223 class RandomAccessFileOutputProvider { | |
| 224 final Uri out; | |
| 225 final Uri sourceMapOut; | |
| 226 final MessageCallback onInfo; | |
| 227 final MessageCallback onFailure; | |
| 228 | |
| 229 int totalCharactersWritten = 0; | |
| 230 List<String> allOutputFiles = new List<String>(); | |
| 231 | |
| 232 RandomAccessFileOutputProvider(this.out, | |
| 233 this.sourceMapOut, | |
| 234 {this.onInfo, | |
| 235 this.onFailure}); | |
| 236 | |
| 237 static Uri computePrecompiledUri(Uri out) { | |
| 238 String extension = 'precompiled.js'; | |
| 239 String outPath = out.path; | |
| 240 if (outPath.endsWith('.js')) { | |
| 241 outPath = outPath.substring(0, outPath.length - 3); | |
| 242 return out.resolve('$outPath.$extension'); | |
| 243 } else { | |
| 244 return out.resolve(extension); | |
| 245 } | |
| 246 } | |
| 247 | |
| 248 EventSink<String> call(String name, String extension) { | |
| 249 Uri uri; | |
| 250 String sourceMapFileName; | |
| 251 bool isPrimaryOutput = false; | |
| 252 if (name == '') { | |
| 253 if (extension == 'js' || extension == 'dart') { | |
| 254 isPrimaryOutput = true; | |
| 255 uri = out; | |
| 256 sourceMapFileName = | |
| 257 sourceMapOut.path.substring(sourceMapOut.path.lastIndexOf('/') + 1); | |
| 258 } else if (extension == 'precompiled.js') { | |
| 259 uri = computePrecompiledUri(out); | |
| 260 onInfo("File ($uri) is compatible with header" | |
| 261 " \"Content-Security-Policy: script-src 'self'\""); | |
| 262 } else if (extension == 'js.map' || extension == 'dart.map') { | |
| 263 uri = sourceMapOut; | |
| 264 } else if (extension == 'info.html' || extension == "info.json") { | |
| 265 String outName = out.path.substring(out.path.lastIndexOf('/') + 1); | |
| 266 uri = out.resolve('$outName.$extension'); | |
| 267 } else { | |
| 268 onFailure('Unknown extension: $extension'); | |
| 269 } | |
| 270 } else { | |
| 271 uri = out.resolve('$name.$extension'); | |
| 272 } | |
| 273 | |
| 274 if (uri.scheme != 'file') { | |
| 275 onFailure('Unhandled scheme ${uri.scheme} in $uri.'); | |
| 276 } | |
| 277 | |
| 278 RandomAccessFile output; | |
| 279 try { | |
| 280 output = new File(uri.toFilePath()).openSync(mode: FileMode.WRITE); | |
| 281 } on FileSystemException catch(e) { | |
| 282 onFailure('$e'); | |
| 283 } | |
| 284 | |
| 285 allOutputFiles.add(relativize(currentDirectory, uri, Platform.isWindows)); | |
| 286 | |
| 287 int charactersWritten = 0; | |
| 288 | |
| 289 writeStringSync(String data) { | |
| 290 // Write the data in chunks of 8kb, otherwise we risk running OOM. | |
| 291 int chunkSize = 8*1024; | |
| 292 | |
| 293 int offset = 0; | |
| 294 while (offset < data.length) { | |
| 295 output.writeStringSync( | |
| 296 data.substring(offset, math.min(offset + chunkSize, data.length))); | |
| 297 offset += chunkSize; | |
| 298 } | |
| 299 charactersWritten += data.length; | |
| 300 } | |
| 301 | |
| 302 onDone() { | |
| 303 output.closeSync(); | |
| 304 if (isPrimaryOutput) { | |
| 305 totalCharactersWritten += charactersWritten; | |
| 306 } | |
| 307 } | |
| 308 | |
| 309 return new EventSinkWrapper(writeStringSync, onDone); | |
| 310 } | |
| 311 } | |
| 312 | |
| 313 class EventSinkWrapper extends EventSink<String> { | |
| 314 var onAdd, onClose; | |
| 315 | |
| 316 EventSinkWrapper(this.onAdd, this.onClose); | |
| 317 | |
| 318 void add(String data) => onAdd(data); | |
| 319 | |
| 320 void addError(error, [StackTrace stackTrace]) => throw error; | |
| 321 | |
| 322 void close() => onClose(); | |
| 323 } | |
| OLD | NEW |