// Copyright (c) 2017, 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. import 'package:_fe_analyzer_shared/src/messages/codes.dart' show LocatedMessage, Message, codeExperimentNotEnabled, codeInternalProblemUnsupported; import 'package:_fe_analyzer_shared/src/parser/experimental_features.dart' show DefaultExperimentalFeatures; import 'package:_fe_analyzer_shared/src/parser/parser.dart' hide ExperimentalFeatures; import 'package:_fe_analyzer_shared/src/parser/stack_listener.dart'; import 'package:_fe_analyzer_shared/src/scanner/token.dart'; import 'package:analyzer/src/dart/analysis/experiments.dart'; import 'package:pub_semver/pub_semver.dart'; /// "Mini AST" representation of a declaration which can accept annotations. class AnnotatedNode { final Comment? documentationComment; final List metadata; AnnotatedNode(this.documentationComment, List? metadata) : metadata = metadata ?? const []; } /// "Mini AST" representation of an annotation. class Annotation { final String name; final String? constructorName; final List? arguments; Annotation(this.name, this.constructorName, this.arguments); } /// "Mini AST" representation of a class declaration. class ClassDeclaration extends CompilationUnitMember { final String name; final TypeName? superclass; final List members; ClassDeclaration( super.documentationComment, super.metadata, this.name, this.superclass, this.members, ); } /// "Mini AST" representation of a class member. class ClassMember extends AnnotatedNode { ClassMember(super.documentationComment, super.metadata); } /// "Mini AST" representation of a comment. class Comment { final bool isDocumentation; final List tokens; factory Comment(Token commentToken) { var tokens = []; bool isDocumentation = false; Token? token = commentToken; while (token != null) { if (token.lexeme.startsWith('/**') || token.lexeme.startsWith('///')) { isDocumentation = true; } tokens.add(token); token = token.next; } return Comment._(isDocumentation, tokens); } Comment._(this.isDocumentation, this.tokens); } /// "Mini AST" representation of a CompilationUnit. class CompilationUnit { final declarations = []; } /// "Mini AST" representation of a top level member of a compilation unit. class CompilationUnitMember extends AnnotatedNode { CompilationUnitMember(super.documentationComment, super.metadata); } /// "Mini AST" representation of a constructor declaration. class ConstructorDeclaration extends ClassMember { final String name; ConstructorDeclaration(super.documentationComment, super.metadata, this.name); } /// "Mini AST" representation of an individual enum constant in an enum /// declaration. class EnumConstantDeclaration extends AnnotatedNode { final String name; EnumConstantDeclaration( super.documentationComment, super.metadata, this.name, ); } /// "Mini AST" representation of an enum declaration. class EnumDeclaration extends CompilationUnitMember { final String name; final List constants; EnumDeclaration( super.documentationComment, super.metadata, this.name, this.constants, ); } /// "Mini AST" representation of an expression. class Expression { String toCode() { throw UnimplementedError('$runtimeType'); } } /// "Mini AST" representation of an integer literal. class IntegerLiteral extends Expression { final int value; IntegerLiteral(this.value); } /// "Mini AST" representation of a list literal. class ListLiteral extends Expression { final Token leftBracket; final List elements; final Token rightBracket; ListLiteral(this.leftBracket, this.elements, this.rightBracket); @override String toCode() { return '[${elements.map((e) => e.toCode()).join(', ')}]'; } } /// "Mini AST" representation of a method declaration. class MethodDeclaration extends ClassMember { final bool isGetter; final String name; final TypeName? returnType; MethodDeclaration( super.documentationComment, super.metadata, this.isGetter, this.name, this.returnType, ); } /// Parser listener which generates a "mini AST" representation of the source /// code. This representation is just sufficient for summary code generation. class MiniAstBuilder extends StackListener { bool inMetadata = false; final compilationUnit = CompilationUnit(); @override bool get isDartLibrary => throw UnimplementedError(); @override Uri get uri => throw UnimplementedError(); @override void addProblem( Message message, int charOffset, int length, { bool wasHandled = false, List? context, }) { internalProblem(message, charOffset, uri); } @override void beginMetadata(Token token) { inMetadata = true; } @override void beginMetadataStar(Token token) { debugEvent("beginMetadataStar"); var precedingComments = token.precedingComments; if (precedingComments != null) { push(Comment(precedingComments)); } else { push(NullValues.Comments); } } @override void endArguments(int count, Token beginToken, Token endToken) { debugEvent("Arguments"); push(popList(count, List.filled(count, null, growable: true))); } @override void endBinaryExpression(Token token, Token endToken) { debugEvent("BinaryExpression"); pop(); // RHS pop(); // LHS push(UnknownExpression()); } @override void endClassDeclaration(Token beginToken, Token endToken) { debugEvent("ClassDeclaration"); var members = popTypedList() ?? []; var superclass = pop() as TypeName?; pop(); // Type variables var name = pop() as String; var metadata = popTypedList(); var comment = pop() as Comment?; compilationUnit.declarations.add( ClassDeclaration(comment, metadata, name, superclass, members), ); } @override void endClassOrMixinOrExtensionBody( DeclarationKind kind, int memberCount, Token beginToken, Token endToken, ) { debugEvent("ClassOrMixinBody"); push( popList( memberCount, List.filled(memberCount, null, growable: true), ), ); } @override void endCombinators(int count) { debugEvent("Combinators"); } @override void endConditionalUris(int count) { debugEvent("ConditionalUris"); if (count != 0) { internalProblem( codeInternalProblemUnsupported.withArgumentsOld("Conditional URIs"), -1, null, ); } } @override void endConstructorReference( Token start, Token? periodBeforeName, Token endToken, ConstructorReferenceContext constructorReferenceContext, ) { debugEvent("ConstructorReference"); } @override void endEnumDeclaration( Token beginToken, Token enumKeyword, Token leftBrace, int memberCount, Token endToken, ) { debugEvent("Enum"); } @override void endFactory( DeclarationKind kind, Token beginToken, Token factoryKeyword, Token endToken, ) { debugEvent("Factory"); pop(); // Body pop(); // Type variables var name = pop() as String; var metadata = popTypedList() as List?; var comment = pop() as Comment?; push(ConstructorDeclaration(comment, metadata, name)); } @override void endFieldInitializer(Token assignment, Token token) { debugEvent("FieldInitializer"); pop(); // Expression } @override void endFormalParameter( Token? varOrFinal, Token? thisKeyword, Token? superKeyword, Token? periodAfterThisOrSuper, Token nameToken, Token? initializerStart, Token? initializerEnd, FormalParameterKind kind, MemberKind memberKind, ) { debugEvent("FormalParameter"); pop(); // Name pop(); // Type pop(); // Metadata pop(); // Comment } @override void endFormalParameters( int count, Token beginToken, Token endToken, MemberKind kind, ) { debugEvent("FormalParameters"); } @override void endImport(Token importKeyword, Token? augmentToken, Token? semicolon) { debugEvent("Import"); pop(NullValues.Prefix); // Prefix identifier pop(); // URI pop(); // Metadata pop(); // Comment } @override void endLibraryName(Token libraryKeyword, Token semicolon, bool hasName) { debugEvent("LibraryName"); if (hasName) { pop(); // Library name } pop(); // Metadata pop(); // Comment } @override void endLiteralString(int interpolationCount, Token endToken) { super.endLiteralString(interpolationCount, endToken); var value = pop() as String; push(StringLiteral(value)); } @override void endMember() { debugEvent("Member"); } @override void endMetadata(Token beginToken, Token? periodBeforeName, Token endToken) { debugEvent("Metadata"); inMetadata = false; var arguments = pop() as List?; var constructorName = popIfNotNull(periodBeforeName) as String?; pop(); // Type arguments var name = pop() as String; push(Annotation(name, constructorName, arguments?.cast())); } @override void endMetadataStar(int count) { debugEvent("MetadataStar"); push( popList(count, List.filled(count, null, growable: true)) ?? NullValues.Metadata, ); } @override void endMethod( DeclarationKind kind, Token? getOrSet, Token beginToken, Token beginParam, Token? beginInitializers, Token endToken, ) { debugEvent("Method"); pop(); // Body pop(); // Initializers pop(); // Formal parameters pop(); // Type variables var name = pop() as String; var returnType = pop() as TypeName?; var metadata = popTypedList(); var comment = pop() as Comment?; push( MethodDeclaration( comment, metadata, getOrSet?.lexeme == 'get', name, returnType, ), ); } @override void endShow(Token showKeyword) { debugEvent("Show"); pop(); // Shown names } @override void endTopLevelFields( Token? augmentToken, Token? externalToken, Token? staticToken, Token? covariantToken, Token? lateToken, Token? varFinalOrConst, int count, Token beginToken, Token endToken, ) { // We ignore top level variable declarations; they are present just to make // the IDL analyze without warnings. debugEvent("TopLevelFields"); popList(count, List.filled(count, null, growable: true)); // Fields pop(); // Type pop(); // Metadata pop(); // Comment } @override void endTypeArguments(int count, Token beginToken, Token endToken) { debugEvent("TypeArguments"); push(popList(count, List.filled(count, null, growable: true))); } @override void handleAsyncModifier(Token? asyncToken, Token? starToken) { debugEvent("AsyncModifier"); } @override void handleCascadeAccess(Token token, Token endToken, bool isNullAware) { debugEvent("CascadeAccess"); pop(); // RHS pop(); // LHS push(UnknownExpression()); } @override void handleClassNoWithClause() { debugEvent("NoClassWithClause"); } @override void handleClassWithClause(Token withKeyword) { debugEvent("ClassWithClause"); } @override void handleDotAccess(Token token, Token endToken, bool isNullAware) { debugEvent("DotAccess"); if (!isNullAware) { var rightOperand = pop() as String; var leftOperand = pop(); if (leftOperand is String && !leftOperand.contains('.')) { push(PrefixedIdentifier(leftOperand, token, rightOperand)); } else { push(UnknownExpression()); } } else { pop(); // RHS pop(); // LHS push(UnknownExpression()); } } @override void handleEnumElement(Token beginToken, Token? augmentToken) { debugEvent("EnumElement"); pop(); // Arguments. pop(); // Type arguments. } @override void handleEnumElements(Token endToken, int count) { debugEvent("EnumElements"); var constants = List.filled( count, null, growable: true, ); popList(count, constants); pop(); // Type variables. var name = pop() as String; var metadata = popTypedList(); var comment = pop() as Comment?; compilationUnit.declarations.add( EnumDeclaration(comment, metadata, name, constants.nonNulls.toList()), ); } @override void handleEnumHeader( Token? augmentToken, Token enumKeyword, Token leftBrace, ) { debugEvent("EnumHeader"); } @override void handleEnumNoWithClause() { debugEvent("NoEnumWithClause"); } @override void handleFormalParameterWithoutValue(Token token) { debugEvent("FormalParameterWithoutValue"); } @override void handleFunctionBodySkipped(Token token, bool isExpressionBody) { if (isExpressionBody) pop(); push(NullValues.FunctionBody); } @override void handleIdentifier(Token token, IdentifierContext context) { if (context == IdentifierContext.enumValueDeclaration) { var metadata = popTypedList() as List?; var comment = pop() as Comment?; push(EnumConstantDeclaration(comment, metadata, token.lexeme)); } else { push(token.lexeme); } } @override void handleIdentifierList(int count) { debugEvent("IdentifierList"); push(popList(count, List.filled(count, null, growable: true))); } @override void handleImportPrefix(Token? deferredKeyword, Token? asKeyword) { debugEvent("ImportPrefix"); pushIfNull(asKeyword, NullValues.Prefix); } @override void handleInvalidMember(Token endToken) { debugEvent("InvalidMember"); pop(); // metadata star } @override void handleInvalidTopLevelDeclaration(Token endToken) { debugEvent("InvalidTopLevelDeclaration"); pop(); // metadata star } @override void handleInvalidTypeArguments(Token token) { debugEvent("InvalidTypeArguments"); pop(NullValues.TypeArguments); } @override void handleLiteralInt(Token token) { debugEvent("LiteralInt"); push(IntegerLiteral(int.parse(token.lexeme))); } @override void handleLiteralList( int count, Token leftBracket, Token? constKeyword, Token rightBracket, ) { debugEvent("LiteralList"); var elements = List.filled(count, null); popList(count, elements); pop(); // type arguments push( ListLiteral(leftBracket, List.from(elements), rightBracket), ); } @override void handleLiteralNull(Token token) { debugEvent("LiteralNull"); push(UnknownExpression()); } @override void handleNamedArgument(Token colon) { var expression = pop() as Expression; var name = pop() as String; push(NamedExpression(name, colon, expression)); } @override void handleNamedMixinApplicationWithClause(Token withKeyword) { debugEvent("NamedMixinApplicationWithClause"); } @override // TODO(jensj): Handle directly. void handleNamedRecordField(Token colon) => handleNamedArgument(colon); @override void handleNativeClause(Token nativeToken, bool hasName) { debugEvent("NativeClause"); if (hasName) { pop(); // Pop the native name which is a StringLiteral. } } @override void handleNativeFunctionBodyIgnored(Token nativeToken, Token semicolon) { debugEvent("NativeFunctionBodyIgnored"); } @override void handleNativeFunctionBodySkipped(Token nativeToken, Token semicolon) { debugEvent("NativeFunctionBodySkipped"); push(NullValues.FunctionBody); } @override void handleNonNullAssertExpression(Token bang) { reportNonNullAssertExpressionNotEnabled(bang); } @override void handleNoTypeNameInConstructorReference(Token token) { debugEvent("NoTypeNameInConstructorReference"); } @override void handleQualified(Token period) { debugEvent("Qualified"); var suffix = pop() as String; var prefix = pop() as String; push('$prefix.$suffix'); } @override void handleRecoverDeclarationHeader(DeclarationHeaderKind kind) { pop(); // superclass } @override void handleRecoverImport(Token? semicolon) { debugEvent("RecoverImport"); pop(NullValues.Prefix); // Prefix identifier } @override void handleSend(Token beginToken, Token endToken) { debugEvent("Send"); var arguments = pop(); pop(); // Type arguments if (arguments != null) { pop(); // Receiver push(UnknownExpression()); } else { // Property get. } } @override void handleType(Token beginToken, Token? questionMark) { debugEvent("Type"); // reportErrorIfNullableType(questionMark); var typeArguments = popTypedList(); var name = pop() as String; push(TypeName(name, typeArguments)); } @override internalProblem(Message message, int charOffset, Uri? uri) { throw UnsupportedError(message.problemMessage); } List? popList(int n, List list) { if (n == 0) return null; return stack.popList(n, list, null); } /// Calls [pop] and creates a list with the appropriate type parameter `T` /// from the resulting `List`. List? popTypedList() { var list = pop() as List?; return list != null ? List.from(list) : null; } void reportErrorIfNullableType(Token? questionMark) { if (questionMark != null) { assert(optional('?', questionMark)); var feature = ExperimentalFeatures.non_nullable; handleRecoverableError( codeExperimentNotEnabled.withArgumentsOld( feature.enableString, _versionAsString(ExperimentStatus.currentVersion), ), questionMark, questionMark, ); } } void reportNonNullAssertExpressionNotEnabled(Token bang) { var feature = ExperimentalFeatures.non_nullable; handleRecoverableError( codeExperimentNotEnabled.withArgumentsOld( feature.enableString, _versionAsString(ExperimentStatus.currentVersion), ), bang, bang, ); } static String _versionAsString(Version version) { return '${version.major}.${version.minor}.${version.patch}'; } } /// Parser intended for use with [MiniAstBuilder]. class MiniAstParser extends Parser { MiniAstParser(MiniAstBuilder super.listener) : super(experimentalFeatures: const DefaultExperimentalFeatures()); @override Token parseArgumentsOpt(Token token) { var listener = this.listener as MiniAstBuilder; if (listener.inMetadata) { return super.parseArgumentsOpt(token); } else { return skipArgumentsOpt(token); } } @override Token parseFunctionBody(Token token, bool isExpression, bool allowAbstract) { return skipFunctionBody(token, isExpression, allowAbstract); } @override Token parseInvalidBlock(Token token) => skipBlock(token); } /// "Mini AST" representation of a named expression. class NamedExpression extends Expression { final String name; final Token colon; final Expression expression; NamedExpression(this.name, this.colon, this.expression); } /// "Mini AST" representation of a named expression. class PrefixedIdentifier extends Expression { final String prefix; final Token operator; final String identifier; PrefixedIdentifier(this.prefix, this.operator, this.identifier); @override String toCode() { return '$prefix.$identifier'; } } /// "Mini AST" representation of a string literal. class StringLiteral extends Expression { final String stringValue; StringLiteral(this.stringValue); } /// "Mini AST" representation of a type name. class TypeName { final String name; final List? typeArguments; TypeName(this.name, this.typeArguments); } /// "Mini AST" representation of an expression which summary code generation /// need not be concerned about. class UnknownExpression extends Expression {}