blob: de817967c175d05f1478c8e2d1e2a0f3651a682f [file] [log] [blame]
// 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.
part of '../api.dart';
/// The base class representing an arbitrary chunk of Dart code, which may or
/// may not be syntactically or semantically valid yet.
class Code {
/// All the chunks of [Code], raw [String]s, or [Identifier]s that
/// comprise this [Code] object.
final List<Object> parts;
/// Can be used to more efficiently detect the kind of code, avoiding is
/// checks and enabling switch statements.
CodeKind get kind => CodeKind.raw;
Code.fromString(String code) : parts = [code];
Code.fromParts(this.parts)
: assert(parts.every((element) =>
element is String || element is Code || element is Identifier));
}
/// A piece of code representing a syntactically valid declaration.
class DeclarationCode extends Code {
@override
CodeKind get kind => CodeKind.declaration;
DeclarationCode.fromString(String code) : super.fromString(code);
DeclarationCode.fromParts(List<Object> parts) : super.fromParts(parts);
}
/// A piece of code representing a syntactically valid expression.
class ExpressionCode extends Code {
@override
CodeKind get kind => CodeKind.expression;
ExpressionCode.fromString(String code) : super.fromString(code);
ExpressionCode.fromParts(List<Object> parts) : super.fromParts(parts);
}
/// A piece of code representing a syntactically valid function body.
///
/// This includes any and all code after the parameter list of a function,
/// including modifiers like `async`.
///
/// Both arrow and block function bodies are allowed.
class FunctionBodyCode extends Code {
@override
CodeKind get kind => CodeKind.functionBody;
FunctionBodyCode.fromString(String code) : super.fromString(code);
FunctionBodyCode.fromParts(List<Object> parts) : super.fromParts(parts);
}
/// A piece of code identifying a syntactically valid function parameter.
///
/// There is no distinction here made between named and positional parameters.
///
/// It is the job of the user to construct and combine these together in a way
/// that creates valid parameter lists.
class ParameterCode implements Code {
final Code? defaultValue;
final List<String> keywords;
final String name;
final TypeAnnotationCode? type;
@override
CodeKind get kind => CodeKind.parameter;
@override
List<Object> get parts => [
if (keywords.isNotEmpty) ...[
...keywords.joinAsCode(' '),
' ',
],
if (type != null) ...[
type!,
' ',
],
name,
if (defaultValue != null) ...[
' = ',
defaultValue!,
]
];
ParameterCode({
this.defaultValue,
this.keywords = const [],
required this.name,
this.type,
});
}
/// A piece of code representing a type annotation.
abstract class TypeAnnotationCode implements Code {
/// Returns a [TypeAnnotationCode] object which is a non-nullable version
/// of this one.
///
/// Returns the current instance if it is already non-nullable.
TypeAnnotationCode get asNonNullable => this;
/// Returns a [TypeAnnotationCode] object which is a non-nullable version
/// of this one.
///
/// Returns the current instance if it is already nullable.
NullableTypeAnnotationCode get asNullable =>
new NullableTypeAnnotationCode(this);
/// Whether or not this type is nullable.
bool get isNullable => false;
}
/// The nullable version of an underlying type annotation.
class NullableTypeAnnotationCode implements TypeAnnotationCode {
/// The underlying type that is being made nullable.
TypeAnnotationCode underlyingType;
@override
CodeKind get kind => CodeKind.nullableTypeAnnotation;
@override
List<Object> get parts => [...underlyingType.parts, '?'];
/// Creates a nullable [underlyingType] annotation.
///
/// If [underlyingType] is a NullableTypeAnnotationCode, returns that
/// same type.
NullableTypeAnnotationCode(this.underlyingType);
@override
TypeAnnotationCode get asNonNullable => underlyingType;
@override
NullableTypeAnnotationCode get asNullable => this;
@override
bool get isNullable => true;
}
/// A piece of code representing a reference to a named type.
class NamedTypeAnnotationCode extends TypeAnnotationCode {
final Identifier name;
final List<TypeAnnotationCode> typeArguments;
@override
CodeKind get kind => CodeKind.namedTypeAnnotation;
@override
List<Object> get parts => [
name,
if (typeArguments.isNotEmpty) ...[
'<',
...typeArguments.joinAsCode(', '),
'>',
],
];
NamedTypeAnnotationCode({required this.name, this.typeArguments = const []});
}
/// A piece of code representing a function type annotation.
class FunctionTypeAnnotationCode extends TypeAnnotationCode {
final List<ParameterCode> namedParameters;
final List<ParameterCode> positionalParameters;
final TypeAnnotationCode? returnType;
final List<TypeParameterCode> typeParameters;
@override
CodeKind get kind => CodeKind.functionTypeAnnotation;
@override
List<Object> get parts => [
if (returnType != null) returnType!,
' Function',
if (typeParameters.isNotEmpty) ...[
'<',
...typeParameters.joinAsCode(', '),
'>',
],
'(',
for (ParameterCode positional in positionalParameters) ...[
positional,
', ',
],
if (namedParameters.isNotEmpty) ...[
'{',
for (ParameterCode named in namedParameters) ...[
named,
', ',
],
'}',
],
')',
];
FunctionTypeAnnotationCode({
this.namedParameters = const [],
this.positionalParameters = const [],
this.returnType,
this.typeParameters = const [],
});
}
class OmittedTypeAnnotationCode extends TypeAnnotationCode {
final OmittedTypeAnnotation typeAnnotation;
OmittedTypeAnnotationCode(this.typeAnnotation);
@override
CodeKind get kind => CodeKind.omittedTypeAnnotation;
@override
List<Object> get parts => [typeAnnotation];
}
/// A piece of code representing a valid named type parameter.
class TypeParameterCode implements Code {
final TypeAnnotationCode? bound;
final String name;
@override
CodeKind get kind => CodeKind.typeParameter;
@override
List<Object> get parts => [
name,
if (bound != null) ...[
' extends ',
bound!,
]
];
TypeParameterCode({this.bound, required this.name});
}
extension Join<T extends Object> on List<T> {
/// Joins all the items in [this] with [separator], and returns
/// a new list.
List<Object> joinAsCode(String separator) => [
for (int i = 0; i < length - 1; i++) ...[
this[i],
separator,
],
if (isNotEmpty) last,
];
}
enum CodeKind {
declaration,
expression,
functionBody,
functionTypeAnnotation,
namedTypeAnnotation,
nullableTypeAnnotation,
omittedTypeAnnotation,
parameter,
raw,
typeParameter,
}