Version 2.14.0-173.0.dev
Merge commit '7baeabe0e43b09a703b919906559d99752efb7a1' into 'dev'
diff --git a/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
index efa3e86c..b05b24d 100644
--- a/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/annotation_resolver.dart
@@ -43,7 +43,7 @@
if (elementAnnotationImpl == null) {
// Analyzer ignores annotations on "part of" directives.
assert(parent is PartDirective || parent is PartOfDirective);
- } else {
+ } else if (_resolver.shouldCloneAnnotations) {
elementAnnotationImpl.annotationAst = _createCloner().cloneNode(node);
}
}
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index b8101a1..5812ae5 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -253,6 +253,13 @@
/// always safe to use `.last` to examine the top of the stack.
final List<Expression?> _unfinishedNullShorts = [null];
+ /// During resolution we clone annotation ASTs into the element model.
+ /// But we should not do this during linking element models, moreover
+ /// currently by doing this we will lose elements for parameters of
+ /// generic function types.
+ /// TODO(scheglov) Stop cloning altogether.
+ bool shouldCloneAnnotations = true;
+
/// Initialize a newly created visitor to resolve the nodes in an AST node.
///
/// The [definingLibrary] is the element for the library containing the node
diff --git a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
index 09c4891..ef9cf58 100644
--- a/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_binary_reader.dart
@@ -5,9 +5,12 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/ast_factory.dart';
import 'package:analyzer/src/dart/ast/token.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/testing/ast_test_factory.dart';
import 'package:analyzer/src/generated/testing/token_factory.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
@@ -401,6 +404,17 @@
defaultValue,
);
+ var nonDefaultElement = parameter.declaredElement!;
+ var element = DefaultParameterElementImpl(
+ nonDefaultElement.name,
+ nonDefaultElement.nameOffset,
+ );
+ element.parameterKind = kind;
+ if (parameter is SimpleFormalParameterImpl) {
+ parameter.declaredElement = element;
+ }
+ element.type = nonDefaultElement.type;
+
return node;
}
@@ -600,7 +614,17 @@
formalParameters,
question: AstBinaryFlags.hasQuestion(flags) ? Tokens.QUESTION : null,
);
- node.type = _reader.readType();
+ var type = _reader.readRequiredType() as FunctionType;
+ node.type = type;
+
+ var element = GenericFunctionTypeElementImpl.forOffset(-1);
+ element.parameters = formalParameters.parameters
+ .map((parameter) => parameter.declaredElement!)
+ .toList();
+ element.returnType = returnType?.type ?? DynamicTypeImpl.instance;
+ element.type = type;
+ node.declaredElement = element;
+
return node;
}
@@ -999,8 +1023,14 @@
requiredKeyword:
AstBinaryFlags.isRequired(flags) ? Tokens.REQUIRED : null,
);
- _reader.readType(); // TODO(scheglov) actual type
+ var actualType = _reader.readRequiredType();
_reader.readByte(); // TODO(scheglov) inherits covariant
+
+ var element = ParameterElementImpl(identifier?.name ?? '', -1);
+ element.parameterKind = node.kind;
+ element.type = actualType;
+ node.declaredElement = element;
+
return node;
}
diff --git a/pkg/analyzer/lib/src/summary2/ast_resolver.dart b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
index 83100d7..da1447b 100644
--- a/pkg/analyzer/lib/src/summary2/ast_resolver.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_resolver.dart
@@ -56,6 +56,7 @@
: _featureSet = node.thisOrAncestorOfType<CompilationUnit>()!.featureSet;
void resolveAnnotation(AnnotationImpl node) {
+ _resolverVisitor.shouldCloneAnnotations = false;
node.accept(_resolutionVisitor);
node.accept(_variableResolverVisitor);
_prepareEnclosingDeclarations();
diff --git a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
index 701c255..1ed2d25 100644
--- a/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
+++ b/pkg/analyzer/test/src/summary/resolved_ast_printer.dart
@@ -694,11 +694,15 @@
}
@override
- void visitGenericFunctionType(GenericFunctionType node) {
+ void visitGenericFunctionType(covariant GenericFunctionTypeImpl node) {
_writeNextCodeLine(node);
_writeln('GenericFunctionType');
_withIndent(() {
var properties = _Properties();
+ properties.addGenericFunctionTypeElement(
+ 'declaredElement',
+ node.declaredElement,
+ );
properties.addToken('functionKeyword', node.functionKeyword);
properties.addNode('parameters', node.parameters);
properties.addNode('returnType', node.returnType);
@@ -1728,6 +1732,24 @@
}
}
+ void _writeGenericFunctionTypeElement(
+ String name,
+ GenericFunctionTypeElement? element,
+ ) {
+ _sink.write(_indent);
+ _sink.write('$name: ');
+ if (element == null) {
+ _sink.writeln('<null>');
+ } else {
+ _withIndent(() {
+ _sink.writeln('GenericFunctionTypeElement');
+ _writeParameterElements(element.parameters);
+ _writeType('returnType', element.returnType);
+ _writeType('type', element.type);
+ });
+ }
+ }
+
void _writeln(String line) {
_sink.writeln(line);
}
@@ -1766,6 +1788,33 @@
}
}
+ void _writeParameterElements(List<ParameterElement> parameters) {
+ _writelnWithIndent('parameters');
+ _withIndent(() {
+ for (var parameter in parameters) {
+ _writelnWithIndent(parameter.name);
+ _withIndent(() {
+ _writeParameterKind(parameter);
+ _writeType('type', parameter.type);
+ });
+ }
+ });
+ }
+
+ void _writeParameterKind(ParameterElement parameter) {
+ if (parameter.isOptionalNamed) {
+ _writelnWithIndent('kind: optional named');
+ } else if (parameter.isOptionalPositional) {
+ _writelnWithIndent('kind: optional positional');
+ } else if (parameter.isRequiredNamed) {
+ _writelnWithIndent('kind: required named');
+ } else if (parameter.isRequiredPositional) {
+ _writelnWithIndent('kind: required positional');
+ } else {
+ throw StateError('Unknown kind: $parameter');
+ }
+ }
+
void _writeProperties(_Properties container) {
var properties = container.properties;
properties.sort((a, b) => a.name.compareTo(b.name));
@@ -1822,6 +1871,17 @@
}
}
+class _GenericFunctionTypeElementProperty extends _Property {
+ final GenericFunctionTypeElement? element;
+
+ _GenericFunctionTypeElementProperty(String name, this.element) : super(name);
+
+ @override
+ void write(ResolvedAstPrinter printer) {
+ printer._writeGenericFunctionTypeElement(name, element);
+ }
+}
+
class _NodeListProperty extends _Property {
final NodeList nodeList;
@@ -1853,6 +1913,15 @@
);
}
+ void addGenericFunctionTypeElement(
+ String name,
+ GenericFunctionTypeElement? element,
+ ) {
+ properties.add(
+ _GenericFunctionTypeElementProperty(name, element),
+ );
+ }
+
void addNode(String name, AstNode? node) {
properties.add(
_NodeProperty(name, node),
diff --git a/pkg/analyzer/test/src/summary/resynthesize_common.dart b/pkg/analyzer/test/src/summary/resynthesize_common.dart
index 7074657..236dd23 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_common.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_common.dart
@@ -7933,6 +7933,443 @@
''');
}
+ test_genericFunction_asTypeArgument_ofAnnotation_class() async {
+ var library = await checkLibrary(r'''
+class A<T> {
+ const A();
+}
+
+@A<int Function(String a)>()
+class B {}
+''');
+ checkElementText(
+ library,
+ r'''
+class A {
+ const A();
+}
+ typeParameters
+ T
+ bound: null
+ defaultType: dynamic
+class B {
+}
+ metadata
+ Annotation
+ arguments: ArgumentList
+ element: ConstructorMember
+ base: self::@class::A::@constructor::•
+ substitution: {T: dynamic}
+ name: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ typeArguments: TypeArgumentList
+ arguments
+ GenericFunctionType
+ declaredElement: GenericFunctionTypeElement
+ parameters
+ a
+ kind: required positional
+ type: String
+ returnType: int
+ type: int Function(String)
+ functionKeyword: Function
+ parameters: FormalParameterList
+ parameters
+ SimpleFormalParameter
+ declaredElement: a@-1
+ declaredElementType: String
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::String
+ staticType: null
+ token: String
+ type: String
+ returnType: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::int
+ staticType: null
+ token: int
+ type: int
+ type: int Function(String)
+''',
+ withFullyResolvedAst: true);
+ }
+
+ test_genericFunction_asTypeArgument_ofAnnotation_topLevelVariable() async {
+ var library = await checkLibrary(r'''
+class A<T> {
+ const A();
+}
+
+@A<int Function(String a)>()
+var v = 0;
+''');
+ checkElementText(
+ library,
+ r'''
+class A {
+ const A();
+}
+ typeParameters
+ T
+ bound: null
+ defaultType: dynamic
+int v;
+ metadata
+ Annotation
+ arguments: ArgumentList
+ element: ConstructorMember
+ base: self::@class::A::@constructor::•
+ substitution: {T: dynamic}
+ name: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ typeArguments: TypeArgumentList
+ arguments
+ GenericFunctionType
+ declaredElement: GenericFunctionTypeElement
+ parameters
+ a
+ kind: required positional
+ type: String
+ returnType: int
+ type: int Function(String)
+ functionKeyword: Function
+ parameters: FormalParameterList
+ parameters
+ SimpleFormalParameter
+ declaredElement: a@-1
+ declaredElementType: String
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::String
+ staticType: null
+ token: String
+ type: String
+ returnType: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::int
+ staticType: null
+ token: int
+ type: int
+ type: int Function(String)
+''',
+ withFullyResolvedAst: true);
+ }
+
+ test_genericFunction_asTypeArgument_parameters_optionalNamed() async {
+ var library = await checkLibrary(r'''
+class A<T> {
+ const A();
+}
+
+const v = A<String Function({int? a})>();
+''');
+ checkElementText(
+ library,
+ r'''
+class A {
+ const A();
+}
+ typeParameters
+ T
+ bound: null
+ defaultType: dynamic
+const A<String Function({int? a})> v;
+ constantInitializer
+ InstanceCreationExpression
+ argumentList: ArgumentList
+ constructorName: ConstructorName
+ staticElement: ConstructorMember
+ base: self::@class::A::@constructor::•
+ substitution: {T: String Function({int? a})}
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ type: A<String Function({int? a})>
+ typeArguments: TypeArgumentList
+ arguments
+ GenericFunctionType
+ declaredElement: GenericFunctionTypeElement
+ parameters
+ a
+ kind: optional named
+ type: int?
+ returnType: String
+ type: String Function({int? a})
+ functionKeyword: Function
+ parameters: FormalParameterList
+ parameters
+ DefaultFormalParameter
+ declaredElement: a@-1
+ declaredElementType: int?
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ parameter: SimpleFormalParameter
+ declaredElement: a@-1
+ declaredElementType: int?
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::int
+ staticType: null
+ token: int
+ type: int?
+ returnType: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::String
+ staticType: null
+ token: String
+ type: String
+ type: String Function({int? a})
+ staticType: A<String Function({int? a})>
+''',
+ withFullyResolvedAst: true);
+ }
+
+ test_genericFunction_asTypeArgument_parameters_optionalPositional() async {
+ var library = await checkLibrary(r'''
+class A<T> {
+ const A();
+}
+
+const v = A<String Function([int? a])>();
+''');
+ checkElementText(
+ library,
+ r'''
+class A {
+ const A();
+}
+ typeParameters
+ T
+ bound: null
+ defaultType: dynamic
+const A<String Function([int?])> v;
+ constantInitializer
+ InstanceCreationExpression
+ argumentList: ArgumentList
+ constructorName: ConstructorName
+ staticElement: ConstructorMember
+ base: self::@class::A::@constructor::•
+ substitution: {T: String Function([int?])}
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ type: A<String Function([int?])>
+ typeArguments: TypeArgumentList
+ arguments
+ GenericFunctionType
+ declaredElement: GenericFunctionTypeElement
+ parameters
+ a
+ kind: optional positional
+ type: int?
+ returnType: String
+ type: String Function([int?])
+ functionKeyword: Function
+ parameters: FormalParameterList
+ parameters
+ DefaultFormalParameter
+ declaredElement: a@-1
+ declaredElementType: int?
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ parameter: SimpleFormalParameter
+ declaredElement: a@-1
+ declaredElementType: int?
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::int
+ staticType: null
+ token: int
+ type: int?
+ returnType: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::String
+ staticType: null
+ token: String
+ type: String
+ type: String Function([int?])
+ staticType: A<String Function([int?])>
+''',
+ withFullyResolvedAst: true);
+ }
+
+ test_genericFunction_asTypeArgument_parameters_requiredNamed() async {
+ var library = await checkLibrary(r'''
+class A<T> {
+ const A();
+}
+
+const v = A<String Function({required int a})>();
+''');
+ checkElementText(
+ library,
+ r'''
+class A {
+ const A();
+}
+ typeParameters
+ T
+ bound: null
+ defaultType: dynamic
+const A<String Function({required int a})> v;
+ constantInitializer
+ InstanceCreationExpression
+ argumentList: ArgumentList
+ constructorName: ConstructorName
+ staticElement: ConstructorMember
+ base: self::@class::A::@constructor::•
+ substitution: {T: String Function({required int a})}
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ type: A<String Function({required int a})>
+ typeArguments: TypeArgumentList
+ arguments
+ GenericFunctionType
+ declaredElement: GenericFunctionTypeElement
+ parameters
+ a
+ kind: required named
+ type: int
+ returnType: String
+ type: String Function({required int a})
+ functionKeyword: Function
+ parameters: FormalParameterList
+ parameters
+ DefaultFormalParameter
+ declaredElement: a@-1
+ declaredElementType: int
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ parameter: SimpleFormalParameter
+ declaredElement: a@-1
+ declaredElementType: int
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ requiredKeyword: required
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::int
+ staticType: null
+ token: int
+ type: int
+ returnType: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::String
+ staticType: null
+ token: String
+ type: String
+ type: String Function({required int a})
+ staticType: A<String Function({required int a})>
+''',
+ withFullyResolvedAst: true);
+ }
+
+ test_genericFunction_asTypeArgument_parameters_requiredPositional() async {
+ var library = await checkLibrary(r'''
+class A<T> {
+ const A();
+}
+
+const v = A<String Function(int a)>();
+''');
+ checkElementText(
+ library,
+ r'''
+class A {
+ const A();
+}
+ typeParameters
+ T
+ bound: null
+ defaultType: dynamic
+const A<String Function(int)> v;
+ constantInitializer
+ InstanceCreationExpression
+ argumentList: ArgumentList
+ constructorName: ConstructorName
+ staticElement: ConstructorMember
+ base: self::@class::A::@constructor::•
+ substitution: {T: String Function(int)}
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: self::@class::A
+ staticType: null
+ token: A
+ type: A<String Function(int)>
+ typeArguments: TypeArgumentList
+ arguments
+ GenericFunctionType
+ declaredElement: GenericFunctionTypeElement
+ parameters
+ a
+ kind: required positional
+ type: int
+ returnType: String
+ type: String Function(int)
+ functionKeyword: Function
+ parameters: FormalParameterList
+ parameters
+ SimpleFormalParameter
+ declaredElement: a@-1
+ declaredElementType: int
+ identifier: SimpleIdentifier
+ staticElement: <null>
+ staticType: null
+ token: a
+ type: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::int
+ staticType: null
+ token: int
+ type: int
+ returnType: TypeName
+ name: SimpleIdentifier
+ staticElement: dart:core::@class::String
+ staticType: null
+ token: String
+ type: String
+ type: String Function(int)
+ staticType: A<String Function(int)>
+''',
+ withFullyResolvedAst: true);
+ }
+
test_genericFunction_boundOf_typeParameter_ofMixin() async {
var library = await checkLibrary(r'''
mixin B<X extends void Function()> {}
diff --git a/pkg/compiler/lib/src/js/js.dart b/pkg/compiler/lib/src/js/js.dart
index b49abc0..9c8328e 100644
--- a/pkg/compiler/lib/src/js/js.dart
+++ b/pkg/compiler/lib/src/js/js.dart
@@ -157,7 +157,7 @@
}
}
}
- _cachedLiteral = js.escapedString(text);
+ _cachedLiteral = js.string(text);
}
return _cachedLiteral;
}
diff --git a/pkg/compiler/lib/src/js/size_estimator.dart b/pkg/compiler/lib/src/js/size_estimator.dart
index 8ecfd9e..24527ec6 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 < 3) return false;
+ if (field.length == 0) return false;
// Ignore the leading and trailing string-delimiter.
- for (int i = 1; i < field.length - 1; i++) {
+ for (int i = 0; i < field.length; 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 != 1 && isDigit(charCode))) {
+ i > 0 && 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,16 +776,17 @@
newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
Node selector = access.selector;
if (selector is LiteralString) {
- String fieldWithQuotes = literalStringToString(selector);
- if (isValidJavaScriptId(fieldWithQuotes)) {
+ String field = literalStringToString(selector);
+ if (isValidJavaScriptId(field)) {
if (access.receiver is LiteralNumber) {
// We can eliminate the space in some cases, but for simplicity we
// always assume it is necessary.
out(' '); // ' '
}
- // '.${fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)}'
- out('.${fieldWithQuotes.substring(1, fieldWithQuotes.length - 1)}');
+ // '.${field}'
+ out('.');
+ out(field);
return;
}
} else if (selector is Name) {
@@ -875,7 +876,9 @@
@override
void visitLiteralString(LiteralString node) {
+ out('"');
out(literalStringToString(node));
+ out('"');
}
@override
@@ -968,10 +971,12 @@
if (name is LiteralString) {
String text = literalStringToString(name);
if (isValidJavaScriptId(text)) {
- // '${text.substring(1, text.length - 1)}
- out('${text.substring(1, text.length - 1)}');
+ out(text);
} else {
- out(text); // '$text'
+ // Approximation to `_handleString(text)`.
+ out('"');
+ out(text);
+ out('"');
}
} 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 ec3928d..dd67c4a 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.escapedString(value, ascii: true);
+ return js.string(value);
}
return StringReference(constant);
}
@@ -288,8 +288,7 @@
}
// Keys in literal maps must be emitted in place.
- jsAst.Literal keyExpression =
- js.escapedString(key.stringValue, ascii: true);
+ jsAst.Literal keyExpression = js.string(key.stringValue);
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 3c9f11f..4c59ced 100644
--- a/pkg/compiler/lib/src/js_backend/runtime_types_new.dart
+++ b/pkg/compiler/lib/src/js_backend/runtime_types_new.dart
@@ -109,9 +109,7 @@
return _finishEncoding(js.string(String.fromCharCodes(_codes)));
}
_flushCodes();
- jsAst.LiteralString quote = jsAst.LiteralString('"');
- return _finishEncoding(
- jsAst.StringConcatenation([quote, ..._fragments, quote]));
+ return _finishEncoding(jsAst.StringConcatenation(_fragments));
}
void _start(TypeRecipe recipe) {
@@ -487,13 +485,13 @@
CommonElements get _commonElements => _dartTypes.commonElements;
ClassEntity get _objectClass => _commonElements.objectClass;
- 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("'");
+ 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('"');
bool _isObject(InterfaceType type) => identical(type.element, _objectClass);
@@ -522,28 +520,32 @@
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([
- js.quoteName(_emitter.typeAccessNewRti(redirection.key)),
+ _doubleQuote,
+ _emitter.typeAccessNewRti(redirection.key),
+ _doubleQuote,
_colon,
- js.quoteName(_emitter.typeAccessNewRti(redirection.value)),
+ _doubleQuote,
+ _emitter.typeAccessNewRti(redirection.value),
+ _doubleQuote,
]);
jsAst.StringConcatenation _encodeEntry(
MapEntry<InterfaceType, _RulesetEntry> entry) =>
js.concatenateStrings([
- js.quoteName(_emitter.typeAccessNewRti(entry.key.element)),
+ _doubleQuote,
+ _emitter.typeAccessNewRti(entry.key.element),
+ _doubleQuote,
_colon,
_leftBrace,
...js.joinLiterals([
@@ -558,7 +560,9 @@
jsAst.StringConcatenation _encodeSupertype(
InterfaceType targetType, InterfaceType supertype) =>
js.concatenateStrings([
- js.quoteName(_emitter.typeAccessNewRti(supertype.element)),
+ _doubleQuote,
+ _emitter.typeAccessNewRti(supertype.element),
+ _doubleQuote,
_colon,
_leftBracket,
...js.joinLiterals(
@@ -571,30 +575,36 @@
jsAst.StringConcatenation _encodeTypeVariable(InterfaceType targetType,
TypeVariableType typeVariable, DartType supertypeArgument) =>
js.concatenateStrings([
- js.quoteName(_emitter.typeVariableAccessNewRti(typeVariable.element)),
+ _doubleQuote,
+ _emitter.typeVariableAccessNewRti(typeVariable.element),
+ _doubleQuote,
_colon,
_encodeSupertypeArgument(targetType, supertypeArgument),
]);
jsAst.Literal _encodeSupertypeArgument(
InterfaceType targetType, DartType supertypeArgument) =>
- _recipeEncoder.encodeMetadataRecipe(
- _emitter, targetType, supertypeArgument);
+ js.concatenateStrings([
+ _doubleQuote,
+ _recipeEncoder.encodeMetadataRecipe(
+ _emitter, targetType, supertypeArgument),
+ _doubleQuote
+ ]);
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([
- js.quoteName(_emitter.typeAccessNewRti(entry.key)),
+ _doubleQuote,
+ _emitter.typeAccessNewRti(entry.key),
+ _doubleQuote,
_colon,
js.number(entry.value),
]);
@@ -602,20 +612,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([
- js.quoteName(_emitter.typeAccessNewRti(classEntry.key)),
+ _doubleQuote,
+ _emitter.typeAccessNewRti(classEntry.key),
+ _doubleQuote,
_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 05e1b8f..9fc5044 100644
--- a/pkg/compiler/lib/src/js_backend/string_reference.dart
+++ b/pkg/compiler/lib/src/js_backend/string_reference.dart
@@ -256,8 +256,7 @@
for (_ReferenceSet referenceSet in _referencesByString.values) {
if (referenceSet.generateAtUse) {
StringConstantValue constant = referenceSet.constant;
- js.Expression reference =
- js.js.escapedString(constant.stringValue, ascii: true);
+ js.Expression reference = js.string(constant.stringValue);
for (StringReference ref in referenceSet._references) {
ref.value = reference;
}
@@ -275,8 +274,7 @@
for (_ReferenceSet referenceSet in referenceSetsUsingProperties) {
String string = referenceSet.constant.stringValue;
var propertyName = js.string(referenceSet.propertyName);
- properties.add(
- js.Property(propertyName, js.js.escapedString(string, ascii: true)));
+ properties.add(js.Property(propertyName, js.string(string)));
var access = js.js('#.#', [holderLocalName, propertyName]);
for (StringReference ref in referenceSet._references) {
ref.value = access;
@@ -439,4 +437,10 @@
visitNode(node);
}
}
+
+ @override
+ void visitLiteralString(js.LiteralString node) {
+ // [js.LiteralString] and [js.LiteralStringFromName] do not contain embedded
+ // [StringReference] or [StringReferenceResource] nodes.
+ }
}
diff --git a/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart
index 0acdc0b..819b321 100644
--- a/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart
+++ b/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart
@@ -328,9 +328,8 @@
// Function.apply calls the function with no type arguments, so generic
// methods need the stub to default the type arguments.
// This has to be the first stub.
- Selector namedSelector = new Selector.fromElement(member).toNonGeneric();
- Selector closureSelector =
- namedSelector.isClosureCall ? null : namedSelector.toCallSelector();
+ Selector namedSelector = Selector.fromElement(member).toNonGeneric();
+ Selector closureSelector = namedSelector.toCallSelector();
renamedCallSelectors.add(namedSelector);
stubSelectors.add(namedSelector);
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index f84c045..bea472a 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -1400,14 +1400,20 @@
// The first entry in the funsOrNames-array must be a string.
funsOrNames.add(js.quoteName(method.name));
for (ParameterStubMethod stubMethod in method.parameterStubs) {
- callNames.add(stubMethod.callName);
- addFunOrName(stubMethod);
+ js.Name callName = stubMethod.callName;
+ // `callName` might be `null` if the method is called directly with some
+ // CallStructure but it can be proven that the tearoff not called with
+ // with that CallStructure, e.g. the closure does no need the defaulting
+ // of arguments but some direct call does.
+ if (callName != null) {
+ callNames.add(callName);
+ addFunOrName(stubMethod);
+ }
}
- js.ArrayInitializer callNameArray =
- new js.ArrayInitializer(callNames.map(js.quoteName).toList());
- js.ArrayInitializer funsOrNamesArray =
- new js.ArrayInitializer(funsOrNames);
+ final callNameArray =
+ js.ArrayInitializer([...callNames.map(js.quoteName)]);
+ final funsOrNamesArray = js.ArrayInitializer(funsOrNames);
bool isIntercepted = false;
if (method is InstanceMethod) {
@@ -1415,7 +1421,7 @@
}
int requiredParameterCount = method.requiredParameterCount;
- js.Expression optionalParameterDefaultValues = new js.LiteralNull();
+ js.Expression optionalParameterDefaultValues = js.LiteralNull();
if (method.canBeApplied) {
optionalParameterDefaultValues =
_encodeOptionalParameterDefaultValues(method);
diff --git a/pkg/compiler/test/codegen/jsarray_indexof_test.dart b/pkg/compiler/test/codegen/jsarray_indexof_test.dart
index 4b75ecd..b567137 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 f7a1537..ba7376d 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.substring(1, selector.value.length - 1);
+ name = selector.value;
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.substring(1, selector.value.length - 1);
+ name = selector.value;
}
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.substring(1, selector.value.length - 1);
+ name = selector.value;
}
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 7e7ac3f..e9b4822 100644
--- a/pkg/compiler/test/js/js_parser_test.dart
+++ b/pkg/compiler/test/js/js_parser_test.dart
@@ -20,7 +20,9 @@
testError(String expression, [String expect = ""]) {
bool doCheck(exception) {
- Expect.isTrue(exception.toString().contains(expect));
+ final exceptionText = '$exception';
+ Expect.isTrue(exceptionText.contains(expect),
+ 'Missing "$expect" in "$exceptionText"');
return true;
}
@@ -65,9 +67,9 @@
// String literal with \n.
testExpression(r'var x = "\n"');
// String literal with escaped quote.
- testExpression(r'var x = "\""');
+ testExpression(r'''var x = "\""''', r"""var x = '"'""");
// *No clever escapes.
- testError(r'var x = "\x42"', 'escapes are not allowed in literals');
+ testError(r'var x = "\x42"', 'Hex escapes not supported');
// Operator new.
testExpression('new Foo()');
// New with dotted access.
@@ -168,7 +170,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 57b911f..2dacebb 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 01b0dd9..1958f19 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 d741180..6f2f1fa 100644
--- a/pkg/js_ast/lib/src/builder.dart
+++ b/pkg/js_ast/lib/src/builder.dart
@@ -296,186 +296,24 @@
}
/// Creates a literal js string from [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;
+ LiteralString string(String value) => LiteralString(value);
+
+ StringConcatenation concatenateStrings(Iterable<Literal> parts) {
+ return StringConcatenation(List.of(parts, growable: false));
}
- /// 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;
- }
- }
+ 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;
}
-
- 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,
- {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);
- }
-
- 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);
+ LiteralString quoteName(Name name) {
+ return LiteralStringFromName(name);
}
LiteralNumber number(num value) => new LiteralNumber('$value');
@@ -505,18 +343,19 @@
}
LiteralString string(String value) => js.string(value);
-LiteralString quoteName(Name name, {allowNull: false}) {
- return js.quoteName(name, allowNull: allowNull);
-}
-LiteralString stringPart(String value) => js.stringPart(value);
+/// 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);
+
Iterable<Literal> joinLiterals(Iterable<Literal> list, Literal separator) {
return js.joinLiterals(list, separator);
}
-StringConcatenation concatenateStrings(Iterable<Literal> parts,
- {addQuotes: false}) {
- return js.concatenateStrings(parts, addQuotes: addQuotes);
+StringConcatenation concatenateStrings(Iterable<Literal> parts) {
+ return js.concatenateStrings(parts);
}
LiteralNumber number(num value) => js.number(value);
@@ -763,7 +602,7 @@
return CATEGORIES[code];
}
- String getDelimited(int startPosition) {
+ String getRegExp(int startPosition) {
position = startPosition;
int delimiter = src.codeUnitAt(startPosition);
int currentCode;
@@ -780,7 +619,7 @@
escaped == charCodes.$u ||
escaped == charCodes.$U ||
category(escaped) == NUMERIC) {
- error('Numeric and hex escapes are not allowed in literals');
+ error('Numeric and hex escapes are not supported in RegExp literals');
}
}
} while (currentCode != delimiter);
@@ -788,6 +627,46 @@
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 (;;) {
@@ -823,7 +702,7 @@
if (code == charCodes.$SQ || code == charCodes.$DQ) {
// String literal.
lastCategory = STRING;
- lastToken = getDelimited(position);
+ lastToken = getString(position, code);
} else if (code == charCodes.$0 &&
position + 2 < src.length &&
src.codeUnitAt(position + 1) == charCodes.$x) {
@@ -985,7 +864,7 @@
}
return new ArrayInitializer(values);
} else if (last != null && last.startsWith("/")) {
- String regexp = getDelimited(lastPosition);
+ String regexp = getRegExp(lastPosition);
getToken();
String flags = lastToken;
if (!acceptCategory(ALPHA)) flags = "";
@@ -1059,12 +938,12 @@
Literal propertyName;
String identifier = lastToken;
if (acceptCategory(ALPHA)) {
- propertyName = new LiteralString('"$identifier"');
+ propertyName = LiteralString(identifier);
} else if (acceptCategory(STRING)) {
- propertyName = new LiteralString(identifier);
+ propertyName = LiteralString(identifier);
} else if (acceptCategory(SYMBOL)) {
// e.g. void
- propertyName = new LiteralString('"$identifier"');
+ propertyName = LiteralString(identifier);
} else if (acceptCategory(HASH)) {
var nameOrPosition = parseHash();
InterpolatedLiteral interpolatedLiteral =
@@ -1580,40 +1459,3 @@
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 903026e..c90dc08 100644
--- a/pkg/js_ast/lib/src/nodes.dart
+++ b/pkg/js_ast/lib/src/nodes.dart
@@ -1031,14 +1031,17 @@
}
class LiteralStringFromName extends LiteralString {
- Name name;
+ final Name name;
- LiteralStringFromName(this.name) : super(null);
+ LiteralStringFromName(this.name) : super(null) {
+ ArgumentError.checkNotNull(name, 'name');
+ }
@override
bool get isFinalized => name.isFinalized;
- String get value => '"${name.name}"';
+ @override
+ String get value => name.name;
void visitChildren<T>(NodeVisitor<T> visitor) {
name.accept(visitor);
@@ -1371,6 +1374,8 @@
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;
@@ -1378,8 +1383,6 @@
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;
@@ -1520,10 +1523,10 @@
PropertyAccess(this.receiver, this.selector);
PropertyAccess.field(this.receiver, String fieldName)
- : selector = new LiteralString('"$fieldName"');
+ : selector = LiteralString(fieldName);
PropertyAccess.indexed(this.receiver, int index)
- : selector = new LiteralNumber('$index');
+ : selector = LiteralNumber('$index');
T accept<T>(NodeVisitor<T> visitor) => visitor.visitAccess(this);
@@ -1633,16 +1636,11 @@
class LiteralString extends Literal {
final String value;
- /**
- * 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.
- */
+ /// 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.
LiteralString(this.value);
T accept<T>(NodeVisitor<T> visitor) => visitor.visitLiteralString(this);
@@ -1650,17 +1648,39 @@
R accept1<R, A>(NodeVisitor1<R, A> visitor, A arg) =>
visitor.visitLiteralString(this, arg);
- LiteralString _clone() => new LiteralString(value);
+ 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();
+ }
}
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 c6e9a9e..ed165c2 100644
--- a/pkg/js_ast/lib/src/printer.dart
+++ b/pkg/js_ast/lib/src/printer.dart
@@ -5,14 +5,16 @@
part of js_ast;
class JavaScriptPrintingOptions {
+ final bool utf8;
final bool shouldCompressOutput;
final bool minifyLocalVariables;
final bool preferSemicolonToNewlineInMinifiedOutput;
const JavaScriptPrintingOptions({
- this.shouldCompressOutput: false,
- this.minifyLocalVariables: false,
- this.preferSemicolonToNewlineInMinifiedOutput: false,
+ this.utf8 = false,
+ this.shouldCompressOutput = false,
+ this.minifyLocalVariables = false,
+ this.preferSemicolonToNewlineInMinifiedOutput = false,
});
}
@@ -56,11 +58,10 @@
String getText() => buffer.toString();
}
-String DebugPrint(Node node) {
- JavaScriptPrintingOptions options = new JavaScriptPrintingOptions();
- SimpleJavaScriptPrintingContext context =
- new SimpleJavaScriptPrintingContext();
- Printer printer = new Printer(options, context);
+String DebugPrint(Node node, {bool utf8 = false}) {
+ JavaScriptPrintingOptions options = JavaScriptPrintingOptions(utf8: utf8);
+ SimpleJavaScriptPrintingContext context = SimpleJavaScriptPrintingContext();
+ Printer printer = Printer(options, context);
printer.visit(node);
return context.getText();
}
@@ -83,8 +84,8 @@
// A cache of all indentation strings used so far.
List<String> _indentList = <String>[""];
- static final identifierCharacterRegExp = new RegExp(r'^[a-zA-Z_0-9$]');
- static final expressionContinuationRegExp = new RegExp(r'^[-+([]');
+ static final identifierCharacterRegExp = RegExp(r'^[a-zA-Z_0-9$]');
+ static final expressionContinuationRegExp = RegExp(r'^[-+([]');
Printer(JavaScriptPrintingOptions options, JavaScriptPrintingContext context)
: options = options,
@@ -726,10 +727,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 <= 8) return true;
+ if (value is LiteralString && value.value.length <= 6) 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 <= 8) return true;
+ if (value is Name && value.name.length <= 6) return true;
}
return false;
}
@@ -1018,24 +1019,24 @@
}
bool isValidJavaScriptId(String field) {
- if (field.length < 3) return false;
+ if (field.length == 0) return false;
// Ignore the leading and trailing string-delimiter.
- for (int i = 1; i < field.length - 1; i++) {
+ for (int i = 0; i < field.length; 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 != 1 && isDigit(charCode))) {
+ i > 0 && 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;
}
@@ -1043,35 +1044,41 @@
void visitAccess(PropertyAccess access) {
visitNestedExpression(access.receiver, CALL,
newInForInit: inForInit, newAtStatementBegin: atStatementBegin);
+
Node selector = undefer(access.selector);
if (selector is LiteralString) {
- 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;
- }
+ _dotString(access.selector, access.receiver, selector.value);
+ return;
+ } else if (selector is StringConcatenation) {
+ _dotString(access.selector, access.receiver,
+ _StringContentsCollector().collect(selector));
+ return;
} else if (selector is 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);
+ _dotString(access.selector, access.receiver, selector.name);
return;
}
- out("[");
+
+ out('[');
visitNestedExpression(access.selector, EXPRESSION,
newInForInit: false, newAtStatementBegin: false);
- out("]");
+ 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(']');
+ }
}
@override
@@ -1133,12 +1140,25 @@
@override
void visitLiteralString(LiteralString node) {
- out(node.value);
+ _handleString(node.value);
}
@override
visitStringConcatenation(StringConcatenation node) {
- node.visitChildren(this);
+ _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);
}
@override
@@ -1235,18 +1255,15 @@
startNode(node.name);
Node name = undefer(node.name);
if (name is LiteralString) {
- String text = name.value;
- if (isValidJavaScriptId(text)) {
- out(text.substring(1, text.length - 1));
- } else {
- out(text);
- }
+ _outPropertyName(name.value);
} else if (name is Name) {
- node.name.accept(this);
+ _outPropertyName(name.name);
+ } else if (name is LiteralNumber) {
+ out(name.value);
} else {
- assert(name is LiteralNumber);
- LiteralNumber nameNumber = node.name;
- out(nameNumber.value);
+ // TODO(sra): Handle StringConcatenation.
+ // TODO(sra): Handle general expressions, .e.g. `{[x]: 1}`.
+ throw StateError('Unexpected Property name: $name');
}
endNode(node.name);
out(":");
@@ -1255,6 +1272,14 @@
newInForInit: false, newAtStatementBegin: false);
}
+ void _outPropertyName(String name) {
+ if (isValidJavaScriptId(name)) {
+ out(name);
+ } else {
+ _handleString(name);
+ }
+ }
+
@override
void visitRegExpLiteral(RegExpLiteral node) {
out(node.pattern);
@@ -1321,6 +1346,44 @@
}
}
+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
new file mode 100644
index 0000000..91c2bae
--- /dev/null
+++ b/pkg/js_ast/lib/src/strings.dart
@@ -0,0 +1,135 @@
+// 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 5aa45ba..788e081 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 new LiteralString('"$value"');
+ if (value is String) return LiteralString(value);
throw error(
'Interpolated value #$nameOrPosition is not a selector: $value');
};
diff --git a/pkg/js_ast/test/deferred_expression_test.dart b/pkg/js_ast/test/deferred_expression_test.dart
index c91a151..afd9805 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 = new VariableUse('variable');
+ VariableUse variableUse = VariableUse('variable');
DeferredExpression deferred =
- map[variableUse] = new _DeferredExpression(variableUse);
- VariableUse variableUseAlias = new VariableUse('variable');
- map[variableUseAlias] = new _DeferredExpression(variableUseAlias);
+ map[variableUse] = _DeferredExpression(variableUse);
+ VariableUse variableUseAlias = VariableUse('variable');
+ map[variableUseAlias] = _DeferredExpression(variableUseAlias);
- map[deferred] = new _DeferredExpression(deferred);
- Literal literal = new LiteralString('"literal"');
- map[literal] = new _DeferredExpression(literal);
+ map[deferred] = _DeferredExpression(deferred);
+ Literal literal = LiteralString('literal');
+ map[literal] = _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 = new _Context();
+ _Context directContext = _Context();
Printer directPrinter =
- new Printer(const JavaScriptPrintingOptions(), directContext);
+ 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 = new _Context();
+ _Context deferredContext = _Context();
Printer deferredPrinter =
- new Printer(const JavaScriptPrintingOptions(), deferredContext);
+ Printer(const JavaScriptPrintingOptions(), deferredContext);
deferredPrinter.visit(deferredExpression);
Expect.equals(expectedOutput, deferredContext.text);
@@ -121,7 +121,7 @@
}
class _Context implements JavaScriptPrintingContext {
- StringBuffer sb = new StringBuffer();
+ StringBuffer sb = 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] =
- new _Position(startPosition, endPosition, closingPosition);
+ _Position(startPosition, endPosition, closingPosition);
Expect.equals(enterPositions[node], startPosition);
}
diff --git a/pkg/js_ast/test/string_escape_test.dart b/pkg/js_ast/test/string_escape_test.dart
index 9277ed6..78bc758 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, {ascii: false, utf8: false}) {
- if (input is List) input = new String.fromCharCodes(input);
- String actual = js.escapedString(input, ascii: ascii, utf8: utf8).value;
+ check(input, expected, {bool utf8 = false}) {
+ if (input is List) input = String.fromCharCodes(input);
+ String actual = DebugPrint(js.string(input), utf8: utf8);
if (expected is List) {
expect(actual.codeUnits, expected);
} else {
@@ -29,79 +29,57 @@
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, $FF, $DQ]);
- check([$FF], [$DQ, $BACKSLASH, $f, $DQ], ascii: true);
+ check([$FF], [$DQ, $BACKSLASH, $f, $DQ]);
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], [$DQ, 0, 1, 2, 3, $DQ]);
- check([0, 1, 2, 3], r'''"\x00\x01\x02\x03"''', ascii: true);
+ check([0, 1, 2, 3], r'''"\x00\x01\x02\x03"''');
check([0, 1, 2, 3], [$DQ, 0, 1, 2, 3, $DQ], utf8: true);
});
test('line-separator', () {
- // 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]);
check([$LS], [$DQ, $BACKSLASH, $u, $2, $0, $2, $8, $DQ], utf8: true);
});
test('page-separator', () {
- // 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]);
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('\'', [$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]);
+ check('"', [$SQ, $DQ, $SQ]);
+ check("'", [$DQ, $SQ, $DQ]);
// Using single quotes saves us one backslash:
- check([$DQ, $DQ, $SQ], [$SQ, $DQ, $DQ, $BACKSLASH, $SQ, $SQ], ascii: true);
- check([$DQ, $SQ, $SQ], [$DQ, $BACKSLASH, $DQ, $SQ, $SQ, $DQ], ascii: true);
+ check([$DQ, $DQ, $SQ], [$SQ, $DQ, $DQ, $BACKSLASH, $SQ, $SQ]);
+ check([$DQ, $SQ, $SQ], [$DQ, $BACKSLASH, $DQ, $SQ, $SQ, $DQ]);
});
test('u1234', () {
- check('\u1234', [$DQ, 0x1234, $DQ]);
- check('\u1234', [$DQ, $BACKSLASH, $u, $1, $2, $3, $4, $DQ], ascii: true);
+ check('\u1234', [$DQ, $BACKSLASH, $u, $1, $2, $3, $4, $DQ]);
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],
- // ascii: true);
- check([0x12345], r'''"\ud808\udf45"''', ascii: true);
+ // [$DQ, $BACKSLASH, $u, $LCURLY, $1, $2, $3, $4, $5, $RCURLY, $DQ]);
+ check([0x12345], r'''"\ud808\udf45"''');
check([
0x12345
], [
@@ -119,7 +97,7 @@
$4,
$5,
$DQ
- ], ascii: true);
+ ]);
check([0x12345], [$DQ, 55304, 57157, $DQ], utf8: true);
});
@@ -127,21 +105,16 @@
// (0xD834, 0xDD1E) = 0x1D11E
// Strings containing unpaired surrogates must be encoded to prevent
// problems with the utf8 file-level encoding.
- 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]);
check([0xD834], [$DQ, $BACKSLASH, $u, $d, $8, $3, $4, $DQ], utf8: true);
- 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]);
check([0xDD1E], [$DQ, $BACKSLASH, $u, $d, $d, $1, $e, $DQ], utf8: true);
- 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]);
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
@@ -160,8 +133,8 @@
$1,
$e,
$DQ
- ], ascii: true);
- check([0xD834, 0xDD1E], r'''"\ud834\udd1e"''', ascii: true);
+ ]);
+ check([0xD834, 0xDD1E], r'''"\ud834\udd1e"''');
check([0xD834, 0xDD1E], [$DQ, 0xD834, 0xDD1E, $DQ], utf8: true);
});
}
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index d2807a7a4..3e587e3 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -9,7 +9,6 @@
import 'package:dart2js_tools/deobfuscate_stack_trace.dart';
import 'package:status_file/expectation.dart';
-import 'package:test_runner/src/repository.dart';
import 'package:test_runner/src/static_error.dart';
import 'browser_controller.dart';
@@ -461,67 +460,53 @@
/// A parsed analyzer error diagnostic.
class AnalyzerError implements Comparable<AnalyzerError> {
/// Parses all errors from analyzer [stderr] output.
- static Iterable<AnalyzerError> parseStderr(String stderr) sync* {
- for (var outputLine in stderr.split("\n")) {
- var error = _tryParse(outputLine);
- if (error != null) yield error;
+ static List<AnalyzerError> parseStderr(String stderr) {
+ var result = <AnalyzerError>[];
+
+ var jsonData = json.decode(stderr) as Map<String, dynamic>;
+ var version = jsonData['version'];
+ if (version != 1) {
+ DebugLogger.error('Unexpected analyzer JSON data version: $version');
+ throw UnimplementedError();
}
- }
- static AnalyzerError _tryParse(String line) {
- if (line.isEmpty) return null;
+ for (var diagnostic in jsonData['diagnostics'] as List<dynamic>) {
+ var diagnosticMap = diagnostic as Map<String, dynamic>;
- // Split and unescape the fields.
- // The escaping is implemented in:
- // pkg/analyzer_cli/lib/src/error_formatter.dart#L392
- var fields = <String>[];
- var field = StringBuffer();
- var inEscape = false;
- for (var i = 0; i < line.length; i++) {
- var c = line[i];
+ var type = diagnosticMap['type'] as String;
+ var code = diagnosticMap['code'] as String;
+ var errorCode = '$type.${code.toUpperCase()}';
- if (inEscape) {
- switch (c) {
- case '\\':
- field.write('\\');
- break;
- case '|':
- field.write('|');
- break;
- case 'n':
- field.write('\n');
- break;
- // TODO(rnystrom): Are there other escapes?
- default:
- field.write(c);
- break;
- }
+ var error = _parse(
+ diagnosticMap, diagnosticMap['problemMessage'] as String, errorCode);
+ result.add(error);
- inEscape = false;
- } else if (c == '\\') {
- inEscape = true;
- } else if (c == '|') {
- fields.add(field.toString());
- field = StringBuffer();
- } else {
- field.write(c);
+ var contextMessages = diagnosticMap['contextMessages'] as List<dynamic>;
+ for (var contextMessage in contextMessages ?? const []) {
+ var contextMessageMap = contextMessage as Map<String, dynamic>;
+ error.contextMessages.add(
+ _parse(contextMessageMap, contextMessageMap['message'] as String));
}
}
- // Add the last field.
- fields.add(field.toString());
+ return result;
+ }
- // Lines without enough fields are other output we don't care about.
- if (fields.length < 8) return null;
+ static AnalyzerError _parse(Map<String, dynamic> diagnostic, String message,
+ [String errorCode]) {
+ var location = diagnostic['location'] as Map<String, dynamic>;
+ var range = location['range'] as Map<String, dynamic>;
+ var start = range['start'] as Map<String, dynamic>;
+ var end = range['end'] as Map<String, dynamic>;
return AnalyzerError._(
- severity: fields[0],
- errorCode: "${fields[1]}.${fields[2]}",
- file: fields[3],
- message: fields[7],
- line: int.parse(fields[4]),
- column: int.parse(fields[5]),
- length: int.parse(fields[6]));
+ severity: diagnostic['severity'] as String,
+ errorCode: errorCode,
+ file: location['file'] as String,
+ message: message,
+ line: start['line'] as int,
+ column: start['column'] as int,
+ length: (end['offset'] as int) - (start['offset'] as int));
}
final String severity;
@@ -532,6 +517,8 @@
final int column;
final int length;
+ final List<AnalyzerError> contextMessages = [];
+
AnalyzerError._(
{this.severity,
this.errorCode,
@@ -558,57 +545,53 @@
class AnalysisCommandOutput extends CommandOutput with _StaticErrorOutput {
static void parseErrors(String stderr, List<StaticError> errors,
[List<StaticError> warnings]) {
- var jsonData = json.decode(stderr) as Map<String, dynamic>;
- var version = jsonData['version'];
- if (version != 1) {
- DebugLogger.error('Unexpected analyzer JSON data version: $version');
- throw UnimplementedError();
- }
- for (var diagnostic in jsonData['diagnostics'] as List<dynamic>) {
- var diagnosticMap = diagnostic as Map<String, dynamic>;
- var location = diagnosticMap['location'] as Map<String, dynamic>;
- var file = location['file'] as String;
- var type = diagnosticMap['type'] as String;
- var code = (diagnosticMap['code'] as String).toUpperCase();
- var staticError =
- _decodeStaticError(ErrorSource.analyzer, '$type.$code', location);
- var contextMessages = diagnosticMap['contextMessages'] as List<dynamic>;
- for (var contextMessage in contextMessages ?? const []) {
- var contextMessageMap = contextMessage as Map<String, dynamic>;
- var contextLocation =
- contextMessageMap['location'] as Map<String, dynamic>;
- if (contextLocation['file'] == file) {
- staticError.contextMessages.add(_decodeStaticError(
- ErrorSource.context,
- contextMessageMap['message'] as String,
- contextLocation));
- } else {
+ StaticError convert(AnalyzerError error) {
+ var staticError = StaticError(ErrorSource.analyzer, error.errorCode,
+ line: error.line, column: error.column, length: error.length);
+
+ for (var context in error.contextMessages) {
+ // TODO(rnystrom): Include these when static error tests get support
+ // for errors/context in other files.
+ if (context.file != error.file) {
DebugLogger.warning(
"Context messages in other files not currently supported.");
+ continue;
}
+
+ staticError.contextMessages.add(StaticError(
+ ErrorSource.context, context.message,
+ line: context.line,
+ column: context.column,
+ length: context.length));
}
- var severity = diagnosticMap['severity'] as String;
- if (severity == 'ERROR') {
- errors.add(staticError);
- } else if (severity == 'WARNING') {
- warnings?.add(staticError);
+ return staticError;
+ }
+
+ // Parse as Analyzer errors and then convert them to the StaticError objects
+ // the static error tests expect.
+ for (var diagnostic in AnalyzerError.parseStderr(stderr)) {
+ if (diagnostic.severity == 'ERROR') {
+ errors.add(convert(diagnostic));
+ } else if (diagnostic.severity == 'WARNING' && warnings != null) {
+ warnings.add(convert(diagnostic));
}
}
}
- static StaticError _decodeStaticError(
- ErrorSource errorSource, String message, Map<String, dynamic> location) {
- var locationRange = location['range'] as Map<String, dynamic>;
- var locationRangeStart = locationRange['start'] as Map<String, dynamic>;
- var locationRangeEnd = locationRange['end'] as Map<String, dynamic>;
- return StaticError(errorSource, message,
- line: locationRangeStart['line'] as int,
- column: locationRangeStart['column'] as int,
- length: (locationRangeEnd['offset'] as int) -
- (locationRangeStart['offset'] as int));
+ /// If the stderr of analyzer could not be parsed as valid JSON, this will
+ /// be the stderr as a string instead. Otherwise it will be null.
+ String get invalidJsonStderr {
+ if (!_parsedErrors) {
+ _parseErrors();
+ _parsedErrors = true;
+ }
+
+ return _invalidJsonStderr;
}
+ String _invalidJsonStderr;
+
AnalysisCommandOutput(
Command command,
int exitCode,
@@ -631,22 +614,7 @@
if (hasNonUtf8) return Expectation.nonUtf8Error;
if (truncatedOutput) return Expectation.truncatedOutput;
- List<StaticError> errors;
- try {
- errors = this.errors;
- } catch (_) {
- // This can happen in at least two scenarios:
- // - The analyzer output was too long so it got truncated (so the
- // resulting output was ill-formed). See also
- // https://github.com/dart-lang/sdk/issues/44493.
- // - The analyzer did not find a file to analyze at the specified
- // location, so it generated the output "No dart files found at
- // <path>.dart" (which is not valid JSON). See
- // https://github.com/dart-lang/sdk/issues/45556.
- // TODO(paulberry,rnystrom): remove this logic once the two above bugs are
- // fixed.
- return Expectation.crash;
- }
+ if (invalidJsonStderr != null) return Expectation.fail;
// If it's a static error test, validate the exact errors.
if (testCase.testFile.isStaticErrorTest) {
@@ -693,22 +661,7 @@
if (hasNonUtf8) return Expectation.nonUtf8Error;
if (truncatedOutput) return Expectation.truncatedOutput;
- List<StaticError> errors;
- try {
- errors = this.errors;
- } catch (_) {
- // This can happen in at least two scenarios:
- // - The analyzer output was too long so it got truncated (so the
- // resulting output was ill-formed). See also
- // https://github.com/dart-lang/sdk/issues/44493.
- // - The analyzer did not find a file to analyze at the specified
- // location, so it generated the output "No dart files found at
- // <path>.dart" (which is not valid JSON). See
- // https://github.com/dart-lang/sdk/issues/45556.
- // TODO(paulberry,rnystrom): remove this logic once the two above bugs are
- // fixed.
- return Expectation.crash;
- }
+ if (invalidJsonStderr != null) return Expectation.fail;
// If it's a static error test, validate the exact errors.
if (testCase.testFile.isStaticErrorTest) {
@@ -726,16 +679,19 @@
@override
void describe(TestCase testCase, Progress progress, OutputWriter output) {
+ if (invalidJsonStderr != null) {
+ output.subsection("invalid JSON on analyzer stderr");
+ output.write(invalidJsonStderr);
+ return;
+ }
+
// Handle static error test output specially. We don't want to show the raw
// stdout if we can give the user the parsed expectations instead.
if (testCase.testFile.isStaticErrorTest || hasCrashed || hasTimedOut) {
super.describe(testCase, progress, output);
} else {
- output.subsection("unexpected analysis errors");
-
- var errorsByFile = <String, List<AnalyzerError>>{};
-
// Parse and sort the errors.
+ var errorsByFile = <String, List<AnalyzerError>>{};
for (var error in AnalyzerError.parseStderr(decodeUtf8(stderr))) {
errorsByFile.putIfAbsent(error.file, () => []).add(error);
}
@@ -744,8 +700,10 @@
files.sort();
for (var file in files) {
- var path = Path(file).relativeTo(Repository.dir).toString();
- output.write("In $path:");
+ var path = Path(file)
+ .relativeTo(testCase.testFile.path.directoryPath)
+ .toString();
+ output.subsection("unexpected analysis errors in $path");
var errors = errorsByFile[file];
errors.sort();
@@ -762,24 +720,21 @@
}
}
- /// Parses the machine-readable output of analyzer, which looks like:
- ///
- /// ERROR|STATIC_TYPE_WARNING|SOME_ERROR_CODE|/path/to/some_test.dart|9|26|1|Error message.
- ///
- /// Pipes can be escaped with backslashes:
- ///
- /// FOO|BAR|FOO\|BAR|FOO\\BAZ
- ///
- /// Is parsed as:
- ///
- /// FOO BAR FOO|BAR FOO\BAZ
+ /// Parses the JSON output of analyzer.
@override
void _parseErrors() {
- var errors = <StaticError>[];
- var warnings = <StaticError>[];
- parseErrors(decodeUtf8(stderr), errors, warnings);
- errors.forEach(addError);
- warnings.forEach(addWarning);
+ var stderrString = decodeUtf8(stderr);
+ try {
+ var errors = <StaticError>[];
+ var warnings = <StaticError>[];
+ parseErrors(stderrString, errors, warnings);
+ errors.forEach(addError);
+ warnings.forEach(addWarning);
+ } on FormatException {
+ // It wasn't JSON. This can happen if analyzer instead prints:
+ // "No dart files found at: ..."
+ _invalidJsonStderr = stderrString;
+ }
}
}
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index 95c7dcb..ab31327 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -1066,27 +1066,10 @@
CommandArtifact computeCompilationArtifact(String tempDir,
List<String> arguments, Map<String, String> environmentOverrides) {
- const legacyTestDirectories = {
- "co19_2",
- "corelib_2",
- "ffi_2",
- "language_2",
- "lib_2",
- "service_2",
- "standalone_2"
- };
-
- // If we are running a legacy test with NNBD enabled, tell analyzer to use
- // a pre-NNBD language version for the test.
- var testPath = arguments.last;
- var segments = Path(testPath).relativeTo(Repository.dir).segments();
- var setLegacyVersion = segments.any(legacyTestDirectories.contains);
-
var args = [
...arguments,
if (_configuration.useAnalyzerCfe) '--use-cfe',
if (_configuration.useAnalyzerFastaParser) '--use-fasta-parser',
- if (setLegacyVersion) '--default-language-version=2.7',
];
// Since this is not a real compilation, no artifacts are produced.
diff --git a/runtime/observatory_2/tests/service_2/evaluate_activation_test.dart b/runtime/observatory_2/tests/service_2/evaluate_activation_test.dart
index a1c3a5b..179dcc5 100644
--- a/runtime/observatory_2/tests/service_2/evaluate_activation_test.dart
+++ b/runtime/observatory_2/tests/service_2/evaluate_activation_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import 'package:observatory_2/service_io.dart';
import 'package:test/test.dart';
import 'test_helper.dart';
diff --git a/runtime/observatory_2/tests/service_2/service_test_common.dart b/runtime/observatory_2/tests/service_2/service_test_common.dart
index 7775b29..e1dc008 100644
--- a/runtime/observatory_2/tests/service_2/service_test_common.dart
+++ b/runtime/observatory_2/tests/service_2/service_test_common.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
library service_test_common;
import 'dart:async';
diff --git a/runtime/observatory_2/tests/service_2/test_helper.dart b/runtime/observatory_2/tests/service_2/test_helper.dart
index 0a352fe..570e830 100644
--- a/runtime/observatory_2/tests/service_2/test_helper.dart
+++ b/runtime/observatory_2/tests/service_2/test_helper.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
library test_helper;
import 'dart:async';
diff --git a/tests/dartdevc_2/hot_restart_expando_test.dart b/tests/dartdevc_2/hot_restart_expando_test.dart
index 4802a52..12ce127 100644
--- a/tests/dartdevc_2/hot_restart_expando_test.dart
+++ b/tests/dartdevc_2/hot_restart_expando_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import 'dart:_runtime' as dart;
import 'package:expect/expect.dart';
diff --git a/tests/ffi_2/regress_45988_test.dart b/tests/ffi_2/regress_45988_test.dart
index 8677840..bcb6f4e 100644
--- a/tests/ffi_2/regress_45988_test.dart
+++ b/tests/ffi_2/regress_45988_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import 'dart:ffi';
import "package:expect/expect.dart";
diff --git a/tests/ffi_2/regress_46004_test.dart b/tests/ffi_2/regress_46004_test.dart
index 627611f..90d8963 100644
--- a/tests/ffi_2/regress_46004_test.dart
+++ b/tests/ffi_2/regress_46004_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import 'dart:ffi';
import "package:expect/expect.dart";
diff --git a/tests/ffi_2/regress_46085_test.dart b/tests/ffi_2/regress_46085_test.dart
index 0f90373..8ef3154 100644
--- a/tests/ffi_2/regress_46085_test.dart
+++ b/tests/ffi_2/regress_46085_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import "dart:ffi";
class MyStruct extends Struct {
diff --git a/tests/ffi_2/regress_46127_test.dart b/tests/ffi_2/regress_46127_test.dart
index 73d9f61..b3f5b27 100644
--- a/tests/ffi_2/regress_46127_test.dart
+++ b/tests/ffi_2/regress_46127_test.dart
@@ -1,7 +1,9 @@
// 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.
-//
+
+// @dart = 2.9
+
// SharedObjects=ffi_test_functions
import 'dart:ffi';
diff --git a/tests/ffi_2/vmspecific_leaf_call_test.dart b/tests/ffi_2/vmspecific_leaf_call_test.dart
index f4c0445..1cb94fb 100644
--- a/tests/ffi_2/vmspecific_leaf_call_test.dart
+++ b/tests/ffi_2/vmspecific_leaf_call_test.dart
@@ -1,7 +1,9 @@
// 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.
-//
+
+// @dart = 2.9
+
// SharedObjects=ffi_test_functions
import 'dart:ffi';
diff --git a/tests/language_2/generic_methods/generic_invocation_all_subexpression_types_test.dart b/tests/language_2/generic_methods/generic_invocation_all_subexpression_types_test.dart
index c1e64c5..b395dc9b 100644
--- a/tests/language_2/generic_methods/generic_invocation_all_subexpression_types_test.dart
+++ b/tests/language_2/generic_methods/generic_invocation_all_subexpression_types_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
// This test verifies that `EXPR<typeArguments>(arguments)` is properly parsed
// as a generic invocation, for all types of expressions that may appear as
// EXPR. We try to pay extra attention to ambiguous expressions (that is, where
diff --git a/tests/language_2/generic_methods/generic_invocation_all_types_test.dart b/tests/language_2/generic_methods/generic_invocation_all_types_test.dart
index f37efda..1c9fda2 100644
--- a/tests/language_2/generic_methods/generic_invocation_all_types_test.dart
+++ b/tests/language_2/generic_methods/generic_invocation_all_types_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
// This test verifies that `f<T, U>(0)` is properly parsed as a generic
// invocation, for all type syntaxes that may appear as T and U.
diff --git a/tests/language_2/generic_methods/non_generic_invocation_all_first_expression_types_test.dart b/tests/language_2/generic_methods/non_generic_invocation_all_first_expression_types_test.dart
index 1b56800..779ebd5 100644
--- a/tests/language_2/generic_methods/non_generic_invocation_all_first_expression_types_test.dart
+++ b/tests/language_2/generic_methods/non_generic_invocation_all_first_expression_types_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
// This test verifies that `EXPR<a,b>-x` is properly parsed as a pair of
// expressions separated by a `,`, for all types of expressions that may appear
// as EXPR. We try to pay extra attention to expressions that will become
diff --git a/tests/language_2/generic_methods/non_generic_invocation_all_first_types_test.dart b/tests/language_2/generic_methods/non_generic_invocation_all_first_types_test.dart
index 59a76a8..7d817ee 100644
--- a/tests/language_2/generic_methods/non_generic_invocation_all_first_types_test.dart
+++ b/tests/language_2/generic_methods/non_generic_invocation_all_first_types_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
// This test verifies that `f<EXPR,b>(x)` is properly parsed as a pair of
// expressions separated by a `,`, for all types of expressions that may appear
// as EXPR that can't be parsed as types.
diff --git a/tests/language_2/generic_methods/non_generic_invocation_all_second_expression_types_test.dart b/tests/language_2/generic_methods/non_generic_invocation_all_second_expression_types_test.dart
index daead6d..53bfeb7 100644
--- a/tests/language_2/generic_methods/non_generic_invocation_all_second_expression_types_test.dart
+++ b/tests/language_2/generic_methods/non_generic_invocation_all_second_expression_types_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
// This test verifies that `f<a,b>EXPR` is properly parsed as a pair of
// expressions separated by a `,`, for all types of expressions that may appear
// as EXPR. We try to pay extra attention to expressions that will become
diff --git a/tests/language_2/generic_methods/non_generic_invocation_all_second_types_test.dart b/tests/language_2/generic_methods/non_generic_invocation_all_second_types_test.dart
index 2036af2..c388dda 100644
--- a/tests/language_2/generic_methods/non_generic_invocation_all_second_types_test.dart
+++ b/tests/language_2/generic_methods/non_generic_invocation_all_second_types_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
// This test verifies that `f<a,EXPR>(x)` is properly parsed as a pair of
// expressions separated by a `,`, for all types of expressions that may appear
// as EXPR that can't be parsed as types.
diff --git a/tests/language_2/regress/regress45890_test.dart b/tests/language_2/regress/regress45890_test.dart
index abf9a84..8dae9eb 100644
--- a/tests/language_2/regress/regress45890_test.dart
+++ b/tests/language_2/regress/regress45890_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import "package:expect/expect.dart";
class C {
diff --git a/tests/language_2/syntax_helper.dart b/tests/language_2/syntax_helper.dart
index bf6f995..f4965b4 100644
--- a/tests/language_2/syntax_helper.dart
+++ b/tests/language_2/syntax_helper.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import 'dart:convert';
import "package:expect/expect.dart";
diff --git a/tests/language_2/vm/regress_45260_test.dart b/tests/language_2/vm/regress_45260_test.dart
index 17991e3..a75143f 100644
--- a/tests/language_2/vm/regress_45260_test.dart
+++ b/tests/language_2/vm/regress_45260_test.dart
@@ -1,7 +1,9 @@
// 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.
-//
+
+// @dart = 2.9
+
// VMOptions=--optimization-counter-threshold=5 --deterministic
import 'package:expect/expect.dart';
diff --git a/tests/language_2/vm/regress_45855_test.dart b/tests/language_2/vm/regress_45855_test.dart
index b99676e..d35675c 100644
--- a/tests/language_2/vm/regress_45855_test.dart
+++ b/tests/language_2/vm/regress_45855_test.dart
@@ -1,7 +1,9 @@
// 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.
-//
+
+// @dart = 2.9
+
// VMOptions=--optimization-counter-threshold=100 --deterministic
//
// The Dart Project Fuzz Tester (1.89).
diff --git a/tests/lib_2/typed_data/restricted_types_error_test.dart b/tests/lib_2/typed_data/restricted_types_error_test.dart
index 02b5721..51cffb0 100644
--- a/tests/lib_2/typed_data/restricted_types_error_test.dart
+++ b/tests/lib_2/typed_data/restricted_types_error_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import 'dart:typed_data';
abstract class CEByteBuffer extends ByteBuffer {}
diff --git a/tests/standalone_2/dwarf_stack_trace_invisible_functions_test.dart b/tests/standalone_2/dwarf_stack_trace_invisible_functions_test.dart
index a54f3eb..f4907f5 100644
--- a/tests/standalone_2/dwarf_stack_trace_invisible_functions_test.dart
+++ b/tests/standalone_2/dwarf_stack_trace_invisible_functions_test.dart
@@ -2,7 +2,9 @@
// 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.
-/// VMOptions=--dwarf-stack-traces --save-debugging-info=dwarf_invisible_functions.so
+// @dart = 2.9
+
+// VMOptions=--dwarf-stack-traces --save-debugging-info=dwarf_invisible_functions.so
import 'dart:io';
diff --git a/tests/standalone_2/io/http_on_unix_socket_test.dart b/tests/standalone_2/io/http_on_unix_socket_test.dart
index e9611dc..c98afb5 100644
--- a/tests/standalone_2/io/http_on_unix_socket_test.dart
+++ b/tests/standalone_2/io/http_on_unix_socket_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
import 'dart:async';
import 'dart:io';
import 'dart:convert';
diff --git a/tests/web_2/regress/43520_safari_test.dart b/tests/web_2/regress/43520_safari_test.dart
index 2dee2bb..52c059a 100644
--- a/tests/web_2/regress/43520_safari_test.dart
+++ b/tests/web_2/regress/43520_safari_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
/// Regression test for dartbug.com/43520.
///
/// Safari has a bug that makes it a syntax error for a function name to overlap
diff --git a/tests/web_2/regress/45943_test.dart b/tests/web_2/regress/45943_test.dart
index 4b40c44a..1cdee39 100644
--- a/tests/web_2/regress/45943_test.dart
+++ b/tests/web_2/regress/45943_test.dart
@@ -2,6 +2,8 @@
// 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.
+// @dart = 2.9
+
class _InspectorOverlayLayer {
String selection;
diff --git a/tools/VERSION b/tools/VERSION
index 432be1f..f3750fc 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 172
+PRERELEASE 173
PRERELEASE_PATCH 0
\ No newline at end of file