Extend AST for enhanced enums.

Change-Id: Ief64e4ba23311543702e7aa1b855e1a3519172a0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/222120
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
Konstantin Shcheglov 2021-12-06 18:57:17 +00:00 committed by Commit Bot
parent 24e681927a
commit abd1dc8465
11 changed files with 279 additions and 51 deletions

View file

@ -1577,7 +1577,9 @@ abstract class EnumConstantDeclaration implements Declaration {
/// The declaration of an enumeration.
///
/// enumType ::=
/// metadata 'enum' [SimpleIdentifier] '{' [SimpleIdentifier] (',' [SimpleIdentifier])* (',')? '}'
/// metadata 'enum' [SimpleIdentifier] [TypeParameterList]?
/// [WithClause]? [ImplementsClause]? '{' [SimpleIdentifier]
/// (',' [SimpleIdentifier])* (';' [ClassMember]+)? '}'
///
/// Clients may not extend, implement or mix-in this class.
abstract class EnumDeclaration implements NamedCompilationUnitMember {
@ -1590,6 +1592,10 @@ abstract class EnumDeclaration implements NamedCompilationUnitMember {
/// Return the 'enum' keyword.
Token get enumKeyword;
/// Returns the `implements` clause for the enumeration, or `null` if the
/// enumeration does not implement any interfaces.
ImplementsClause? get implementsClause;
/// Return the left curly bracket.
Token get leftBracket;
@ -1601,6 +1607,14 @@ abstract class EnumDeclaration implements NamedCompilationUnitMember {
/// Return the right curly bracket.
Token get rightBracket;
/// Returns the type parameters for the enumeration, or `null` if the
/// enumeration does not have any type parameters.
TypeParameterList? get typeParameters;
/// Return the `with` clause for the enumeration, or `null` if the
/// enumeration does not have a `with` clause.
WithClause? get withClause;
}
/// An export directive.

View file

@ -301,6 +301,7 @@ abstract class AstFactory {
/// [comment] and [metadata] can be `null` if the declaration does not have
/// the corresponding attribute. The list of [constants] must contain at least
/// one value.
@Deprecated('Use enumDeclaration2() instead')
EnumDeclaration enumDeclaration(
Comment? comment,
List<Annotation>? metadata,
@ -308,9 +309,26 @@ abstract class AstFactory {
SimpleIdentifier name,
Token leftBracket,
List<EnumConstantDeclaration> constants,
List<ClassMember> members,
Token rightBracket);
/// Returns a newly created enumeration declaration. Either or both of the
/// [comment] and [metadata] can be `null` if the declaration does not have
/// the corresponding attribute. The list of [constants] must contain at least
/// one value.
EnumDeclaration enumDeclaration2({
required Comment? comment,
required List<Annotation>? metadata,
required Token enumKeyword,
required SimpleIdentifier name,
required TypeParameterList? typeParameters,
required WithClause? withClause,
required ImplementsClause? implementsClause,
required Token leftBracket,
required List<EnumConstantDeclaration> constants,
required List<ClassMember> members,
required Token rightBracket,
});
/// Returns a newly created export directive. Either or both of the
/// [comment] and [metadata] can be `null` if the directive does not have the
/// corresponding attribute. The list of [combinators] can be `null` if there

View file

@ -3286,14 +3286,27 @@ class EnumConstantDeclarationImpl extends DeclarationImpl
/// The declaration of an enumeration.
///
/// enumType ::=
/// metadata 'enum' [SimpleIdentifier] '{' [SimpleIdentifier]
/// (',' [SimpleIdentifier])* (',')? '}'
/// metadata 'enum' [SimpleIdentifier] [TypeParameterList]?
/// [WithClause]? [ImplementsClause]? '{' [SimpleIdentifier]
/// (',' [SimpleIdentifier])* (';' [ClassMember]+)? '}'
class EnumDeclarationImpl extends NamedCompilationUnitMemberImpl
implements EnumDeclaration {
/// The 'enum' keyword.
@override
Token enumKeyword;
/// The type parameters, or `null` if the enumeration does not have any
/// type parameters.
TypeParameterListImpl? _typeParameters;
/// The `with` clause for the enumeration, or `null` if the class does not
/// have a `with` clause.
WithClauseImpl? _withClause;
/// The `implements` clause for the enumeration, or `null` if the enumeration
/// does not implement any interfaces.
ImplementsClauseImpl? _implementsClause;
/// The left curly bracket.
@override
Token leftBracket;
@ -3317,11 +3330,17 @@ class EnumDeclarationImpl extends NamedCompilationUnitMemberImpl
List<Annotation>? metadata,
this.enumKeyword,
SimpleIdentifierImpl name,
this._typeParameters,
this._withClause,
this._implementsClause,
this.leftBracket,
List<EnumConstantDeclaration> constants,
List<ClassMember> members,
this.rightBracket)
: super(comment, metadata, name) {
_becomeParentOf(_typeParameters);
_becomeParentOf(_withClause);
_becomeParentOf(_implementsClause);
_constants._initialize(this, constants);
_members._initialize(this, members);
}
@ -3331,6 +3350,9 @@ class EnumDeclarationImpl extends NamedCompilationUnitMemberImpl
Iterable<SyntacticEntity> get childEntities => super._childEntities
..add(enumKeyword)
..add(_name)
..add(_typeParameters)
..add(_withClause)
..add(_implementsClause)
..add(leftBracket)
..addAll(_constants)
..addAll(_members)
@ -3348,9 +3370,31 @@ class EnumDeclarationImpl extends NamedCompilationUnitMemberImpl
@override
Token get firstTokenAfterCommentAndMetadata => enumKeyword;
@override
ImplementsClauseImpl? get implementsClause => _implementsClause;
set implementsClause(ImplementsClause? implementsClause) {
_implementsClause =
_becomeParentOf(implementsClause as ImplementsClauseImpl?);
}
@override
NodeListImpl<ClassMember> get members => _members;
@override
TypeParameterListImpl? get typeParameters => _typeParameters;
set typeParameters(TypeParameterList? typeParameters) {
_typeParameters = _becomeParentOf(typeParameters as TypeParameterListImpl?);
}
@override
WithClauseImpl? get withClause => _withClause;
set withClause(WithClause? withClause) {
_withClause = _becomeParentOf(withClause as WithClauseImpl?);
}
@override
E? accept<E>(AstVisitor<E> visitor) => visitor.visitEnumDeclaration(this);
@ -3358,6 +3402,9 @@ class EnumDeclarationImpl extends NamedCompilationUnitMemberImpl
void visitChildren(AstVisitor visitor) {
super.visitChildren(visitor);
_name.accept(visitor);
_typeParameters?.accept(visitor);
_withClause?.accept(visitor);
_implementsClause?.accept(visitor);
_constants.accept(visitor);
_members.accept(visitor);
}

View file

@ -383,6 +383,7 @@ class AstFactoryImpl extends AstFactory {
EnumConstantDeclarationImpl(
comment as CommentImpl?, metadata, name as SimpleIdentifierImpl);
@Deprecated('Use enumDeclaration2() instead')
@override
EnumDeclarationImpl enumDeclaration(
Comment? comment,
@ -391,17 +392,48 @@ class AstFactoryImpl extends AstFactory {
SimpleIdentifier name,
Token leftBracket,
List<EnumConstantDeclaration> constants,
List<ClassMember> members,
Token rightBracket) =>
EnumDeclarationImpl(
comment as CommentImpl?,
metadata,
enumKeyword,
name as SimpleIdentifierImpl,
leftBracket,
constants,
members,
rightBracket);
enumDeclaration2(
comment: comment,
metadata: metadata,
enumKeyword: enumKeyword,
name: name,
typeParameters: null,
withClause: null,
implementsClause: null,
leftBracket: leftBracket,
constants: constants,
members: [],
rightBracket: rightBracket);
@override
EnumDeclarationImpl enumDeclaration2({
required Comment? comment,
required List<Annotation>? metadata,
required Token enumKeyword,
required SimpleIdentifier name,
required TypeParameterList? typeParameters,
required WithClause? withClause,
required ImplementsClause? implementsClause,
required Token leftBracket,
required List<EnumConstantDeclaration> constants,
required List<ClassMember> members,
required Token rightBracket,
}) {
return EnumDeclarationImpl(
comment as CommentImpl?,
metadata,
enumKeyword,
name as SimpleIdentifierImpl,
typeParameters as TypeParameterListImpl?,
withClause as WithClauseImpl?,
implementsClause as ImplementsClauseImpl?,
leftBracket,
constants,
members,
rightBracket,
);
}
@override
ExportDirectiveImpl exportDirective(

View file

@ -314,8 +314,12 @@ class ToSourceVisitor implements AstVisitor<void> {
_visitNodeList(node.metadata, separator: ' ', suffix: ' ');
sink.write('enum ');
_visitNode(node.name);
_visitNode(node.typeParameters);
_visitNode(node.withClause, prefix: ' ');
_visitNode(node.implementsClause, prefix: ' ');
sink.write(' {');
_visitNodeList(node.constants, separator: ', ');
_visitNodeList(node.members, prefix: '; ', separator: ' ');
sink.write('}');
}

View file

@ -2002,8 +2002,19 @@ class NodeReplacer implements AstVisitor<bool> {
if (identical(node.name, _oldNode)) {
node.name = _newNode as SimpleIdentifier;
return true;
} else if (identical(node.typeParameters, _oldNode)) {
node.typeParameters = _newNode as TypeParameterList;
return true;
} else if (identical(node.withClause, _oldNode)) {
node.withClause = _newNode as WithClause;
return true;
} else if (identical(node.implementsClause, _oldNode)) {
node.implementsClause = _newNode as ImplementsClause;
return true;
} else if (_replaceInList(node.constants)) {
return true;
} else if (_replaceInList(node.members)) {
return true;
}
return visitAnnotatedNode(node);
}

View file

@ -2813,8 +2813,21 @@ class AstBuilder extends StackListener {
);
}
declarations.add(enumDeclaration = ast.enumDeclaration(comment, metadata,
enumKeyword, name, leftBrace, [], [], leftBrace.endGroup!));
declarations.add(
enumDeclaration = ast.enumDeclaration2(
comment: comment,
metadata: metadata,
enumKeyword: enumKeyword,
name: name,
typeParameters: typeParameters,
withClause: withClause,
implementsClause: implementsClause,
leftBracket: leftBrace,
constants: [],
members: [],
rightBracket: leftBrace.endGroup!,
),
);
}
@override

View file

@ -458,30 +458,6 @@ class AstTestFactory {
static EmptyStatementImpl emptyStatement() => astFactory
.emptyStatement(TokenFactory.tokenFromType(TokenType.SEMICOLON));
static EnumDeclarationImpl enumDeclaration(
SimpleIdentifier name, List<EnumConstantDeclaration> constants) =>
astFactory.enumDeclaration(
null,
null,
TokenFactory.tokenFromKeyword(Keyword.ENUM),
name,
TokenFactory.tokenFromType(TokenType.OPEN_CURLY_BRACKET),
constants,
[],
TokenFactory.tokenFromType(TokenType.CLOSE_CURLY_BRACKET));
static EnumDeclarationImpl enumDeclaration2(
String name, List<String> constantNames) {
var constants = constantNames.map((name) {
return astFactory.enumConstantDeclaration(
null,
null,
identifier3(name),
);
}).toList();
return enumDeclaration(identifier3(name), constants);
}
static ExportDirectiveImpl exportDirective(
List<Annotation> metadata, String uri,
[List<Combinator> combinators = const []]) =>

View file

@ -123,6 +123,10 @@ class FindNode {
return _node(search, (n) => n is DoubleLiteral);
}
EnumConstantDeclaration enumConstantDeclaration(String search) {
return _node(search, (n) => n is EnumConstantDeclaration);
}
EnumDeclaration enumDeclaration(String search) {
return _node(search, (n) => n is EnumDeclaration);
}

View file

@ -1649,12 +1649,44 @@ void f() {
}
void test_enumDeclaration() {
var node = AstTestFactory.enumDeclaration2("E", ["ONE", "TWO"]);
node.documentationComment = astFactory.endOfLineComment(EMPTY_TOKEN_LIST);
node.metadata
.add(AstTestFactory.annotation(AstTestFactory.identifier3("a")));
_assertReplace(node, Getter_NodeReplacerTest_test_enumDeclaration());
_testAnnotatedNode(node);
var findNode = _parseStringToFindNode(r'''
enum E1<T> with M1 implements I1 {one, two}
enum E2<U> with M2 implements I2 {one, two}
''');
_assertReplace2<EnumDeclaration>(
destination: findNode.enumDeclaration('enum E1'),
source: findNode.enumDeclaration('enum E2'),
getters: [
(node) => node.name,
(node) => node.typeParameters!,
(node) => node.withClause!,
(node) => node.implementsClause!,
],
);
}
void test_enumDeclaration_constants() {
var findNode = _parseStringToFindNode(r'''
enum E1 {one}
enum E2 {two}
''');
_assertReplaceInList(
destination: findNode.enumDeclaration('enum E1'),
child: findNode.enumConstantDeclaration('one'),
replacement: findNode.enumConstantDeclaration('two'),
);
}
void test_enumDeclaration_members() {
var findNode = _parseStringToFindNode(r'''
enum E1 {one; void foo() {}}
enum E2 {two; void bar() {}}
''');
_assertReplaceInList(
destination: findNode.enumDeclaration('enum E1'),
child: findNode.methodDeclaration('foo'),
replacement: findNode.methodDeclaration('bar'),
);
}
void test_exportDirective() {
@ -2303,12 +2335,24 @@ class B extends A {
}
}
void _assertReplaceInList({
required AstNode destination,
required AstNode child,
required AstNode replacement,
}) {
expect(child.parent, destination);
NodeReplacer.replace(child, replacement);
expect(replacement.parent, destination);
}
FindNode _parseStringToFindNode(String content) {
var parseResult = parseString(
content: content,
featureSet: FeatureSet.fromEnableFlags2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: [
Feature.enhanced_enums.enableString,
Feature.super_parameters.enableString,
],
),

View file

@ -2,14 +2,17 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/src/dart/analysis/experiments.dart';
import 'package:analyzer/src/dart/ast/ast_factory.dart';
import 'package:analyzer/src/dart/ast/to_source_visitor.dart';
import 'package:analyzer/src/generated/testing/ast_test_factory.dart';
import 'package:analyzer/src/generated/testing/token_factory.dart';
import 'package:analyzer/src/summary2/ast_binary_tokens.dart';
import 'package:analyzer/src/test_utilities/find_node.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@ -765,14 +768,62 @@ class ToSourceVisitor2Test {
_assertSource(";", AstTestFactory.emptyStatement());
}
void test_visitEnumDeclaration_multiple() {
_assertSource("enum E {ONE, TWO}",
AstTestFactory.enumDeclaration2("E", ["ONE", "TWO"]));
void test_visitEnumDeclaration_constants_multiple() {
var findNode = _parseStringToFindNode(r'''
enum E {one, two}
''');
_assertSource(
'enum E {one, two}',
findNode.enumDeclaration('E'),
);
}
void test_visitEnumDeclaration_single() {
void test_visitEnumDeclaration_constants_single() {
var findNode = _parseStringToFindNode(r'''
enum E {one}
''');
_assertSource(
"enum E {ONE}", AstTestFactory.enumDeclaration2("E", ["ONE"]));
'enum E {one}',
findNode.enumDeclaration('E'),
);
}
void test_visitEnumDeclaration_field_constructor() {
var findNode = _parseStringToFindNode(r'''
enum E {
one, two;
final int field;
E(this.field);
}
''');
_assertSource(
'enum E {one, two; final int field; E(this.field);}',
findNode.enumDeclaration('enum E'),
);
}
void test_visitEnumDeclaration_method() {
var findNode = _parseStringToFindNode(r'''
enum E {
one, two;
void myMethod() {}
int get myGetter => 0;
}
''');
_assertSource(
'enum E {one, two; void myMethod() {} int get myGetter => 0;}',
findNode.enumDeclaration('enum E'),
);
}
void test_visitEnumDeclaration_withoutMembers() {
var findNode = _parseStringToFindNode(r'''
enum E<T> with M1, M2 implements I1, I2 {one, two}
''');
_assertSource(
'enum E<T> with M1, M2 implements I1, I2 {one, two}',
findNode.enumDeclaration('E'),
);
}
void test_visitExportDirective_combinator() {
@ -3356,4 +3407,18 @@ import 'foo.dart'
node.accept(ToSourceVisitor(buffer));
expect(buffer.toString(), expectedSource);
}
FindNode _parseStringToFindNode(String content) {
var parseResult = parseString(
content: content,
featureSet: FeatureSet.fromEnableFlags2(
sdkLanguageVersion: ExperimentStatus.currentVersion,
flags: [
Feature.enhanced_enums.enableString,
Feature.super_parameters.enableString,
],
),
);
return FindNode(parseResult.content, parseResult.unit);
}
}