| // Copyright (c) 2015, 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:convert'; |
| import 'dart:io' as io; |
| |
| import '../lib/shared_messages.dart'; |
| |
| const String jsonPath = '../lib/generated/shared_messages.json'; |
| const String dart2jsPath = |
| '../../compiler/lib/src/diagnostics/generated/shared_messages.dart'; |
| const String analyzerPath = |
| '../../analyzer/lib/src/generated/generated/shared_messages.dart'; |
| |
| final String dontEditWarning = """ |
| /* |
| DON'T EDIT. GENERATED. DON'T EDIT. |
| This file has been generated by 'publish.dart' in the dart_messages package. |
| |
| Messages are maintained in `lib/shared_messages.dart` of that same package. |
| After any change to that file, run `bin/publish.dart` to generate a new version |
| of the json, dart2js and analyzer representations. |
| */"""; |
| |
| const String copyrightHeader = ''' |
| // Copyright (c) 2015, 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.'''; |
| |
| void markAsReadonly(String path) { |
| // TODO(15078): mark as read-only. Currently not possible: |
| // http://dartbug.com/15078. |
| } |
| |
| void emitJson() { |
| var input = MESSAGES; |
| var outPath = io.Platform.script.resolve(jsonPath).toFilePath(); |
| print("Emitting JSON:"); |
| print(" Input: ${input.length} entries"); |
| print(" Output: $outPath"); |
| new io.File(outPath).writeAsStringSync(messagesAsJson); |
| print("Emitting JSON done."); |
| } |
| |
| /// Escapes the given string [str]. |
| /// |
| /// The parameter [str] may be `null` in which case the result is "null". |
| String escapeString(String str) { |
| return JSON.encode(str); |
| } |
| |
| /// Emits the messages in dart2js format. |
| /// |
| /// The dart2js-format consists of two entities: |
| /// 1. the `MessageKind` enum, and |
| /// 2. the MessageKind-to-Template map `TEMPLATES`. |
| /// |
| /// The template is an instance of MessageTemplate: |
| /// |
| /// const MessageTemplate( |
| /// this.kind, |
| /// this.template, |
| /// {this.howToFix, |
| /// this.examples, |
| /// this.options: const <String>[]}); |
| /// |
| /// A sample output thus looks as follows: |
| /// |
| /// enum MessageKind { |
| /// EXAMPLE_MESSAGE, |
| /// } |
| /// |
| /// const Map<MessageKind, MessageTemplate> { |
| /// EXAMPLE_MESSAGE: const MessageTemplate( |
| /// EXAMPLE_MESSAGE, |
| /// "Don't use #foo with #bar", |
| /// howToFix: "Just don't do it", |
| /// options: const ['--some-flag']), |
| /// examples: const [''' |
| /// some example with bad code;'''], |
| /// }; |
| void emitDart2js() { |
| var input = MESSAGES; |
| var outPath = io.Platform.script.resolve(dart2jsPath).toFilePath(); |
| print("Emitting dart2js:"); |
| print(" Input: ${input.length} entries"); |
| print(" Output: $outPath"); |
| |
| StringBuffer out = new StringBuffer(); |
| out.writeln(copyrightHeader); |
| out.writeln(dontEditWarning); |
| out.writeln("import '../messages.dart' show MessageKind, MessageTemplate;"); |
| out.writeln(); |
| out.writeln("const Map<MessageKind, MessageTemplate> TEMPLATES = " |
| "const <MessageKind, MessageTemplate>{ "); |
| input.forEach((name, message) { |
| if (!message.usedBy.contains(Platform.dart2js)) return; |
| |
| out.writeln(" MessageKind.$name: const MessageTemplate("); |
| // TODO(floitsch): include id. |
| out.writeln(" MessageKind.$name,"); |
| out.write(" "); |
| out.write(escapeString(message.template)); |
| if (message.howToFix != null) { |
| out.write(",\n howToFix: ${escapeString(message.howToFix)}"); |
| } |
| if (message.options != null) { |
| out.write(",\n options: const ["); |
| out.write(message.options.map(escapeString).join(",")); |
| out.writeln("]"); |
| } |
| if (message.examples != null) { |
| out.writeln(",\n examples: const ["); |
| |
| String escapeExampleContent(String content) { |
| if (content.contains("\n") || content.contains('"')) { |
| return 'r"""\n$content"""'; |
| } else if (content.contains("\\")) { |
| return 'r"$content"'; |
| } |
| return '"$content"'; |
| } |
| |
| for (var example in message.examples) { |
| if (example is String) { |
| out.write(" "); |
| out.write(escapeExampleContent(example)); |
| } else if (example is Map) { |
| out.writeln(" const {"); |
| example.forEach((String fileName, String content) { |
| out.writeln(" '$fileName': "); |
| out.write(escapeExampleContent(content)); |
| out.writeln(","); |
| }); |
| out.write(" }"); |
| } |
| out.writeln(","); |
| } |
| out.writeln(" ]"); |
| } |
| out.writeln(" ), // Generated. Don't edit."); |
| }); |
| out.writeln("};"); |
| |
| new io.File(outPath).writeAsStringSync(out.toString()); |
| print("Emitting dart2js done."); |
| } |
| |
| String convertToAnalyzerTemplate(String template, holeOrder) { |
| var holeMap; |
| if (holeOrder != null) { |
| holeMap = {}; |
| for (int i = 0; i < holeOrder.length; i++) { |
| holeMap[holeOrder[i]] = i; |
| } |
| } |
| int seenHoles = 0; |
| return template.replaceAllMapped(new RegExp(r"#\w+|#{\w+}"), (Match match) { |
| if (holeMap != null) { |
| String matchedString = match[0]; |
| String holeName = matchedString.startsWith("#{") |
| ? matchedString.substring(2, matchedString.length - 1) |
| : matchedString.substring(1); |
| int index = holeMap[holeName]; |
| if (index == null) { |
| throw "Couldn't find hole-position for $holeName $holeMap"; |
| } |
| return "{$index}"; |
| } else { |
| return "{${seenHoles++}}"; |
| } |
| }); |
| } |
| |
| String camlToAllCaps(String input) { |
| StringBuffer out = new StringBuffer(); |
| for (int i = 0; i < input.length; i++) { |
| String c = input[i]; |
| if (c.toUpperCase() == c) { |
| out.write("_$c"); |
| } else { |
| out.write(c.toUpperCase()); |
| } |
| } |
| return out.toString(); |
| } |
| |
| /// Emits the messages in analyzer format. |
| /// |
| /// Messages are encoded as instances of `ErrorCode` classes where the |
| /// corresponding class is given by the `category` field of the Message. |
| /// |
| /// All instances are stored as top-level const variables. |
| /// |
| /// A sample output looks as follows: |
| /// |
| /// const FooCategoryErrorCode EXAMPLE_MESSAGE = const FooCategoryErrorCode( |
| /// "EXAMPLE_MESSAGE", |
| /// "Don't use {0} with {1}", |
| /// "Just don't do it"); |
| void emitAnalyzer() { |
| var input = MESSAGES; |
| var outPath = io.Platform.script.resolve(analyzerPath).toFilePath(); |
| print("Emitting analyzer:"); |
| print(" Input: ${input.length} entries"); |
| print(" Output: $outPath"); |
| |
| StringBuffer out = new StringBuffer(); |
| out.writeln(copyrightHeader); |
| out.writeln(dontEditWarning); |
| out.writeln("import 'package:analyzer/src/generated/error.dart';"); |
| out.writeln("import 'package:analyzer/src/generated/parser.dart' " |
| "show ParserErrorCode;"); |
| input.forEach((name, message) { |
| if (!message.usedBy.contains(Platform.analyzer)) return; |
| List<Category> categories = message.categories; |
| bool hasMultipleCategories = categories.length != 1; |
| for (Category category in categories) { |
| String className = category.name + "Code"; |
| String analyzerName = |
| hasMultipleCategories ? "$name${camlToAllCaps(category.name)}" : name; |
| out.writeln(); |
| out.writeln("const $className $analyzerName = const $className("); |
| out.writeln(" '$name',"); |
| |
| String template = message.template; |
| List holeOrder = message.templateHoleOrder; |
| String analyzerTemplate = convertToAnalyzerTemplate(template, holeOrder); |
| out.write(" "); |
| out.write(escapeString(analyzerTemplate)); |
| out.write(",\n "); |
| out.write(escapeString(message.howToFix)); |
| out.writeln("); // Generated. Don't edit."); |
| } |
| }); |
| |
| new io.File(outPath).writeAsStringSync(out.toString()); |
| print("Emitting analyzer done."); |
| } |
| |
| /// Translates the shared messages in `../lib/shared_messages.dart` to JSON, |
| /// dart2js, and analyzer formats. |
| /// |
| /// Emits the json-output to [jsonPath], the dart2js-output to [dart2jsPath], |
| /// and the analyzer-output to [analyzerPath]. |
| void main() { |
| emitJson(); |
| emitDart2js(); |
| emitAnalyzer(); |
| } |