Version 2.14.0-141.0.dev
Merge commit '43b9fca8df27744819133cc658735a44a1c873b1' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index dd474e4..873b751 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
"constraint, update this by running tools/generate_package_config.dart."
],
"configVersion": 2,
- "generated": "2021-05-20T11:33:30.068787",
+ "generated": "2021-05-17T10:34:01.378194",
"generator": "tools/generate_package_config.dart",
"packages": [
{
@@ -387,7 +387,7 @@
"name": "js_ast",
"rootUri": "../pkg/js_ast",
"packageUri": "lib/",
- "languageVersion": "2.10"
+ "languageVersion": "2.0"
},
{
"name": "js_runtime",
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index 069482a..62ab3cd 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -742,15 +742,7 @@
}
}
- void printComparisons() {
- printHeading(1, 'Comparison of experiments');
- printMrrComparison();
- printCounter(rankComparison);
- printOtherMetrics();
- printCompletionCounts();
- }
-
- void printCompletionCounts() {
+ void printComparisonOfCompletionCounts() {
String toString(int count, int totalCount) {
return '$count (${printPercentage(count / totalCount, 2)})';
}
@@ -776,6 +768,70 @@
printTable(table);
}
+ void printComparisonOfOtherMetrics() {
+ List<String> toRow(Iterable<ArithmeticMeanComputer> sources) {
+ var computers = sources.toList();
+ var row = [computers.first.name];
+ for (var computer in computers) {
+ var min = computer.min;
+ var mean = computer.mean.toStringAsFixed(6);
+ var max = computer.max;
+ row.add('$min, $mean, $max');
+ }
+ return row;
+ }
+
+ var table = [
+ ['', for (var metrics in targetMetrics) metrics.name],
+ toRow(targetMetrics.map((metrics) => metrics.meanCompletionMS)),
+ toRow(targetMetrics.map((metrics) => metrics.charsBeforeTop)),
+ toRow(targetMetrics.map((metrics) => metrics.charsBeforeTopFive)),
+ toRow(targetMetrics.map((metrics) => metrics.insertionLengthTheoretical)),
+ ];
+ rightJustifyColumns(table, range(1, table[0].length));
+
+ printHeading(2, 'Comparison of other metrics');
+ printTable(table);
+
+ for (var metrics in targetMetrics) {
+ var distribution = metrics.distributionCompletionMS.displayString();
+ print('${metrics.name}: $distribution');
+ }
+ print('');
+ }
+
+ void printComparisons() {
+ printHeading(1, 'Comparison of experiments');
+ printMrrComparison();
+ printCounter(rankComparison);
+ printComparisonOfOtherMetrics();
+ printComparisonOfCompletionCounts();
+ }
+
+ void printCompletionCounts(CompletionMetrics metrics) {
+ String toString(int count, int totalCount) {
+ return '$count (${printPercentage(count / totalCount, 2)})';
+ }
+
+ var counter = metrics.completionCounter;
+ var table = [
+ ['', metrics.name],
+ ['total', counter.totalCount.toString()],
+ [
+ 'successful',
+ toString(counter.getCountOf('successful'), counter.totalCount)
+ ],
+ [
+ 'unsuccessful',
+ toString(counter.getCountOf('unsuccessful'), counter.totalCount)
+ ],
+ ];
+ rightJustifyColumns(table, range(1, table[0].length));
+
+ printHeading(2, 'Completion counts');
+ printTable(table);
+ }
+
void printCounter(Counter counter) {
var name = counter.name;
var total = counter.totalCount;
@@ -797,9 +853,6 @@
printCounter(metrics.completionElementKindCounter);
}
- var distribution = metrics.distributionCompletionMS.displayString();
- print('${metrics.name}: $distribution');
-
List<String> toRow(MeanReciprocalRankComputer computer) {
return [
computer.name,
@@ -852,6 +905,14 @@
}
printTable(table);
}
+ //
+ // Print information that would normally appear in the comprison when there
+ // is no comparison section.
+ //
+ if (targetMetrics.length == 1) {
+ printOtherMetrics(metrics);
+ printCompletionCounts(metrics);
+ }
}
void printMissingInformation(CompletionMetrics metrics) {
@@ -933,40 +994,33 @@
printTable(table);
}
- void printOtherMetrics() {
- List<String> toRow(Iterable<ArithmeticMeanComputer> sources) {
- var computers = sources.toList();
- var row = [computers.first.name];
- for (var computer in computers) {
- var min = computer.min;
- var mean = computer.mean.toStringAsFixed(6);
- var max = computer.max;
- row.add('$min, $mean, $max');
- }
- return row;
+ void printOtherMetrics(CompletionMetrics metrics) {
+ List<String> toRow(ArithmeticMeanComputer computer) {
+ var min = computer.min;
+ var mean = computer.mean.toStringAsFixed(6);
+ var max = computer.max;
+ return [computer.name, '$min, $mean, $max'];
}
var table = [
- ['', for (var metrics in targetMetrics) metrics.name],
- toRow(targetMetrics.map((metrics) => metrics.meanCompletionMS)),
- toRow(targetMetrics.map((metrics) => metrics.charsBeforeTop)),
- toRow(targetMetrics.map((metrics) => metrics.charsBeforeTopFive)),
- toRow(targetMetrics.map((metrics) => metrics.insertionLengthTheoretical)),
+ toRow(metrics.meanCompletionMS),
+ toRow(metrics.charsBeforeTop),
+ toRow(metrics.charsBeforeTopFive),
+ toRow(metrics.insertionLengthTheoretical),
];
rightJustifyColumns(table, range(1, table[0].length));
- printHeading(2, 'Comparison of other metrics');
+ printHeading(2, 'Other metrics');
printTable(table);
- for (var metrics in targetMetrics) {
- var distribution = metrics.distributionCompletionMS.displayString();
- print('${metrics.name}: $distribution');
- }
+ var distribution = metrics.distributionCompletionMS.displayString();
+ print('${metrics.name}: $distribution');
+ print('');
}
void printResults() {
+ print('');
if (targetMetrics.length > 1) {
- print('');
printComparisons();
}
var needsBlankLine = false;
diff --git a/pkg/analyzer/lib/src/workspace/package_build.dart b/pkg/analyzer/lib/src/workspace/package_build.dart
index 8f09c11..94716f7 100644
--- a/pkg/analyzer/lib/src/workspace/package_build.dart
+++ b/pkg/analyzer/lib/src/workspace/package_build.dart
@@ -9,9 +9,11 @@
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/source_io.dart';
+import 'package:analyzer/src/lint/pub.dart';
import 'package:analyzer/src/source/package_map_resolver.dart';
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/util/uri.dart';
+import 'package:analyzer/src/workspace/pub.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
@@ -56,7 +58,7 @@
: _workspace = workspace,
_context = workspace.provider.pathContext;
- Map<String, List<Folder>> get packageMap => _workspace._packageMap;
+ Map<String, List<Folder>> get packageMap => _workspace.packageMap;
@override
Source? resolveAbsolute(Uri uri) {
@@ -120,7 +122,7 @@
}
/// Information about a package:build workspace.
-class PackageBuildWorkspace extends Workspace {
+class PackageBuildWorkspace extends Workspace implements PubWorkspace {
/// The name of the directory that identifies the root of the workspace. Note,
/// the presence of this file does not show package:build is used. For that,
/// the subdirectory [_dartToolBuildName] must exist. A `pub` subdirectory
@@ -131,21 +133,26 @@
/// projects built with package:build.
static const String _dartToolBuildName = 'build';
- /// We use pubspec.yaml to get the package name to be consistent with how
- /// package:build does it.
- static const String _pubspecName = 'pubspec.yaml';
-
static const List<String> _generatedPathParts = [
'.dart_tool',
'build',
'generated'
];
- /// The resource provider used to access the file system.
- final ResourceProvider provider;
+ /// We use pubspec.yaml to get the package name to be consistent with how
+ /// package:build does it.
+ static const String _pubspecName = 'pubspec.yaml';
+
+ /// The associated pubspec file.
+ final File _pubspecFile;
/// The map from a package name to the list of its `lib/` folders.
- final Map<String, List<Folder>> _packageMap;
+ @override
+ final Map<String, List<Folder>> packageMap;
+
+ /// The resource provider used to access the file system.
+ @override
+ final ResourceProvider provider;
/// The absolute workspace root path (the directory containing the
/// `.dart_tool` directory).
@@ -169,18 +176,19 @@
PackageBuildWorkspace._(
this.provider,
- this._packageMap,
+ this.packageMap,
this.root,
this.projectPackageName,
this.generatedRootPath,
this.generatedThisPath,
+ this._pubspecFile,
) {
_theOnlyPackage = PackageBuildWorkspacePackage(root, this);
}
@override
UriResolver get packageUriResolver => PackageBuildPackageUriResolver(
- this, PackageMapUriResolver(provider, _packageMap));
+ this, PackageMapUriResolver(provider, packageMap));
/// For some package file, which may or may not be a package source (it could
/// be in `bin/`, `web/`, etc), find where its built counterpart will exist if
@@ -190,7 +198,7 @@
/// use [builtPackageSourcePath]. For `bin/`, `web/`, etc, it must be relative
/// to the project root.
File? builtFile(String builtPath, String packageName) {
- if (!_packageMap.containsKey(packageName)) {
+ if (!packageMap.containsKey(packageName)) {
return null;
}
path.Context context = provider.pathContext;
@@ -294,7 +302,7 @@
final generatedThisPath =
provider.pathContext.join(generatedRootPath, packageName);
return PackageBuildWorkspace._(provider, packageMap, folder.path,
- packageName, generatedRootPath, generatedThisPath);
+ packageName, generatedRootPath, generatedThisPath, pubspec);
} catch (_) {
return null;
}
@@ -315,7 +323,18 @@
/// Separate from [Packages] or package maps, this class is designed to simply
/// understand whether arbitrary file paths represent libraries declared within
/// a given package in a PackageBuildWorkspace.
-class PackageBuildWorkspacePackage extends WorkspacePackage {
+class PackageBuildWorkspacePackage extends WorkspacePackage
+ implements PubWorkspacePackage {
+ @override
+ late final Pubspec? pubspec = () {
+ try {
+ final content = workspace._pubspecFile.readAsStringSync();
+ return Pubspec.parse(content);
+ } catch (_) {
+ // Pubspec will be null.
+ }
+ }();
+
@override
final String root;
@@ -343,7 +362,7 @@
@override
Map<String, List<Folder>> packagesAvailableTo(String libraryPath) =>
- workspace._packageMap;
+ workspace.packageMap;
@override
bool sourceIsInPublicApi(Source source) {
diff --git a/pkg/analyzer/lib/src/workspace/simple.dart b/pkg/analyzer/lib/src/workspace/simple.dart
index 4f9cc06..51d96d2 100644
--- a/pkg/analyzer/lib/src/workspace/simple.dart
+++ b/pkg/analyzer/lib/src/workspace/simple.dart
@@ -19,7 +19,7 @@
/// The [ResourceProvider] by which paths are converted into [Resource]s.
final ResourceProvider provider;
- Map<String, List<Folder>> packageMap;
+ final Map<String, List<Folder>> packageMap;
/// The absolute workspace root path.
@override
diff --git a/pkg/compiler/lib/src/js/js.dart b/pkg/compiler/lib/src/js/js.dart
index 9c8328e..b49abc0 100644
--- a/pkg/compiler/lib/src/js/js.dart
+++ b/pkg/compiler/lib/src/js/js.dart
@@ -157,7 +157,7 @@
}
}
}
- _cachedLiteral = js.string(text);
+ _cachedLiteral = js.escapedString(text);
}
return _cachedLiteral;
}
diff --git a/pkg/compiler/lib/src/js/size_estimator.dart b/pkg/compiler/lib/src/js/size_estimator.dart
index 24527ec6..8ecfd9e 100644
--- a/pkg/compiler/lib/src/js/size_estimator.dart
+++ b/pkg/compiler/lib/src/js/size_estimator.dart
@@ -749,24 +749,24 @@
}
bool isValidJavaScriptId(String field) {
- if (field.length == 0) return false;
+ if (field.length < 3) return false;
// Ignore the leading and trailing string-delimiter.
- for (int i = 0; i < field.length; i++) {
+ for (int i = 1; i < field.length - 1; i++) {
// TODO(floitsch): allow more characters.
int charCode = field.codeUnitAt(i);
if (!(charCodes.$a <= charCode && charCode <= charCodes.$z ||
charCodes.$A <= charCode && charCode <= charCodes.$Z ||
charCode == charCodes.$$ ||
charCode == charCodes.$_ ||
- i > 0 && isDigit(charCode))) {
+ i != 1 && isDigit(charCode))) {
return false;
}
}
// TODO(floitsch): normally we should also check that the field is not a
// reserved word. We don't generate fields with reserved word names except
// for 'super'.
- if (field == 'super') return false;
- if (field == 'catch') return false;
+ if (field == '"super"') return false;
+ if (field == '"catch"') return false;
return true;
}
@@ -776,17 +776,16 @@
newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
Node selector = access.selector;
if (selector is LiteralString) {
- String field = literalStringToString(selector);
- if (isValidJavaScriptId(field)) {
+ String fieldWithQuotes = literalStringToString(selector);
+ if (isValidJavaScriptId(fieldWithQuotes)) {
if (access.receiver is LiteralNumber) {
// We can eliminate the space in some cases, but for simplicity we
// always assume it is necessary.
out(' '); // ' '
}
- // '.${field}'
- out('.');
- out(field);
+ // '.${fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)}'
+ out('.${fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)}');
return;
}
} else if (selector is Name) {
@@ -876,9 +875,7 @@
@override
void visitLiteralString(LiteralString node) {
- out('"');
out(literalStringToString(node));
- out('"');
}
@override
@@ -971,12 +968,10 @@
if (name is LiteralString) {
String text = literalStringToString(name);
if (isValidJavaScriptId(text)) {
- out(text);
+ // '${text.substring(1, text.length - 1)}
+ out('${text.substring(1, text.length - 1)}');
} else {
- // Approximation to `_handleString(text)`.
- out('"');
- out(text);
- out('"');
+ out(text); // '$text'
}
} else if (name is Name) {
node.name.accept(this);
diff --git a/pkg/compiler/lib/src/js_backend/constant_emitter.dart b/pkg/compiler/lib/src/js_backend/constant_emitter.dart
index dd67c4a..ec3928d 100644
--- a/pkg/compiler/lib/src/js_backend/constant_emitter.dart
+++ b/pkg/compiler/lib/src/js_backend/constant_emitter.dart
@@ -148,7 +148,7 @@
jsAst.Expression visitString(StringConstantValue constant, [_]) {
String value = constant.stringValue;
if (value.length < StringReferencePolicy.minimumLength) {
- return js.string(value);
+ return js.escapedString(value, ascii: true);
}
return StringReference(constant);
}
@@ -288,7 +288,8 @@
}
// Keys in literal maps must be emitted in place.
- jsAst.Literal keyExpression = js.string(key.stringValue);
+ jsAst.Literal keyExpression =
+ js.escapedString(key.stringValue, ascii: true);
jsAst.Expression valueExpression =
_constantReferenceGenerator(constant.values[i]);
properties.add(new jsAst.Property(keyExpression, valueExpression));
diff --git a/pkg/compiler/lib/src/js_backend/runtime_types_new.dart b/pkg/compiler/lib/src/js_backend/runtime_types_new.dart
index 4c59ced..3c9f11f 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types_new.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types_new.dart
@@ -109,7 +109,9 @@
return _finishEncoding(js.string(String.fromCharCodes(_codes)));
}
_flushCodes();
- return _finishEncoding(jsAst.StringConcatenation(_fragments));
+ jsAst.LiteralString quote = jsAst.LiteralString('"');
+ return _finishEncoding(
+ jsAst.StringConcatenation([quote, ..._fragments, quote]));
}
void _start(TypeRecipe recipe) {
@@ -485,13 +487,13 @@
CommonElements get _commonElements => _dartTypes.commonElements;
ClassEntity get _objectClass => _commonElements.objectClass;
- final _leftBrace = js.string('{');
- final _rightBrace = js.string('}');
- final _leftBracket = js.string('[');
- final _rightBracket = js.string(']');
- final _colon = js.string(':');
- final _comma = js.string(',');
- final _doubleQuote = js.string('"');
+ final _leftBrace = js.stringPart('{');
+ final _rightBrace = js.stringPart('}');
+ final _leftBracket = js.stringPart('[');
+ final _rightBracket = js.stringPart(']');
+ final _colon = js.stringPart(':');
+ final _comma = js.stringPart(',');
+ final _quote = js.stringPart("'");
bool _isObject(InterfaceType type) => identical(type.element, _objectClass);
@@ -520,32 +522,28 @@
jsAst.StringConcatenation _encodeRuleset(Ruleset ruleset) =>
js.concatenateStrings([
+ _quote,
_leftBrace,
...js.joinLiterals([
...ruleset._redirections.entries.map(_encodeRedirection),
...ruleset._entries.entries.map(_encodeEntry),
], _comma),
_rightBrace,
+ _quote,
]);
jsAst.StringConcatenation _encodeRedirection(
MapEntry<ClassEntity, ClassEntity> redirection) =>
js.concatenateStrings([
- _doubleQuote,
- _emitter.typeAccessNewRti(redirection.key),
- _doubleQuote,
+ js.quoteName(_emitter.typeAccessNewRti(redirection.key)),
_colon,
- _doubleQuote,
- _emitter.typeAccessNewRti(redirection.value),
- _doubleQuote,
+ js.quoteName(_emitter.typeAccessNewRti(redirection.value)),
]);
jsAst.StringConcatenation _encodeEntry(
MapEntry<InterfaceType, _RulesetEntry> entry) =>
js.concatenateStrings([
- _doubleQuote,
- _emitter.typeAccessNewRti(entry.key.element),
- _doubleQuote,
+ js.quoteName(_emitter.typeAccessNewRti(entry.key.element)),
_colon,
_leftBrace,
...js.joinLiterals([
@@ -560,9 +558,7 @@
jsAst.StringConcatenation _encodeSupertype(
InterfaceType targetType, InterfaceType supertype) =>
js.concatenateStrings([
- _doubleQuote,
- _emitter.typeAccessNewRti(supertype.element),
- _doubleQuote,
+ js.quoteName(_emitter.typeAccessNewRti(supertype.element)),
_colon,
_leftBracket,
...js.joinLiterals(
@@ -575,36 +571,30 @@
jsAst.StringConcatenation _encodeTypeVariable(InterfaceType targetType,
TypeVariableType typeVariable, DartType supertypeArgument) =>
js.concatenateStrings([
- _doubleQuote,
- _emitter.typeVariableAccessNewRti(typeVariable.element),
- _doubleQuote,
+ js.quoteName(_emitter.typeVariableAccessNewRti(typeVariable.element)),
_colon,
_encodeSupertypeArgument(targetType, supertypeArgument),
]);
jsAst.Literal _encodeSupertypeArgument(
InterfaceType targetType, DartType supertypeArgument) =>
- js.concatenateStrings([
- _doubleQuote,
- _recipeEncoder.encodeMetadataRecipe(
- _emitter, targetType, supertypeArgument),
- _doubleQuote
- ]);
+ _recipeEncoder.encodeMetadataRecipe(
+ _emitter, targetType, supertypeArgument);
jsAst.StringConcatenation encodeErasedTypes(
Map<ClassEntity, int> erasedTypes) =>
js.concatenateStrings([
+ _quote,
_leftBrace,
...js.joinLiterals(erasedTypes.entries.map(encodeErasedType), _comma),
_rightBrace,
+ _quote,
]);
jsAst.StringConcatenation encodeErasedType(
MapEntry<ClassEntity, int> entry) =>
js.concatenateStrings([
- _doubleQuote,
- _emitter.typeAccessNewRti(entry.key),
- _doubleQuote,
+ js.quoteName(_emitter.typeAccessNewRti(entry.key)),
_colon,
js.number(entry.value),
]);
@@ -612,20 +602,20 @@
jsAst.StringConcatenation encodeTypeParameterVariances(
Map<ClassEntity, List<Variance>> typeParameterVariances) =>
js.concatenateStrings([
+ _quote,
_leftBrace,
...js.joinLiterals(
typeParameterVariances.entries
.map(_encodeTypeParameterVariancesForClass),
_comma),
_rightBrace,
+ _quote,
]);
jsAst.StringConcatenation _encodeTypeParameterVariancesForClass(
MapEntry<ClassEntity, List<Variance>> classEntry) =>
js.concatenateStrings([
- _doubleQuote,
- _emitter.typeAccessNewRti(classEntry.key),
- _doubleQuote,
+ js.quoteName(_emitter.typeAccessNewRti(classEntry.key)),
_colon,
_leftBracket,
...js.joinLiterals(
diff --git a/pkg/compiler/lib/src/js_backend/string_reference.dart b/pkg/compiler/lib/src/js_backend/string_reference.dart
index 590ff93..05e1b8f 100644
--- a/pkg/compiler/lib/src/js_backend/string_reference.dart
+++ b/pkg/compiler/lib/src/js_backend/string_reference.dart
@@ -256,7 +256,8 @@
for (_ReferenceSet referenceSet in _referencesByString.values) {
if (referenceSet.generateAtUse) {
StringConstantValue constant = referenceSet.constant;
- js.Expression reference = js.string(constant.stringValue);
+ js.Expression reference =
+ js.js.escapedString(constant.stringValue, ascii: true);
for (StringReference ref in referenceSet._references) {
ref.value = reference;
}
@@ -274,7 +275,8 @@
for (_ReferenceSet referenceSet in referenceSetsUsingProperties) {
String string = referenceSet.constant.stringValue;
var propertyName = js.string(referenceSet.propertyName);
- properties.add(js.Property(propertyName, js.string(string)));
+ properties.add(
+ js.Property(propertyName, js.js.escapedString(string, ascii: true)));
var access = js.js('#.#', [holderLocalName, propertyName]);
for (StringReference ref in referenceSet._references) {
ref.value = access;
diff --git a/pkg/compiler/test/codegen/jsarray_indexof_test.dart b/pkg/compiler/test/codegen/jsarray_indexof_test.dart
index b567137..4b75ecd 100644
--- a/pkg/compiler/test/codegen/jsarray_indexof_test.dart
+++ b/pkg/compiler/test/codegen/jsarray_indexof_test.dart
@@ -67,7 +67,7 @@
"${js.nodeToString(method.code, pretty: true)}");
}, onPropertyAccess: (js.PropertyAccess node) {
js.Node selector = node.selector;
- if (selector is js.LiteralString && selector.value == 'length') {
+ if (selector is js.LiteralString && selector.value == '"length"') {
lengthCount++;
}
});
diff --git a/pkg/compiler/test/codegen/model_test.dart b/pkg/compiler/test/codegen/model_test.dart
index ba7376d..f7a1537 100644
--- a/pkg/compiler/test/codegen/model_test.dart
+++ b/pkg/compiler/test/codegen/model_test.dart
@@ -101,7 +101,7 @@
/// Call to fixed backend name, so we include the argument
/// values to test encoding of optional parameters in native
/// methods.
- name = selector.value;
+ name = selector.value.substring(1, selector.value.length - 1);
fixedNameCall = true;
}
if (name != null) {
@@ -146,7 +146,7 @@
/// Call to fixed backend name, so we include the argument
/// values to test encoding of optional parameters in native
/// methods.
- name = selector.value;
+ name = selector.value.substring(1, selector.value.length - 1);
}
if (receiverName != null && name != null) {
@@ -236,7 +236,7 @@
if (selector is js.Name) {
name = selector.key;
} else if (selector is js.LiteralString) {
- name = selector.value;
+ name = selector.value.substring(1, selector.value.length - 1);
}
if (name != null) {
features.addElement(Tags.assignment, '${name}');
diff --git a/pkg/compiler/test/js/js_parser_test.dart b/pkg/compiler/test/js/js_parser_test.dart
index e9b4822..7e7ac3f 100644
--- a/pkg/compiler/test/js/js_parser_test.dart
+++ b/pkg/compiler/test/js/js_parser_test.dart
@@ -20,9 +20,7 @@
testError(String expression, [String expect = ""]) {
bool doCheck(exception) {
- final exceptionText = '$exception';
- Expect.isTrue(exceptionText.contains(expect),
- 'Missing "$expect" in "$exceptionText"');
+ Expect.isTrue(exception.toString().contains(expect));
return true;
}
@@ -67,9 +65,9 @@
// String literal with \n.
testExpression(r'var x = "\n"');
// String literal with escaped quote.
- testExpression(r'''var x = "\""''', r"""var x = '"'""");
+ testExpression(r'var x = "\""');
// *No clever escapes.
- testError(r'var x = "\x42"', 'Hex escapes not supported');
+ testError(r'var x = "\x42"', 'escapes are not allowed in literals');
// Operator new.
testExpression('new Foo()');
// New with dotted access.
@@ -170,7 +168,7 @@
testExpression("x << y + 1");
testExpression("x <<= y + 1");
// Array initializers.
- testExpression('x = ["foo", "bar", x[4]]');
+ testExpression("x = ['foo', 'bar', x[4]]");
testExpression("[]");
testError("[42 42]");
testExpression('beebop([1, 2, 3])');
diff --git a/pkg/compiler/test/js/size_estimator_expectations.json b/pkg/compiler/test/js/size_estimator_expectations.json
index 2dacebb..57b911f 100644
--- a/pkg/compiler/test/js/size_estimator_expectations.json
+++ b/pkg/compiler/test/js/size_estimator_expectations.json
@@ -102,8 +102,8 @@
},
{
"original": "x = ['a', 'b', 'c']",
- "expected": "#=[\"a\",\"b\",\"c\"]",
- "minified": "x=[\"a\",\"b\",\"c\"]"
+ "expected": "#=['a','b','c']",
+ "minified": "x=['a','b','c']"
},
{
"original": "a = {'b': 1, 'c': 2}",
@@ -154,8 +154,8 @@
},
{
"original": "if (x == true) { return true; } else if (y < 3 || z > 5) { return l != null ? 'a' : 4; } else { foo(); return; }",
- "expected": "if(#==!0)return !0;else if(#<3||#>5)return #!=null?\"a\":4;else{#();return;}",
- "minified": "if(x==true)return true;else if(y<3||z>5)return l!=null?\"a\":4;else{foo();return}"
+ "expected": "if(#==!0)return !0;else if(#<3||#>5)return #!=null?'a':4;else{#();return;}",
+ "minified": "if(x==true)return true;else if(y<3||z>5)return l!=null?'a':4;else{foo();return}"
},
{
"original": "for (var a = 0; a < 10; a++) { foo(a); }",
@@ -179,8 +179,8 @@
},
{
"original": "switch (foo) { case 'a': case 'b': bar(); break; case 'c': 1; break; default: boo(); }",
- "expected": "switch(#){case \"a\":case \"b\":#();break;case \"c\":1;break;default:#();}",
- "minified": "switch(foo){case\"a\":case\"b\":bar();break;case\"c\":1;break;default:boo()}"
+ "expected": "switch(#){case 'a':case 'b':#();break;case 'c':1;break;default:#();}",
+ "minified": "switch(foo){case'a':case'b':bar();break;case'c':1;break;default:boo()}"
},
{
"original": "foo.prototype.Goo = function(a) { return a.bar(); }",
@@ -193,4 +193,4 @@
"minified": "try{null=4}catch(e){print(e)}"
}
]
-}
+}
\ No newline at end of file
diff --git a/pkg/js_ast/lib/js_ast.dart b/pkg/js_ast/lib/js_ast.dart
index 1958f19..01b0dd9 100644
--- a/pkg/js_ast/lib/js_ast.dart
+++ b/pkg/js_ast/lib/js_ast.dart
@@ -4,9 +4,9 @@
library js_ast;
+import 'dart:collection' show IterableBase;
import 'src/precedence.dart';
import 'src/characters.dart' as charCodes;
-import 'src/strings.dart';
part 'src/nodes.dart';
part 'src/builder.dart';
diff --git a/pkg/js_ast/lib/src/builder.dart b/pkg/js_ast/lib/src/builder.dart
index 1f980ea..2fee946 100644
--- a/pkg/js_ast/lib/src/builder.dart
+++ b/pkg/js_ast/lib/src/builder.dart
@@ -296,29 +296,186 @@
}
/// Creates a literal js string from [value].
- LiteralString string(String value) => LiteralString(value);
+ LiteralString _legacyEscapedString(String value) {
+ // Start by escaping the backslashes.
+ String escaped = value.replaceAll('\\', '\\\\');
+ // Do not escape unicode characters and ' because they are allowed in the
+ // string literal anyway.
+ escaped = escaped.replaceAllMapped(new RegExp('\n|"|\b|\t|\v|\r'), (match) {
+ switch (match.group(0)) {
+ case "\n":
+ return r"\n";
+ case "\"":
+ return r'\"';
+ case "\b":
+ return r"\b";
+ case "\t":
+ return r"\t";
+ case "\f":
+ return r"\f";
+ case "\r":
+ return r"\r";
+ case "\v":
+ return r"\v";
+ }
+ throw new UnsupportedError("Unexpected match: ${match.group(0)}");
+ });
+ LiteralString result = string(escaped);
+ // We don't escape ' under the assumption that the string is wrapped
+ // into ". Verify that assumption.
+ assert(result.value.codeUnitAt(0) == '"'.codeUnitAt(0));
+ return result;
+ }
+
+ /// Creates a literal js string from [value].
+ LiteralString escapedString(String value,
+ {bool utf8: false, bool ascii: false}) {
+ if (utf8 == false && ascii == false) return _legacyEscapedString(value);
+ if (utf8 && ascii) throw new ArgumentError('Cannot be both UTF8 and ASCII');
+
+ int singleQuotes = 0;
+ int doubleQuotes = 0;
+ int otherEscapes = 0;
+ int unpairedSurrogates = 0;
+
+ for (int rune in value.runes) {
+ if (rune == charCodes.$BACKSLASH) {
+ ++otherEscapes;
+ } else if (rune == charCodes.$SQ) {
+ ++singleQuotes;
+ } else if (rune == charCodes.$DQ) {
+ ++doubleQuotes;
+ } else if (rune == charCodes.$LF ||
+ rune == charCodes.$CR ||
+ rune == charCodes.$LS ||
+ rune == charCodes.$PS) {
+ // Line terminators.
+ ++otherEscapes;
+ } else if (rune == charCodes.$BS ||
+ rune == charCodes.$TAB ||
+ rune == charCodes.$VTAB ||
+ rune == charCodes.$FF) {
+ ++otherEscapes;
+ } else if (_isUnpairedSurrogate(rune)) {
+ ++unpairedSurrogates;
+ } else {
+ if (ascii && (rune < charCodes.$SPACE || rune >= charCodes.$DEL)) {
+ ++otherEscapes;
+ }
+ }
+ }
+
+ LiteralString finish(String quote, String contents) {
+ return new LiteralString('$quote$contents$quote');
+ }
+
+ if (otherEscapes == 0 && unpairedSurrogates == 0) {
+ if (doubleQuotes == 0) return finish('"', value);
+ if (singleQuotes == 0) return finish("'", value);
+ }
+
+ bool useSingleQuotes = singleQuotes < doubleQuotes;
+
+ StringBuffer sb = new StringBuffer();
+
+ for (int rune in value.runes) {
+ String escape = _irregularEscape(rune, useSingleQuotes);
+ if (escape != null) {
+ sb.write(escape);
+ continue;
+ }
+ if (rune == charCodes.$LS ||
+ rune == charCodes.$PS ||
+ _isUnpairedSurrogate(rune) ||
+ ascii && (rune < charCodes.$SPACE || rune >= charCodes.$DEL)) {
+ if (rune < 0x100) {
+ sb.write(r'\x');
+ sb.write(rune.toRadixString(16).padLeft(2, '0'));
+ } else if (rune < 0x10000) {
+ sb.write(r'\u');
+ sb.write(rune.toRadixString(16).padLeft(4, '0'));
+ } else {
+ // Not all browsers accept the ES6 \u{zzzzzz} encoding, so emit two
+ // surrogate pairs.
+ var bits = rune - 0x10000;
+ var leading = 0xD800 | (bits >> 10);
+ var trailing = 0xDC00 | (bits & 0x3ff);
+ sb.write(r'\u');
+ sb.write(leading.toRadixString(16));
+ sb.write(r'\u');
+ sb.write(trailing.toRadixString(16));
+ }
+ } else {
+ sb.writeCharCode(rune);
+ }
+ }
+
+ return finish(useSingleQuotes ? "'" : '"', sb.toString());
+ }
+
+ static bool _isUnpairedSurrogate(int code) => (code & 0xFFFFF800) == 0xD800;
+
+ static String _irregularEscape(int code, bool useSingleQuotes) {
+ switch (code) {
+ case charCodes.$SQ:
+ return useSingleQuotes ? r"\'" : r"'";
+ case charCodes.$DQ:
+ return useSingleQuotes ? r'"' : r'\"';
+ case charCodes.$BACKSLASH:
+ return r'\\';
+ case charCodes.$BS:
+ return r'\b';
+ case charCodes.$TAB:
+ return r'\t';
+ case charCodes.$LF:
+ return r'\n';
+ case charCodes.$VTAB:
+ return r'\v';
+ case charCodes.$FF:
+ return r'\f';
+ case charCodes.$CR:
+ return r'\r';
+ }
+ return null;
+ }
+
+ /// Creates a literal js string from [value].
+ ///
+ /// Note that this function only puts quotes around [value]. It does not do
+ /// any escaping, so use only when you can guarantee that [value] does not
+ /// contain newlines or backslashes. For escaping the string use
+ /// [escapedString].
+ LiteralString string(String value) => new LiteralString('"$value"');
/// Creates an instance of [LiteralString] from [value].
///
/// Does not add quotes or do any escaping.
LiteralString stringPart(String value) => new LiteralString(value);
- StringConcatenation concatenateStrings(Iterable<Literal> parts) {
- return StringConcatenation(List.of(parts, growable: false));
- }
-
- Iterable<Literal> joinLiterals(
- Iterable<Literal> items, Literal separator) sync* {
- bool first = true;
- for (final item in items) {
- if (!first) yield separator;
- yield item;
- first = false;
+ StringConcatenation concatenateStrings(Iterable<Literal> parts,
+ {addQuotes: false}) {
+ List<Literal> _parts;
+ if (addQuotes) {
+ Literal quote = stringPart('"');
+ _parts = <Literal>[quote]
+ ..addAll(parts)
+ ..add(quote);
+ } else {
+ _parts = new List.from(parts, growable: false);
}
+ return new StringConcatenation(_parts);
}
- LiteralString quoteName(Name name) {
- return LiteralStringFromName(name);
+ Iterable<Literal> joinLiterals(Iterable<Literal> list, Literal separator) {
+ return new _InterleaveIterable<Literal>(list, separator);
+ }
+
+ LiteralString quoteName(Name name, {allowNull: false}) {
+ if (name == null) {
+ assert(allowNull);
+ return new LiteralString('""');
+ }
+ return new LiteralStringFromName(name);
}
LiteralNumber number(num value) => new LiteralNumber('$value');
@@ -348,19 +505,18 @@
}
LiteralString string(String value) => js.string(value);
+LiteralString quoteName(Name name, {allowNull: false}) {
+ return js.quoteName(name, allowNull: allowNull);
+}
-/// Returns a LiteralString which has contents determined by [Name].
-///
-/// This is used to force a Name to be a string literal regardless of
-/// context. It is not necessary for properties.
-LiteralString quoteName(Name name) => js.quoteName(name);
-
+LiteralString stringPart(String value) => js.stringPart(value);
Iterable<Literal> joinLiterals(Iterable<Literal> list, Literal separator) {
return js.joinLiterals(list, separator);
}
-StringConcatenation concatenateStrings(Iterable<Literal> parts) {
- return js.concatenateStrings(parts);
+StringConcatenation concatenateStrings(Iterable<Literal> parts,
+ {addQuotes: false}) {
+ return js.concatenateStrings(parts, addQuotes: addQuotes);
}
LiteralNumber number(num value) => js.number(value);
@@ -580,7 +736,7 @@
'/': 5,
'%': 5
};
- static final UNARY_OPERATORS = {
+ static final UNARY_OPERATORS = [
'++',
'--',
'+',
@@ -591,7 +747,7 @@
'void',
'delete',
'await'
- };
+ ].toSet();
static final OPERATORS_THAT_LOOK_LIKE_IDENTIFIERS =
['typeof', 'void', 'delete', 'in', 'instanceof', 'await'].toSet();
@@ -601,7 +757,7 @@
return CATEGORIES[code];
}
- String getRegExp(int startPosition) {
+ String getDelimited(int startPosition) {
position = startPosition;
int delimiter = src.codeUnitAt(startPosition);
int currentCode;
@@ -618,7 +774,7 @@
escaped == charCodes.$u ||
escaped == charCodes.$U ||
category(escaped) == NUMERIC) {
- error('Numeric and hex escapes are not supported in RegExp literals');
+ error('Numeric and hex escapes are not allowed in literals');
}
}
} while (currentCode != delimiter);
@@ -626,46 +782,6 @@
return src.substring(lastPosition, position);
}
- String getString(int startPosition, int quote) {
- assert(src.codeUnitAt(startPosition) == quote);
- position = startPosition + 1;
- final value = StringBuffer();
- while (true) {
- if (position >= src.length) error("Unterminated literal");
- int code = src.codeUnitAt(position++);
- if (code == quote) break;
- if (code == charCodes.$LF) error("Unterminated literal");
- if (code == charCodes.$BACKSLASH) {
- if (position >= src.length) error("Unterminated literal");
- code = src.codeUnitAt(position++);
- if (code == charCodes.$f) {
- value.writeCharCode(12);
- } else if (code == charCodes.$n) {
- value.writeCharCode(10);
- } else if (code == charCodes.$r) {
- value.writeCharCode(13);
- } else if (code == charCodes.$t) {
- value.writeCharCode(8);
- } else if (code == charCodes.$BACKSLASH ||
- code == charCodes.$SQ ||
- code == charCodes.$DQ) {
- value.writeCharCode(code);
- } else if (code == charCodes.$x || code == charCodes.$X) {
- error('Hex escapes not supported in string literals');
- } else if (code == charCodes.$u || code == charCodes.$U) {
- error('Unicode escapes not supported in string literals');
- } else if (charCodes.$0 <= code && code <= charCodes.$9) {
- error('Numeric escapes not supported in string literals');
- } else {
- error('Unknown escape U+${code.toRadixString(16).padLeft(4, '0')}');
- }
- continue;
- }
- value.writeCharCode(code);
- }
- return value.toString();
- }
-
void getToken() {
skippedNewline = false;
for (;;) {
@@ -701,7 +817,7 @@
if (code == charCodes.$SQ || code == charCodes.$DQ) {
// String literal.
lastCategory = STRING;
- lastToken = getString(position, code);
+ lastToken = getDelimited(position);
} else if (code == charCodes.$0 &&
position + 2 < src.length &&
src.codeUnitAt(position + 1) == charCodes.$x) {
@@ -863,7 +979,7 @@
}
return new ArrayInitializer(values);
} else if (last != null && last.startsWith("/")) {
- String regexp = getRegExp(lastPosition);
+ String regexp = getDelimited(lastPosition);
getToken();
String flags = lastToken;
if (!acceptCategory(ALPHA)) flags = "";
@@ -937,12 +1053,12 @@
Literal propertyName;
String identifier = lastToken;
if (acceptCategory(ALPHA)) {
- propertyName = LiteralString(identifier);
+ propertyName = new LiteralString('"$identifier"');
} else if (acceptCategory(STRING)) {
- propertyName = LiteralString(identifier);
+ propertyName = new LiteralString(identifier);
} else if (acceptCategory(SYMBOL)) {
// e.g. void
- propertyName = LiteralString(identifier);
+ propertyName = new LiteralString('"$identifier"');
} else if (acceptCategory(HASH)) {
var nameOrPosition = parseHash();
InterpolatedLiteral interpolatedLiteral =
@@ -1458,3 +1574,40 @@
return new Catch(errorName, body);
}
}
+
+class _InterleaveIterator<T extends Node> implements Iterator<T> {
+ Iterator<T> source;
+ T separator;
+ bool isNextSeparator = false;
+ bool isInitialized = false;
+
+ _InterleaveIterator(this.source, this.separator);
+
+ bool moveNext() {
+ if (!isInitialized) {
+ isInitialized = true;
+ return source.moveNext();
+ } else if (isNextSeparator) {
+ isNextSeparator = false;
+ return true;
+ } else {
+ return isNextSeparator = source.moveNext();
+ }
+ }
+
+ T get current {
+ if (isNextSeparator) return separator;
+ return source.current;
+ }
+}
+
+class _InterleaveIterable<T extends Node> extends IterableBase<T> {
+ Iterable<T> source;
+ T separator;
+
+ _InterleaveIterable(this.source, this.separator);
+
+ Iterator<T> get iterator {
+ return new _InterleaveIterator<T>(source.iterator, separator);
+ }
+}
diff --git a/pkg/js_ast/lib/src/nodes.dart b/pkg/js_ast/lib/src/nodes.dart
index 285e918..903026e 100644
--- a/pkg/js_ast/lib/src/nodes.dart
+++ b/pkg/js_ast/lib/src/nodes.dart
@@ -1038,8 +1038,7 @@
@override
bool get isFinalized => name.isFinalized;
- @override
- String get value => name.name;
+ String get value => '"${name.name}"';
void visitChildren<T>(NodeVisitor<T> visitor) {
name.accept(visitor);
@@ -1372,8 +1371,6 @@
int get precedenceLevel => UNARY;
}
-RegExp _identifierRE = new RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
-
abstract class VariableReference extends Expression {
final String name;
@@ -1381,6 +1378,8 @@
assert(_identifierRE.hasMatch(name), "Non-identifier name '$name'");
}
+ static RegExp _identifierRE = new RegExp(r'^[A-Za-z_$][A-Za-z_$0-9]*$');
+
T accept<T>(NodeVisitor<T> visitor);
int get precedenceLevel => PRIMARY;
@@ -1521,10 +1520,10 @@
PropertyAccess(this.receiver, this.selector);
PropertyAccess.field(this.receiver, String fieldName)
- : selector = LiteralString(fieldName);
+ : selector = new LiteralString('"$fieldName"');
PropertyAccess.indexed(this.receiver, int index)
- : selector = LiteralNumber('$index');
+ : selector = new LiteralNumber('$index');
T accept<T>(NodeVisitor<T> visitor) => visitor.visitAccess(this);
@@ -1634,11 +1633,16 @@
class LiteralString extends Literal {
final String value;
- /// Constructs a LiteralString for a string containing the characters of
- /// `value`.
- ///
- /// When printed, the string will be escaped and quoted according to the
- /// printer's settings.
+ /**
+ * Constructs a LiteralString from a string value.
+ *
+ * The constructor does not add the required quotes. If [value] is not
+ * surrounded by quotes and properly escaped, the resulting object is invalid
+ * as a JS value.
+ *
+ * TODO(sra): Introduce variants for known valid strings that don't allocate a
+ * new string just to add quotes.
+ */
LiteralString(this.value);
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralString(this);
@@ -1646,39 +1650,17 @@
R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
visitor.visitLiteralString(this, arg);
- LiteralString _clone() => LiteralString(value);
-
- @override
- String toString() {
- final sb = StringBuffer('$runtimeType("');
- String end = '"';
- int count = 0;
- for (int rune in value.runes) {
- if (++count > 20) {
- end = '"...';
- break;
- }
- if (32 <= rune && rune < 127) {
- sb.writeCharCode(rune);
- } else {
- sb.write(r'\u{');
- sb.write(rune.toRadixString(16));
- sb.write(r'}');
- }
- }
- sb.write(end);
- sb.write(')');
- return sb.toString();
- }
+ LiteralString _clone() => new LiteralString(value);
}
class StringConcatenation extends Literal {
final List<Literal> parts;
- /// Constructs a StringConcatenation from a list of Literal elements.
- ///
- /// The constructor does not add surrounding quotes to the resulting
- /// concatenated string.
+ /**
+ * Constructs a StringConcatenation from a list of Literal elements.
+ * The constructor does not add surrounding quotes to the resulting
+ * concatenated string.
+ */
StringConcatenation(this.parts);
T accept<T>(NodeVisitor<T> visitor) => visitor.visitStringConcatenation(this);
diff --git a/pkg/js_ast/lib/src/printer.dart b/pkg/js_ast/lib/src/printer.dart
index ed165c2..c6e9a9e 100644
--- a/pkg/js_ast/lib/src/printer.dart
+++ b/pkg/js_ast/lib/src/printer.dart
@@ -5,16 +5,14 @@
part of js_ast;
class JavaScriptPrintingOptions {
- final bool utf8;
final bool shouldCompressOutput;
final bool minifyLocalVariables;
final bool preferSemicolonToNewlineInMinifiedOutput;
const JavaScriptPrintingOptions({
- this.utf8 = false,
- this.shouldCompressOutput = false,
- this.minifyLocalVariables = false,
- this.preferSemicolonToNewlineInMinifiedOutput = false,
+ this.shouldCompressOutput: false,
+ this.minifyLocalVariables: false,
+ this.preferSemicolonToNewlineInMinifiedOutput: false,
});
}
@@ -58,10 +56,11 @@
String getText() => buffer.toString();
}
-String DebugPrint(Node node, {bool utf8 = false}) {
- JavaScriptPrintingOptions options = JavaScriptPrintingOptions(utf8: utf8);
- SimpleJavaScriptPrintingContext context = SimpleJavaScriptPrintingContext();
- Printer printer = Printer(options, context);
+String DebugPrint(Node node) {
+ JavaScriptPrintingOptions options = new JavaScriptPrintingOptions();
+ SimpleJavaScriptPrintingContext context =
+ new SimpleJavaScriptPrintingContext();
+ Printer printer = new Printer(options, context);
printer.visit(node);
return context.getText();
}
@@ -84,8 +83,8 @@
// A cache of all indentation strings used so far.
List<String> _indentList = <String>[""];
- static final identifierCharacterRegExp = RegExp(r'^[a-zA-Z_0-9$]');
- static final expressionContinuationRegExp = RegExp(r'^[-+([]');
+ static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]');
+ static final expressionContinuationRegExp = new RegExp(r'^[-+([]');
Printer(JavaScriptPrintingOptions options, JavaScriptPrintingContext context)
: options = options,
@@ -727,10 +726,10 @@
if (value is This) return true;
if (value is LiteralNull) return true;
if (value is LiteralNumber) return true;
- if (value is LiteralString && value.value.length <= 6) return true;
+ if (value is LiteralString && value.value.length <= 8) return true;
if (value is ObjectInitializer && value.properties.isEmpty) return true;
if (value is ArrayInitializer && value.elements.isEmpty) return true;
- if (value is Name && value.name.length <= 6) return true;
+ if (value is Name && value.name.length <= 8) return true;
}
return false;
}
@@ -1019,24 +1018,24 @@
}
bool isValidJavaScriptId(String field) {
- if (field.length == 0) return false;
+ if (field.length < 3) return false;
// Ignore the leading and trailing string-delimiter.
- for (int i = 0; i < field.length; i++) {
+ for (int i = 1; i < field.length - 1; i++) {
// TODO(floitsch): allow more characters.
int charCode = field.codeUnitAt(i);
if (!(charCodes.$a <= charCode && charCode <= charCodes.$z ||
charCodes.$A <= charCode && charCode <= charCodes.$Z ||
charCode == charCodes.$$ ||
charCode == charCodes.$_ ||
- i > 0 && isDigit(charCode))) {
+ i != 1 && isDigit(charCode))) {
return false;
}
}
// TODO(floitsch): normally we should also check that the field is not a
// reserved word. We don't generate fields with reserved word names except
// for 'super'.
- if (field == 'super') return false;
- if (field == 'catch') return false;
+ if (field == '"super"') return false;
+ if (field == '"catch"') return false;
return true;
}
@@ -1044,41 +1043,35 @@
void visitAccess(PropertyAccess access) {
visitNestedExpression(access.receiver, CALL,
newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
-
Node selector = undefer(access.selector);
if (selector is LiteralString) {
- _dotString(access.selector, access.receiver, selector.value);
- return;
- } else if (selector is StringConcatenation) {
- _dotString(access.selector, access.receiver,
- _StringContentsCollector().collect(selector));
- return;
+ String fieldWithQuotes = selector.value;
+ if (isValidJavaScriptId(fieldWithQuotes)) {
+ if (access.receiver is LiteralNumber &&
+ lastCharCode != charCodes.$CLOSE_PAREN) {
+ out(" ", isWhitespace: true);
+ }
+ out(".");
+ startNode(access.selector);
+ out(fieldWithQuotes.substring(1, fieldWithQuotes.length - 1));
+ endNode(access.selector);
+ return;
+ }
} else if (selector is Name) {
- _dotString(access.selector, access.receiver, selector.name);
+ Node receiver = undefer(access.receiver);
+ if (receiver is LiteralNumber && lastCharCode != charCodes.$CLOSE_PAREN) {
+ out(" ", isWhitespace: true);
+ }
+ out(".");
+ startNode(access.selector);
+ selector.accept(this);
+ endNode(access.selector);
return;
}
-
- out('[');
+ out("[");
visitNestedExpression(access.selector, EXPRESSION,
newInForInit: false, newAtStatementBegin: false);
- out(']');
- }
-
- void _dotString(Node selector, Node receiver, String selectorValue) {
- if (isValidJavaScriptId(selectorValue)) {
- if (undefer(receiver) is LiteralNumber &&
- lastCharCode != charCodes.$CLOSE_PAREN) {
- out(' ', isWhitespace: true);
- }
- out('.');
- startNode(selector);
- out(selectorValue);
- endNode(selector);
- } else {
- out('[');
- _handleString(selectorValue);
- out(']');
- }
+ out("]");
}
@override
@@ -1140,25 +1133,12 @@
@override
void visitLiteralString(LiteralString node) {
- _handleString(node.value);
+ out(node.value);
}
@override
visitStringConcatenation(StringConcatenation node) {
- _handleString(_StringContentsCollector().collect(node));
- }
-
- void _handleString(String value) {
- final kind = StringToSource.analyze(value, utf8: options.utf8);
- out(kind.quote);
- if (kind.simple) {
- out(value);
- } else {
- final sb = StringBuffer();
- StringToSource.writeString(sb, value, kind, utf8: options.utf8);
- out(sb.toString());
- }
- out(kind.quote);
+ node.visitChildren(this);
}
@override
@@ -1255,15 +1235,18 @@
startNode(node.name);
Node name = undefer(node.name);
if (name is LiteralString) {
- _outPropertyName(name.value);
+ String text = name.value;
+ if (isValidJavaScriptId(text)) {
+ out(text.substring(1, text.length - 1));
+ } else {
+ out(text);
+ }
} else if (name is Name) {
- _outPropertyName(name.name);
- } else if (name is LiteralNumber) {
- out(name.value);
+ node.name.accept(this);
} else {
- // TODO(sra): Handle StringConcatenation.
- // TODO(sra): Handle general expressions, .e.g. `{[x]: 1}`.
- throw StateError('Unexpected Property name: $name');
+ assert(name is LiteralNumber);
+ LiteralNumber nameNumber = node.name;
+ out(nameNumber.value);
}
endNode(node.name);
out(":");
@@ -1272,14 +1255,6 @@
newInForInit: false, newAtStatementBegin: false);
}
- void _outPropertyName(String name) {
- if (isValidJavaScriptId(name)) {
- out(name);
- } else {
- _handleString(name);
- }
- }
-
@override
void visitRegExpLiteral(RegExpLiteral node) {
out(node.pattern);
@@ -1346,44 +1321,6 @@
}
}
-class _StringContentsCollector extends BaseVisitor<void> {
- final StringBuffer _buffer = StringBuffer();
-
- String collect(Node node) {
- node.accept(this);
- return _buffer.toString();
- }
-
- void _add(String value) {
- _buffer.write(value);
- }
-
- @override
- void visitNode(Node node) {
- throw StateError('Node should not be part of StringConcatenation: $node');
- }
-
- @override
- void visitLiteralString(LiteralString node) {
- _add(node.value);
- }
-
- @override
- void visitLiteralNumber(LiteralNumber node) {
- _add(node.value);
- }
-
- @override
- void visitName(Name node) {
- _add(node.name);
- }
-
- @override
- void visitStringConcatenation(StringConcatenation node) {
- node.visitChildren(this);
- }
-}
-
class OrderedSet<T> {
final Set<T> set;
final List<T> list;
diff --git a/pkg/js_ast/lib/src/strings.dart b/pkg/js_ast/lib/src/strings.dart
deleted file mode 100644
index 91c2bae..0000000
--- a/pkg/js_ast/lib/src/strings.dart
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2021, 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.
-
-// Utilities for converting between JavaScript source-code Strings and the
-// String value they represent.
-
-import 'characters.dart' as charCodes;
-
-class StringToSourceKind {
- /// [true] if preferable to use double quotes, [false] if preferable to use
- /// single quotes.
- final bool doubleQuotes;
-
- /// [true] if contents require no escaping with the preferred quoting.
- final bool simple;
-
- const StringToSourceKind({this.doubleQuotes, this.simple});
-
- String get quote => doubleQuotes ? '"' : "'";
-}
-
-class StringToSource {
- const StringToSource();
-
- static StringToSourceKind analyze(String value, {/*required*/ bool utf8}) {
- final ascii = !utf8;
- int singleQuotes = 0;
- int doubleQuotes = 0;
- int otherEscapes = 0;
- int unpairedSurrogates = 0;
-
- for (int rune in value.runes) {
- if (rune == charCodes.$BACKSLASH) {
- ++otherEscapes;
- } else if (rune == charCodes.$SQ) {
- ++singleQuotes;
- } else if (rune == charCodes.$DQ) {
- ++doubleQuotes;
- } else if (rune == charCodes.$LF ||
- rune == charCodes.$CR ||
- rune == charCodes.$LS ||
- rune == charCodes.$PS) {
- // Line terminators.
- ++otherEscapes;
- } else if (rune == charCodes.$BS ||
- rune == charCodes.$TAB ||
- rune == charCodes.$VTAB ||
- rune == charCodes.$FF) {
- ++otherEscapes;
- } else if (ascii && (rune < charCodes.$SPACE || rune >= charCodes.$DEL)) {
- ++otherEscapes;
- } else if (_isUnpairedSurrogate(rune)) {
- // Need to escape unpaired surrogates in a UTF8-encoded output otherwise
- // the output would be malformed.
- ++unpairedSurrogates;
- }
- }
-
- if (otherEscapes == 0 && unpairedSurrogates == 0) {
- if (doubleQuotes == 0) {
- return const StringToSourceKind(doubleQuotes: true, simple: true);
- }
- if (singleQuotes == 0) {
- return const StringToSourceKind(doubleQuotes: false, simple: true);
- }
- }
-
- return doubleQuotes <= singleQuotes
- ? const StringToSourceKind(doubleQuotes: true, simple: false)
- : const StringToSourceKind(doubleQuotes: false, simple: false);
- }
-
- static void writeString(
- StringBuffer sb, String string, StringToSourceKind kind,
- {/*required*/ bool utf8}) {
- for (int rune in string.runes) {
- String escape = _irregularEscape(rune, kind.doubleQuotes);
- if (escape != null) {
- sb.write(escape);
- continue;
- }
- if (rune == charCodes.$LS ||
- rune == charCodes.$PS ||
- _isUnpairedSurrogate(rune) ||
- !utf8 && (rune < charCodes.$SPACE || rune >= charCodes.$DEL)) {
- if (rune < 0x100) {
- sb.write(r'\x');
- sb.write(rune.toRadixString(16).padLeft(2, '0'));
- } else if (rune < 0x10000) {
- sb.write(r'\u');
- sb.write(rune.toRadixString(16).padLeft(4, '0'));
- } else {
- // Not all browsers accept the ES6 \u{zzzzzz} encoding, so emit two
- // surrogate pairs.
- var bits = rune - 0x10000;
- var leading = 0xD800 | (bits >> 10);
- var trailing = 0xDC00 | (bits & 0x3ff);
- sb.write(r'\u');
- sb.write(leading.toRadixString(16));
- sb.write(r'\u');
- sb.write(trailing.toRadixString(16));
- }
- } else {
- sb.writeCharCode(rune);
- }
- }
- }
-
- static bool _isUnpairedSurrogate(int code) => (code & 0xFFFFF800) == 0xD800;
-
- static String _irregularEscape(int code, bool useDoubleQuotes) {
- switch (code) {
- case charCodes.$SQ:
- return useDoubleQuotes ? r"'" : r"\'";
- case charCodes.$DQ:
- return useDoubleQuotes ? r'\"' : r'"';
- case charCodes.$BACKSLASH:
- return r'\\';
- case charCodes.$BS:
- return r'\b';
- case charCodes.$TAB:
- return r'\t';
- case charCodes.$LF:
- return r'\n';
- case charCodes.$VTAB:
- return r'\v';
- case charCodes.$FF:
- return r'\f';
- case charCodes.$CR:
- return r'\r';
- }
- return null;
- }
-}
diff --git a/pkg/js_ast/lib/src/template.dart b/pkg/js_ast/lib/src/template.dart
index 788e081..5aa45ba 100644
--- a/pkg/js_ast/lib/src/template.dart
+++ b/pkg/js_ast/lib/src/template.dart
@@ -268,7 +268,7 @@
return (arguments) {
var value = arguments[nameOrPosition];
if (value is Expression) return value;
- if (value is String) return LiteralString(value);
+ if (value is String) return new LiteralString('"$value"');
throw error(
'Interpolated value #$nameOrPosition is not a selector: $value');
};
diff --git a/pkg/js_ast/pubspec.yaml b/pkg/js_ast/pubspec.yaml
index c510415..7909c53 100644
--- a/pkg/js_ast/pubspec.yaml
+++ b/pkg/js_ast/pubspec.yaml
@@ -3,7 +3,7 @@
publish_to: none
environment:
- sdk: '>=2.10.0 <3.0.0'
+ sdk: '>=2.0.0 <3.0.0'
dev_dependencies:
expect:
diff --git a/pkg/js_ast/test/deferred_expression_test.dart b/pkg/js_ast/test/deferred_expression_test.dart
index afd9805..c91a151 100644
--- a/pkg/js_ast/test/deferred_expression_test.dart
+++ b/pkg/js_ast/test/deferred_expression_test.dart
@@ -7,15 +7,15 @@
main() {
Map<Expression, DeferredExpression> map = {};
- VariableUse variableUse = VariableUse('variable');
+ VariableUse variableUse = new VariableUse('variable');
DeferredExpression deferred =
- map[variableUse] = _DeferredExpression(variableUse);
- VariableUse variableUseAlias = VariableUse('variable');
- map[variableUseAlias] = _DeferredExpression(variableUseAlias);
+ map[variableUse] = new _DeferredExpression(variableUse);
+ VariableUse variableUseAlias = new VariableUse('variable');
+ map[variableUseAlias] = new _DeferredExpression(variableUseAlias);
- map[deferred] = _DeferredExpression(deferred);
- Literal literal = LiteralString('literal');
- map[literal] = _DeferredExpression(literal);
+ map[deferred] = new _DeferredExpression(deferred);
+ Literal literal = new LiteralString('"literal"');
+ map[literal] = new _DeferredExpression(literal);
test(map, '#', [variableUse], 'variable');
test(map, '#', [deferred], 'variable');
@@ -54,18 +54,18 @@
List<Expression> arguments, String expectedOutput) {
Expression directExpression =
js.expressionTemplateFor(template).instantiate(arguments);
- _Context directContext = _Context();
+ _Context directContext = new _Context();
Printer directPrinter =
- Printer(const JavaScriptPrintingOptions(), directContext);
+ new Printer(const JavaScriptPrintingOptions(), directContext);
directPrinter.visit(directExpression);
Expect.equals(expectedOutput, directContext.text);
Expression deferredExpression = js
.expressionTemplateFor(template)
.instantiate(arguments.map((e) => map[e]).toList());
- _Context deferredContext = _Context();
+ _Context deferredContext = new _Context();
Printer deferredPrinter =
- Printer(const JavaScriptPrintingOptions(), deferredContext);
+ new Printer(const JavaScriptPrintingOptions(), deferredContext);
deferredPrinter.visit(deferredExpression);
Expect.equals(expectedOutput, deferredContext.text);
@@ -121,7 +121,7 @@
}
class _Context implements JavaScriptPrintingContext {
- StringBuffer sb = StringBuffer();
+ StringBuffer sb = new StringBuffer();
List<String> errors = [];
Map<Node, int> enterPositions = {};
Map<Node, _Position> exitPositions = {};
@@ -140,7 +140,7 @@
void exitNode(
Node node, int startPosition, int endPosition, int closingPosition) {
exitPositions[node] =
- _Position(startPosition, endPosition, closingPosition);
+ new _Position(startPosition, endPosition, closingPosition);
Expect.equals(enterPositions[node], startPosition);
}
diff --git a/pkg/js_ast/test/printer_callback_test.dart b/pkg/js_ast/test/printer_callback_test.dart
index 153c41d..7d8fb30 100644
--- a/pkg/js_ast/test/printer_callback_test.dart
+++ b/pkg/js_ast/test/printer_callback_test.dart
@@ -170,6 +170,9 @@
String get key => name;
FixedName(this.name);
+
+ @override
+ int compareTo(other) => 0;
}
void check(TestCase testCase) {
diff --git a/pkg/js_ast/test/string_escape_test.dart b/pkg/js_ast/test/string_escape_test.dart
index 78bc758..9277ed6 100644
--- a/pkg/js_ast/test/string_escape_test.dart
+++ b/pkg/js_ast/test/string_escape_test.dart
@@ -12,9 +12,9 @@
const int $RCURLY = $CLOSE_CURLY_BRACKET;
void main() {
- check(input, expected, {bool utf8 = false}) {
- if (input is List) input = String.fromCharCodes(input);
- String actual = DebugPrint(js.string(input), utf8: utf8);
+ check(input, expected, {ascii: false, utf8: false}) {
+ if (input is List) input = new String.fromCharCodes(input);
+ String actual = js.escapedString(input, ascii: ascii, utf8: utf8).value;
if (expected is List) {
expect(actual.codeUnits, expected);
} else {
@@ -29,57 +29,79 @@
test('simple-escapes', () {
check([$BS], [$DQ, $BACKSLASH, $b, $DQ]);
+ check([$BS], [$DQ, $BACKSLASH, $b, $DQ], ascii: true);
check([$BS], [$DQ, $BACKSLASH, $b, $DQ], utf8: true);
check([$LF], [$DQ, $BACKSLASH, $n, $DQ]);
+ check([$LF], [$DQ, $BACKSLASH, $n, $DQ], ascii: true);
check([$LF], [$DQ, $BACKSLASH, $n, $DQ], utf8: true);
- check([$FF], [$DQ, $BACKSLASH, $f, $DQ]);
+ check([$FF], [$DQ, $FF, $DQ]);
+ check([$FF], [$DQ, $BACKSLASH, $f, $DQ], ascii: true);
check([$FF], [$DQ, $BACKSLASH, $f, $DQ], utf8: true);
check([$CR], [$DQ, $BACKSLASH, $r, $DQ]);
+ check([$CR], [$DQ, $BACKSLASH, $r, $DQ], ascii: true);
check([$CR], [$DQ, $BACKSLASH, $r, $DQ], utf8: true);
check([$TAB], [$DQ, $BACKSLASH, $t, $DQ]);
+ check([$TAB], [$DQ, $BACKSLASH, $t, $DQ], ascii: true);
check([$TAB], [$DQ, $BACKSLASH, $t, $DQ], utf8: true);
check([$VTAB], [$DQ, $BACKSLASH, $v, $DQ]);
+ check([$VTAB], [$DQ, $BACKSLASH, $v, $DQ], ascii: true);
check([$VTAB], [$DQ, $BACKSLASH, $v, $DQ], utf8: true);
});
test('unnamed-control-codes-escapes', () {
- check([0, 1, 2, 3], r'''"\x00\x01\x02\x03"''');
+ check([0, 1, 2, 3], [$DQ, 0, 1, 2, 3, $DQ]);
+ check([0, 1, 2, 3], r'''"\x00\x01\x02\x03"''', ascii: true);
check([0, 1, 2, 3], [$DQ, 0, 1, 2, 3, $DQ], utf8: true);
});
test('line-separator', () {
- check([$LS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $8, $DQ]);
+ // Legacy escaper is broken.
+ // check([$LS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $8, $DQ]);
+ check([$LS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $8, $DQ], ascii: true);
check([$LS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $8, $DQ], utf8: true);
});
test('page-separator', () {
- check([$PS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $9, $DQ]);
+ // Legacy escaper is broken.
+ // check([$PS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $9, $DQ]);
+ check([$PS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $9, $DQ], ascii: true);
check([$PS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $9, $DQ], utf8: true);
});
+ test('legacy-escaper-is-broken', () {
+ check([$LS], [$DQ, 0x2028, $DQ]);
+ check([$PS], [$DQ, 0x2029, $DQ]);
+ });
+
test('choose-quotes', () {
- check('"', [$SQ, $DQ, $SQ]);
- check("'", [$DQ, $SQ, $DQ]);
+ check('\'', [$DQ, $SQ, $DQ]);
+ check('"', [$SQ, $DQ, $SQ], ascii: true);
+ check("'", [$DQ, $SQ, $DQ], ascii: true);
+ // Legacy always double-quotes
+ check([$DQ, $DQ, $SQ], [$DQ, $BACKSLASH, $DQ, $BACKSLASH, $DQ, $SQ, $DQ]);
// Using single quotes saves us one backslash:
- check([$DQ, $DQ, $SQ], [$SQ, $DQ, $DQ, $BACKSLASH, $SQ, $SQ]);
- check([$DQ, $SQ, $SQ], [$DQ, $BACKSLASH, $DQ, $SQ, $SQ, $DQ]);
+ check([$DQ, $DQ, $SQ], [$SQ, $DQ, $DQ, $BACKSLASH, $SQ, $SQ], ascii: true);
+ check([$DQ, $SQ, $SQ], [$DQ, $BACKSLASH, $DQ, $SQ, $SQ, $DQ], ascii: true);
});
test('u1234', () {
- check('\u1234', [$DQ, $BACKSLASH, $u, $1, $2, $3, $4, $DQ]);
+ check('\u1234', [$DQ, 0x1234, $DQ]);
+ check('\u1234', [$DQ, $BACKSLASH, $u, $1, $2, $3, $4, $DQ], ascii: true);
check('\u1234', [$DQ, 0x1234, $DQ], utf8: true);
});
test('u12345', () {
+ check([0x12345], [$DQ, 55304, 57157, $DQ]);
// TODO: ES6 option:
//check([0x12345],
- // [$DQ, $BACKSLASH, $u, $LCURLY, $1, $2, $3, $4, $5, $RCURLY, $DQ]);
- check([0x12345], r'''"\ud808\udf45"''');
+ // [$DQ, $BACKSLASH, $u, $LCURLY, $1, $2, $3, $4, $5, $RCURLY, $DQ],
+ // ascii: true);
+ check([0x12345], r'''"\ud808\udf45"''', ascii: true);
check([
0x12345
], [
@@ -97,7 +119,7 @@
$4,
$5,
$DQ
- ]);
+ ], ascii: true);
check([0x12345], [$DQ, 55304, 57157, $DQ], utf8: true);
});
@@ -105,16 +127,21 @@
// (0xD834, 0xDD1E) = 0x1D11E
// Strings containing unpaired surrogates must be encoded to prevent
// problems with the utf8 file-level encoding.
- check([0xD834], [$DQ, $BACKSLASH, $u, $d, $8, $3, $4, $DQ]);
+ check([0xD834], [$DQ, 0xD834, $DQ]); // Legacy escapedString broken.
+ check([0xD834], [$DQ, $BACKSLASH, $u, $d, $8, $3, $4, $DQ], ascii: true);
check([0xD834], [$DQ, $BACKSLASH, $u, $d, $8, $3, $4, $DQ], utf8: true);
- check([0xDD1E], [$DQ, $BACKSLASH, $u, $d, $d, $1, $e, $DQ]);
+ check([0xDD1E], [$DQ, 0xDD1E, $DQ]); // Legacy escapedString broken.
+ check([0xDD1E], [$DQ, $BACKSLASH, $u, $d, $d, $1, $e, $DQ], ascii: true);
check([0xDD1E], [$DQ, $BACKSLASH, $u, $d, $d, $1, $e, $DQ], utf8: true);
- check([0xD834, $A], [$DQ, $BACKSLASH, $u, $d, $8, $3, $4, $A, $DQ]);
+ check([0xD834, $A], [$DQ, 0xD834, $A, $DQ]); // Legacy escapedString broken.
+ check([0xD834, $A], [$DQ, $BACKSLASH, $u, $d, $8, $3, $4, $A, $DQ],
+ ascii: true);
check([0xD834, $A], [$DQ, $BACKSLASH, $u, $d, $8, $3, $4, $A, $DQ],
utf8: true);
+ check([0xD834, 0xDD1E], [$DQ, 0xD834, 0xDD1E, $DQ]); // Legacy ok.
check([
0xD834,
0xDD1E
@@ -133,8 +160,8 @@
$1,
$e,
$DQ
- ]);
- check([0xD834, 0xDD1E], r'''"\ud834\udd1e"''');
+ ], ascii: true);
+ check([0xD834, 0xDD1E], r'''"\ud834\udd1e"''', ascii: true);
check([0xD834, 0xDD1E], [$DQ, 0xD834, 0xDD1E, $DQ], utf8: true);
});
}
diff --git a/tools/VERSION b/tools/VERSION
index 39a1334..c0a8ee3 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 140
+PRERELEASE 141
PRERELEASE_PATCH 0
\ No newline at end of file