| // 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. |
| sealed class Code { |
| /// All the chunks of [Code], raw [String]s, [Identifier]s, or |
| /// [OmittedTypeAnnotation]s that comprise this [Code] object. |
| /// |
| /// Note that [OmittedTypeAnnotation] objects can only be provided through |
| /// the [OmittedTypeAnnotationCode] wrapper instance, but will appear in |
| /// the [parts] of those, so they must be handled whenever iterating [parts]. |
| 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; |
| |
| Code.fromString(String code) : parts = [code]; |
| |
| Code.fromParts(this.parts) { |
| for (final part in parts) { |
| switch (part) { |
| case Code(): |
| case Identifier(): |
| case String(): |
| break; // OK |
| default: |
| throw StateError('Unrecognized code part ${part.runtimeType}'); |
| } |
| } |
| } |
| } |
| |
| /// An arbitrary chunk of code, which does not have to be syntactically valid |
| /// on its own. Useful to construct other types of code from several parts. |
| final class RawCode extends Code { |
| @override |
| CodeKind get kind => CodeKind.raw; |
| |
| RawCode.fromString(super.code) : super.fromString(); |
| |
| RawCode.fromParts(super.parts) : super.fromParts(); |
| } |
| |
| /// A piece of code representing a syntactically valid declaration. |
| final class DeclarationCode extends Code { |
| @override |
| CodeKind get kind => CodeKind.declaration; |
| |
| DeclarationCode.fromString(super.code) : super.fromString(); |
| |
| DeclarationCode.fromParts(super.parts) : super.fromParts(); |
| } |
| |
| /// A piece of code representing a code comment. This may contain identifier |
| /// references inside of `[]` brackets if the comments are doc comments. |
| final class CommentCode extends Code { |
| @override |
| CodeKind get kind => CodeKind.comment; |
| |
| CommentCode.fromString(super.code) : super.fromString(); |
| |
| CommentCode.fromParts(super.parts) : super.fromParts(); |
| } |
| |
| /// A piece of code representing a syntactically valid expression. |
| final class ExpressionCode extends Code { |
| @override |
| CodeKind get kind => CodeKind.expression; |
| |
| ExpressionCode.fromString(super.code) : super.fromString(); |
| |
| ExpressionCode.fromParts(super.parts) : super.fromParts(); |
| } |
| |
| /// 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. |
| final class FunctionBodyCode extends Code { |
| @override |
| CodeKind get kind => CodeKind.functionBody; |
| |
| FunctionBodyCode.fromString(super.code) : super.fromString(); |
| |
| FunctionBodyCode.fromParts(super.parts) : super.fromParts(); |
| } |
| |
| /// A piece of code identifying a syntactically valid function or function type |
| /// parameter. |
| /// |
| /// There is no distinction here made between named and positional parameters. |
| /// |
| /// There is also no distinction between function type parameters and normal |
| /// function parameters, so the [name] is nullable (it is not required for |
| /// positional function type parameters). |
| /// |
| /// It is the job of the user to construct and combine these together in a way |
| /// that creates valid parameter lists. |
| final class ParameterCode implements Code { |
| /// Optional default value for this parameter (not including the ` = `). |
| final Code? defaultValue; |
| |
| /// Any keywords to put on this parameter (before the type). |
| final List<String> keywords; |
| |
| /// Optional name of this parameter (can only be omitted for function types). |
| final String? name; |
| |
| /// Optional type for this parameter. |
| final TypeAnnotationCode? type; |
| |
| /// The type of parameter this is. |
| final ParameterStyle style; |
| |
| @override |
| CodeKind get kind => CodeKind.parameter; |
| |
| @override |
| List<Object> get parts => [ |
| if (keywords.isNotEmpty) ...[ |
| ...keywords.joinAsCode(' '), |
| ' ', |
| ], |
| if (type != null) ...[ |
| type!, |
| ' ', |
| ], |
| if (style == ParameterStyle.fieldFormal) |
| 'this.' |
| else if (style == ParameterStyle.superFormal) |
| 'super.', |
| if (name != null) name!, |
| if (defaultValue != null) ...[ |
| ' = ', |
| defaultValue!, |
| ] |
| ]; |
| |
| ParameterCode({ |
| this.defaultValue, |
| this.keywords = const [], |
| this.name, |
| this.style = ParameterStyle.normal, |
| this.type, |
| }); |
| } |
| |
| /// A piece of code representing a type annotation. |
| sealed class TypeAnnotationCode implements Code, TypeAnnotation { |
| @override |
| TypeAnnotationCode get code => this; |
| |
| /// 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 => NullableTypeAnnotationCode(this); |
| |
| /// Whether or not this type is nullable. |
| @override |
| bool get isNullable => false; |
| } |
| |
| /// The nullable version of an underlying type annotation. |
| final class NullableTypeAnnotationCode implements TypeAnnotationCode { |
| /// The underlying type that is being made nullable. |
| TypeAnnotationCode underlyingType; |
| |
| @override |
| TypeAnnotationCode get code => this; |
| |
| @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. |
| final 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. |
| final class FunctionTypeAnnotationCode extends TypeAnnotationCode { |
| final List<ParameterCode> namedParameters; |
| |
| final List<ParameterCode> optionalPositionalParameters; |
| |
| 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 (optionalPositionalParameters.isNotEmpty) ...[ |
| '[', |
| for (ParameterCode optional in optionalPositionalParameters) ...[ |
| optional, |
| ', ', |
| ], |
| ']', |
| ], |
| if (namedParameters.isNotEmpty) ...[ |
| '{', |
| for (ParameterCode named in namedParameters) ...[ |
| named, |
| ', ', |
| ], |
| '}', |
| ], |
| ')', |
| ]; |
| |
| FunctionTypeAnnotationCode({ |
| this.namedParameters = const [], |
| this.optionalPositionalParameters = const [], |
| this.positionalParameters = const [], |
| this.returnType, |
| this.typeParameters = const [], |
| }); |
| } |
| |
| /// A piece of code identifying a syntactically valid record field declaration. |
| /// This is only usable in the context of [RecordTypeAnnotationCode] objects. |
| /// |
| /// There is no distinction here made between named and positional fields. |
| /// |
| /// The name is not required because it is optional for positional fields. |
| /// |
| /// It is the job of the user to construct and combine these together in a way |
| /// that creates valid record type annotations. |
| final class RecordFieldCode implements Code { |
| final String? name; |
| final TypeAnnotationCode type; |
| |
| @override |
| CodeKind get kind => CodeKind.recordField; |
| |
| @override |
| List<Object> get parts => [ |
| type, |
| if (name != null) ' ${name!}', |
| ]; |
| |
| RecordFieldCode({ |
| this.name, |
| required this.type, |
| }); |
| } |
| |
| /// A piece of code representing a syntactically valid record type annotation. |
| final class RecordTypeAnnotationCode extends TypeAnnotationCode { |
| final List<RecordFieldCode> namedFields; |
| |
| final List<RecordFieldCode> positionalFields; |
| |
| @override |
| CodeKind get kind => CodeKind.recordTypeAnnotation; |
| |
| @override |
| List<Object> get parts => [ |
| '(', |
| if (positionalFields.isNotEmpty) |
| for (RecordFieldCode positional in positionalFields) ...[ |
| if (positional != positionalFields.first) ', ', |
| positional, |
| ], |
| if (namedFields.isNotEmpty) ...[ |
| if (positionalFields.isNotEmpty) ', ', |
| '{', |
| for (RecordFieldCode named in namedFields) ...[ |
| if (named != namedFields.first) ', ', |
| named, |
| ], |
| '}', |
| ], |
| ')', |
| ]; |
| |
| RecordTypeAnnotationCode({ |
| this.namedFields = const [], |
| this.positionalFields = const [], |
| }); |
| } |
| |
| final class OmittedTypeAnnotationCode extends TypeAnnotationCode { |
| final OmittedTypeAnnotation typeAnnotation; |
| |
| OmittedTypeAnnotationCode(this.typeAnnotation); |
| |
| @override |
| CodeKind get kind => CodeKind.omittedTypeAnnotation; |
| |
| @override |
| List<Object> get parts => [typeAnnotation]; |
| } |
| |
| /// Raw type annotations are typically used to refer to a local type which you |
| /// do not have an [Identifier] for (possibly you just created it). |
| /// |
| /// Whenever possible, use a more specific [TypeAnnotationCode] subtype. |
| final class RawTypeAnnotationCode extends RawCode |
| implements TypeAnnotationCode { |
| @override |
| CodeKind get kind => CodeKind.rawTypeAnnotation; |
| |
| /// Returns a [TypeAnnotationCode] object which is a non-nullable version |
| /// of this one. |
| /// |
| /// Returns the current instance if it is already non-nullable. |
| @override |
| 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. |
| @override |
| NullableTypeAnnotationCode get asNullable => NullableTypeAnnotationCode(this); |
| |
| RawTypeAnnotationCode._(super.parts) : super.fromParts(); |
| |
| /// Creates a [TypeAnnotationCode] from a raw [String]. |
| /// |
| /// The [code] object must not have trailing whitespace. |
| static TypeAnnotationCode fromString(String code) => fromParts([code]); |
| |
| /// Creates a [TypeAnnotationCode] from a raw code [parts]. |
| /// |
| /// Must not end in trailing whitespace. |
| static TypeAnnotationCode fromParts(List<Object> parts) { |
| bool wasNullable; |
| (wasNullable, parts) = _makeNonNullable(parts); |
| TypeAnnotationCode code = RawTypeAnnotationCode._(parts); |
| if (wasNullable) code = code.asNullable; |
| return code; |
| } |
| |
| @override |
| TypeAnnotationCode get code => this; |
| |
| @override |
| bool get isNullable => false; |
| |
| /// Checks if [parts] ends with a ?, and if so then it is removed. |
| /// |
| /// Returns a record which indicates if [parts] was nullable originally, as |
| /// well as the potentially new list of parts. |
| /// |
| /// Throws if [parts] ends with whitespace because we don't allow type |
| /// annotations to do that. |
| static (bool wasNullable, List<Object> parts) _makeNonNullable( |
| List<Object> parts) { |
| final Iterator<Object> iterator = parts.reversed.iterator; |
| while (iterator.moveNext()) { |
| final Object current = iterator.current; |
| switch (current) { |
| case String(): |
| if (current.trimRight() != current) { |
| throw ArgumentError( |
| 'Invalid type annotation, type annotations should not end with ' |
| 'whitespace but got `$current`.'); |
| } else if (current.isEmpty) { |
| continue; |
| } else if (current.endsWith('?')) { |
| // It was nullable, trim the `?` and return a copy. |
| return ( |
| true, |
| // We are iterating backwards, and need to reverse it after. |
| [ |
| // Strip the '?'. |
| current.substring(0, current.length - 1), |
| for (bool hasNext = iterator.moveNext(); |
| hasNext; |
| hasNext = iterator.moveNext()) |
| iterator.current, |
| ].reversed.toList(), |
| ); |
| } else { |
| return (false, parts); |
| } |
| case Identifier(): |
| // Identifiers never contain a `?`. |
| return (false, parts); |
| } |
| } |
| throw ArgumentError('The empty string is not a valid type annotation.'); |
| } |
| } |
| |
| /// A piece of code representing a valid named type parameter. |
| final 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 [Join] with [separator], and returns a new |
| /// list. |
| /// |
| /// Works on any kind of non-nullable list which accepts String entries, and |
| /// does not convert the individual items to strings. |
| List<Object> joinAsCode(String separator) => [ |
| for (int i = 0; i < length - 1; i++) ...[ |
| this[i], |
| separator, |
| ], |
| if (isNotEmpty) last, |
| ]; |
| } |
| |
| enum CodeKind { |
| comment, |
| declaration, |
| expression, |
| functionBody, |
| functionTypeAnnotation, |
| namedTypeAnnotation, |
| nullableTypeAnnotation, |
| omittedTypeAnnotation, |
| parameter, |
| raw, |
| rawTypeAnnotation, |
| recordField, |
| recordTypeAnnotation, |
| typeParameter, |
| } |