blob: 6ece7949683f7f8c101704a54f551fc2bd914be3 [file] [log] [blame]
// 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();
}