| // Copyright (c) 2025, 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 'dart:io' as io; |
| |
| import 'package:analysis_server_client/protocol.dart' hide Element; |
| import 'package:analysis_server_client/server.dart'; |
| import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/nullability_suffix.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:analyzer/src/dart/ast/ast.dart'; |
| import 'package:analyzer_testing/package_root.dart' as pkg_root; |
| import 'package:collection/collection.dart'; |
| import 'package:path/path.dart'; |
| |
| Future<void> main() async { |
| await _Generator().generate(); |
| } |
| |
| class _Generator { |
| late String newCode; |
| late ClassElement parameterKindClass; |
| late ClassElement currentClassElement; |
| List<_ImplClass> implClasses = []; |
| |
| Future<void> generate() async { |
| var astUnitResult = await _getAstResolvedUnit(); |
| |
| newCode = astUnitResult.content; |
| |
| var utilitiesLibraryResult = await astUnitResult.session.getLibraryByUri( |
| 'package:analyzer/src/generated/utilities_dart.dart', |
| ); |
| utilitiesLibraryResult as LibraryElementResult; |
| parameterKindClass = |
| utilitiesLibraryResult.element2.getClass2('ParameterKind')!; |
| |
| await _buildImplClasses(astUnitResult); |
| _removeGeneratedMembers(astUnitResult); |
| _generateAllClassMembers(); |
| |
| var astPath = _getAstPath(); |
| newCode = await _formatSortCode(astPath, newCode); |
| |
| io.File(astPath).writeAsStringSync(newCode); |
| } |
| |
| Future<_ImplClass?> _buildImplClass(ClassDeclarationImpl nodeImpl) async { |
| var classElement = nodeImpl.declaredFragment!.element; |
| var generateObject = |
| classElement.metadata.annotations |
| .map((annotation) { |
| var generateObject = annotation.computeConstantValue(); |
| var generateObjectType = generateObject?.type; |
| if (generateObjectType?.element3?.name3 != 'GenerateNodeImpl') { |
| return null; |
| } |
| return generateObject; |
| }) |
| .nonNulls |
| .firstOrNull; |
| if (generateObject == null) { |
| return null; |
| } |
| |
| var entitiesField = generateObject.getField('childEntitiesOrder'); |
| if (entitiesField == null) { |
| return null; |
| } |
| |
| var entities = entitiesField.toListValue(); |
| if (entities == null) { |
| return null; |
| } |
| |
| currentClassElement = classElement; |
| var interfaceElement = classElement.interfaces.last.element3; |
| |
| var inheritanceManager = classElement.inheritanceManager; |
| |
| var properties = |
| entities |
| .map((entity) { |
| var propertyName = entity.getField('name')!.toStringValue()!; |
| var isSuper = entity.getField('isSuper')!.toBoolValue()!; |
| var withOverride = |
| entity.getField('withOverride')!.toBoolValue()!; |
| var isTokenFinal = |
| entity.getField('isTokenFinal')!.toBoolValue()!; |
| var superNullAssertOverride = |
| entity.getField('superNullAssertOverride')!.toBoolValue()!; |
| var tokenGroupId = entity.getField('tokenGroupId')!.toIntValue(); |
| var type = entity.getField('type')!.toTypeValue(); |
| |
| if (type == null) { |
| var member = inheritanceManager.getMember4( |
| interfaceElement, |
| Name(null, propertyName), |
| ); |
| if (member case GetterElement getter) { |
| type = getter.returnType; |
| } else { |
| throw StateError('$propertyName: ${member.runtimeType}'); |
| } |
| } |
| type as InterfaceType; |
| |
| var kind = _PropertyTypeKind.fromType(type); |
| if (kind is _PropertyTypeKindToken) { |
| kind.isWritable = !isTokenFinal; |
| kind.groupId = tokenGroupId; |
| } |
| return _Property( |
| name: propertyName, |
| isSuper: isSuper, |
| withOverride: withOverride, |
| withOverrideSuperNotNull: superNullAssertOverride, |
| type: type, |
| typeKind: kind, |
| ); |
| }) |
| .nonNulls |
| .toList(); |
| |
| return _ImplClass( |
| node: nodeImpl, |
| interfaceElement: interfaceElement, |
| properties: properties, |
| leftBracketOffset: nodeImpl.leftBracket.offset, |
| ); |
| } |
| |
| Future<void> _buildImplClasses(ResolvedUnitResult astUnitResult) async { |
| for (var nodeImpl in astUnitResult.unit.declarations) { |
| if (nodeImpl is ClassDeclarationImpl) { |
| var implClass = await _buildImplClass(nodeImpl); |
| if (implClass != null) { |
| implClasses.add(implClass); |
| } |
| } |
| } |
| } |
| |
| void _generateAccept(_ImplClass implClass, StringBuffer buffer) { |
| buffer.write('''\n |
| @generated |
| @override |
| E? accept<E>(AstVisitor<E> visitor) => |
| visitor.visit${implClass.interfaceName}(this); |
| '''); |
| } |
| |
| void _generateAllClassMembers() { |
| var replacements = <_Replacement>[]; |
| for (var implClass in implClasses) { |
| var offset = implClass.leftBracketOffset + '{'.length; |
| var code = _generateSingleClassMembers(implClass); |
| replacements.add(_Replacement(offset, offset, code)); |
| } |
| |
| replacements.sort((a, b) => b.offset - a.offset); |
| for (var replacement in replacements) { |
| newCode = |
| newCode.substring(0, replacement.offset) + |
| replacement.text + |
| newCode.substring(replacement.end); |
| } |
| } |
| |
| void _generateBeginToken(_ImplClass implClass, StringBuffer buffer) { |
| if (implClass.isAnnotatedNodeSubclass) { |
| if (implClass.doNotGenerateLookupNames.contains( |
| 'firstTokenAfterCommentAndMetadata', |
| )) { |
| return; |
| } |
| buffer.write('''\n |
| @generated |
| @override |
| Token get firstTokenAfterCommentAndMetadata { |
| '''); |
| } else { |
| if (implClass.doNotGenerateLookupNames.contains('beginToken')) { |
| return; |
| } |
| buffer.write('''\n |
| @generated |
| @override |
| Token get beginToken { |
| '''); |
| } |
| |
| var foundNonNullProperty = false; |
| propertiesLoop: |
| for (var i = 0; i < implClass.properties.length; i++) { |
| var property = implClass.properties[i]; |
| if (property.typeKind case _PropertyTypeKindToken tokenKind) { |
| if (tokenKind.groupId != null) { |
| var groupProperties = <_Property>[]; |
| while (i < implClass.properties.length) { |
| var groupProperty = implClass.properties[i++]; |
| if (groupProperty.typeKind case _PropertyTypeKindToken groupKind) { |
| if (groupKind.groupId != tokenKind.groupId) { |
| i -= 2; |
| break; |
| } |
| groupProperties.add(groupProperty); |
| } else { |
| i -= 2; |
| break; |
| } |
| } |
| var names = groupProperties.map((p) => p.name).join(', '); |
| buffer.write(''' |
| if (Token.lexicallyFirst($names) case var result?) { |
| return result; |
| }'''); |
| continue; |
| } |
| } |
| |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken(): |
| if (property.isNullable) { |
| buffer.writeln( |
| 'if (${property.name} case var ${property.name}?) {', |
| ); |
| buffer.write('return ${property.name};'); |
| buffer.writeln('}'); |
| } else { |
| buffer.write('return ${property.name};'); |
| foundNonNullProperty = true; |
| break propertiesLoop; |
| } |
| case _PropertyTypeKindTokenList(): |
| throw UnimplementedError(); |
| case _PropertyTypeKindNode(): |
| if (property.isNullable) { |
| buffer.writeln( |
| 'if (${property.name} case var ${property.name}?) {', |
| ); |
| buffer.write('return ${property.name}.beginToken;'); |
| buffer.writeln('}'); |
| } else { |
| buffer.write('return ${property.name}.beginToken;'); |
| foundNonNullProperty = true; |
| break propertiesLoop; |
| } |
| case _PropertyTypeKindNodeList(): |
| buffer.write(''' |
| if (${property.name}.beginToken case var result?) { |
| return result; |
| }'''); |
| case _PropertyTypeKindOther(): |
| // nothing |
| } |
| } |
| |
| if (!foundNonNullProperty) { |
| buffer.writeln("throw StateError('Expected at least one non-null');"); |
| } |
| |
| buffer.write('}'); |
| } |
| |
| void _generateChildContainingRange( |
| _ImplClass implClass, |
| StringBuffer buffer, |
| ) { |
| if (implClass.doNotGenerateLookupNames.contains('_childContainingRange')) { |
| return; |
| } |
| |
| buffer.write(''' |
| \n @generated |
| @override |
| AstNodeImpl? _childContainingRange(int rangeOffset, int rangeEnd) { |
| '''); |
| |
| if (implClass.isAnnotatedNodeSubclass) { |
| buffer.write(r''' |
| if (super._childContainingRange(rangeOffset, rangeEnd) case var result?) { |
| return result; |
| }'''); |
| } |
| |
| for (var property in implClass.properties) { |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken(): |
| case _PropertyTypeKindTokenList(): |
| break; // ignored |
| case _PropertyTypeKindNode(): |
| var propertyName = property.name; |
| if (property.isNullable) { |
| buffer.write(''' |
| if ($propertyName case var $propertyName?) { |
| if ($propertyName._containsOffset(rangeOffset, rangeEnd)) { |
| return $propertyName; |
| } |
| } |
| '''); |
| } else { |
| buffer.write(''' |
| if ($propertyName._containsOffset(rangeOffset, rangeEnd)) { |
| return $propertyName; |
| } |
| '''); |
| } |
| case _PropertyTypeKindNodeList(): |
| var propertyName = property.name; |
| var invocation = '_elementContainingRange(rangeOffset, rangeEnd)'; |
| buffer.write(''' |
| if ($propertyName.$invocation case var result?) { |
| return result; |
| } |
| '''); |
| case _PropertyTypeKindOther(): |
| // nothing |
| } |
| } |
| |
| buffer.write(''' |
| return null; |
| } |
| '''); |
| } |
| |
| void _generateChildEntities(_ImplClass implClass, StringBuffer buffer) { |
| if (implClass.doNotGenerateLookupNames.contains('_childEntities')) { |
| return; |
| } |
| |
| buffer.write(''' |
| \n@generated |
| @override |
| ChildEntities get _childEntities =>'''); |
| |
| if (implClass.isAnnotatedNodeSubclass) { |
| buffer.write('super._childEntities'); |
| } else { |
| buffer.write('ChildEntities()'); |
| } |
| |
| for (var property in implClass.properties) { |
| var propertyName = property.name; |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken(): |
| buffer.write("\n..addToken('$propertyName', $propertyName)"); |
| case _PropertyTypeKindTokenList(): |
| buffer.write("\n..addTokenList('$propertyName', $propertyName)"); |
| case _PropertyTypeKindNode(): |
| buffer.write("\n..addNode('$propertyName', $propertyName)"); |
| case _PropertyTypeKindNodeList(): |
| buffer.write("\n..addNodeList('$propertyName', $propertyName)"); |
| case _PropertyTypeKindOther(): |
| // nothing |
| } |
| } |
| |
| buffer.writeln(';'); |
| } |
| |
| void _generateConstructor(_ImplClass implClass, StringBuffer buffer) { |
| if (implClass.doNotGenerateLookupNames.contains('new')) { |
| return; |
| } |
| |
| buffer.write(''' |
| \n@generated |
| ${implClass.name}({ |
| '''); |
| |
| if (implClass.isAnnotatedNodeSubclass) { |
| buffer.write(r''' |
| required super.comment, |
| required super.metadata,'''); |
| } |
| |
| for (var property in implClass.properties) { |
| var propertyName = property.name; |
| |
| if (property.isSuper) { |
| buffer.writeln('required super.$propertyName,'); |
| continue; |
| } |
| |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken(): |
| case _PropertyTypeKindTokenList(): |
| case _PropertyTypeKindOther(): |
| buffer.writeln('required this.$propertyName,'); |
| case _PropertyTypeKindNode(): |
| var typeCode = property.typeCode; |
| buffer.writeln('required $typeCode $propertyName,'); |
| case _PropertyTypeKindNodeList typeKind: |
| var typeCode = 'List<${typeKind.elementTypeCode}>'; |
| buffer.writeln('required $typeCode $propertyName,'); |
| } |
| } |
| |
| buffer.write('})'); |
| |
| var isFirstFieldInitializer = true; |
| for (var property in implClass.properties) { |
| if (property.isSuper) { |
| continue; |
| } |
| if (property.typeKind is _PropertyTypeKindNode) { |
| if (isFirstFieldInitializer) { |
| buffer.write(' : '); |
| } else { |
| buffer.write(',\n'); |
| } |
| isFirstFieldInitializer = false; |
| var propertyName = property.name; |
| buffer.write('_$propertyName = $propertyName'); |
| } |
| } |
| |
| buffer.writeln(' {'); |
| |
| for (var property in implClass.properties) { |
| if (property.isSuper) { |
| continue; |
| } |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken(): |
| case _PropertyTypeKindTokenList(): |
| case _PropertyTypeKindOther(): |
| break; // nothing |
| case _PropertyTypeKindNode(): |
| buffer.writeln('_becomeParentOf(${property.name});'); |
| case _PropertyTypeKindNodeList(): |
| var name = property.name; |
| buffer.writeln('this.$name._initialize(this, $name);'); |
| } |
| } |
| |
| buffer.writeln('}'); |
| |
| // Remove empty block body with empty body. |
| var bufferStr = buffer.toString(); |
| if (bufferStr.endsWith(' {\n}\n')) { |
| buffer.clear(); |
| buffer.write(bufferStr.substring(0, bufferStr.length - 5)); |
| buffer.writeln(';'); |
| } |
| } |
| |
| void _generateEndToken(_ImplClass implClass, StringBuffer buffer) { |
| if (implClass.doNotGenerateLookupNames.contains('endToken')) { |
| return; |
| } |
| |
| buffer.write('''\n |
| @generated |
| @override |
| Token get endToken { |
| '''); |
| |
| var foundNonNullProperty = false; |
| propertiesLoop: |
| for (var property in implClass.properties.reversed) { |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken(): |
| if (property.isNullable) { |
| buffer.writeln( |
| 'if (${property.name} case var ${property.name}?) {', |
| ); |
| buffer.write('return ${property.name};'); |
| buffer.writeln('}'); |
| } else { |
| buffer.write('return ${property.name};'); |
| foundNonNullProperty = true; |
| break propertiesLoop; |
| } |
| case _PropertyTypeKindTokenList(): |
| var lastIndexStr = '${property.name}.length - 1'; |
| buffer.write('return ${property.name}[$lastIndexStr];'); |
| foundNonNullProperty = true; |
| break propertiesLoop; |
| case _PropertyTypeKindNode(): |
| if (property.isNullable) { |
| buffer.writeln( |
| 'if (${property.name} case var ${property.name}?) {', |
| ); |
| buffer.write('return ${property.name}.endToken;'); |
| buffer.writeln('}'); |
| } else { |
| buffer.write('return ${property.name}.endToken;'); |
| foundNonNullProperty = true; |
| break propertiesLoop; |
| } |
| case _PropertyTypeKindNodeList(): |
| buffer.write(''' |
| if (${property.name}.endToken case var result?) { |
| return result; |
| }'''); |
| case _PropertyTypeKindOther(): |
| // nothing |
| } |
| } |
| |
| if (!foundNonNullProperty) { |
| buffer.writeln("throw StateError('Expected at least one non-null');"); |
| } |
| |
| buffer.write('}'); |
| } |
| |
| void _generateFields(_ImplClass implClass, StringBuffer buffer) { |
| for (var property in implClass.properties) { |
| var propertyName = property.name; |
| |
| if (property.isSuper) { |
| continue; |
| } |
| |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken kind: |
| var maybeOverride = property.withOverride ? '@override' : ''; |
| var finalKeyword = kind.isWritable ? '' : 'final '; |
| buffer.write(''' |
| \n@generated |
| $maybeOverride |
| $finalKeyword ${property.typeCode} $propertyName; |
| '''); |
| case _PropertyTypeKindTokenList(): |
| buffer.write(''' |
| \n@generated |
| @override |
| final ${property.typeCode} $propertyName; |
| '''); |
| case _PropertyTypeKindNode(): |
| var typeCode = property.typeCode; |
| buffer.write(''' |
| \n@generated |
| $typeCode _$propertyName; |
| '''); |
| case _PropertyTypeKindNodeList(): |
| buffer.write(''' |
| \n@generated |
| @override |
| final ${property.typeCode} $propertyName = NodeListImpl._(); |
| '''); |
| case _PropertyTypeKindOther(): |
| buffer.write(''' |
| \n@generated |
| @override |
| final ${property.typeCode} $propertyName; |
| '''); |
| } |
| } |
| } |
| |
| void _generateNodeGettersSetters(StringBuffer buffer, _ImplClass implClass) { |
| for (var property in implClass.properties) { |
| if (property.isSuper) { |
| if (property.withOverrideSuperNotNull) { |
| buffer.write(''' |
| \n@generated |
| @override |
| ${property.typeCode} get ${property.name} => super.${property.name}!; |
| '''); |
| } |
| continue; |
| } |
| if (property.typeKind is _PropertyTypeKindNode) { |
| var propertyName = property.name; |
| var maybeOverride = property.withOverride ? '@override' : ''; |
| buffer.write(''' |
| \n@generated |
| $maybeOverride |
| ${property.typeCode} get $propertyName => _$propertyName; |
| |
| @generated |
| set $propertyName(${property.typeCode} $propertyName) { |
| _$propertyName = _becomeParentOf($propertyName); |
| } |
| '''); |
| } |
| } |
| } |
| |
| void _generateResolveExpression(_ImplClass implClass, StringBuffer buffer) { |
| if (implClass.doNotGenerateLookupNames.contains('resolveExpression')) { |
| return; |
| } |
| |
| if (implClass.interfaceElement.isExpressionOrSubtype) { |
| buffer.write(''' |
| \n@generated |
| @override |
| void resolveExpression(ResolverVisitor resolver, TypeImpl contextType) { |
| resolver.visit${implClass.interfaceName}(this, contextType: contextType); |
| }'''); |
| } |
| } |
| |
| String _generateSingleClassMembers(_ImplClass implClass) { |
| var buffer = StringBuffer(); |
| _generateFields(implClass, buffer); |
| _generateConstructor(implClass, buffer); |
| _generateBeginToken(implClass, buffer); |
| _generateEndToken(implClass, buffer); |
| _generateNodeGettersSetters(buffer, implClass); |
| _generateAccept(implClass, buffer); |
| _generateChildContainingRange(implClass, buffer); |
| _generateChildEntities(implClass, buffer); |
| _generateResolveExpression(implClass, buffer); |
| _generateVisitChildren(implClass, buffer); |
| return buffer.toString(); |
| } |
| |
| void _generateVisitChildren(_ImplClass implClass, StringBuffer buffer) { |
| if (implClass.doNotGenerateLookupNames.contains('visitChildren')) { |
| return; |
| } |
| |
| buffer.write(''' |
| \n@generated |
| @override |
| void visitChildren(AstVisitor visitor) {'''); |
| |
| if (implClass.isAnnotatedNodeSubclass) { |
| buffer.write('super.visitChildren(visitor);'); |
| } |
| |
| for (var property in implClass.properties) { |
| switch (property.typeKind) { |
| case _PropertyTypeKindToken(): |
| case _PropertyTypeKindTokenList(): |
| case _PropertyTypeKindOther(): |
| break; // nothing |
| case _PropertyTypeKindNode(): |
| var propertyName = property.name; |
| var maybeQuestion = property.isNullable ? '?' : ''; |
| buffer.write('\n$propertyName$maybeQuestion.accept(visitor);'); |
| case _PropertyTypeKindNodeList(): |
| var propertyName = property.name; |
| buffer.write('\n$propertyName.accept(visitor);'); |
| } |
| } |
| |
| buffer.writeln('\n}'); |
| } |
| |
| String _getAstPath() { |
| var analyzerPath = normalize(join(pkg_root.packageRoot, 'analyzer')); |
| var analyzerLibPath = normalize(join(analyzerPath, 'lib')); |
| var astPath = normalize( |
| join(analyzerLibPath, 'src', 'dart', 'ast', 'ast.dart'), |
| ); |
| return astPath; |
| } |
| |
| Future<ResolvedUnitResult> _getAstResolvedUnit() async { |
| var astPath = _getAstPath(); |
| var collection = AnalysisContextCollection(includedPaths: [astPath]); |
| var analysisContext = collection.contextFor(astPath); |
| var analysisSession = analysisContext.currentSession; |
| var astUnitResult = await analysisSession.getResolvedUnit(astPath); |
| return astUnitResult as ResolvedUnitResult; |
| } |
| |
| void _removeGeneratedMembers(ResolvedUnitResult astUnitResult) { |
| var replacements = <_Replacement>[]; |
| for (var implClass in implClasses) { |
| for (var member in implClass.node.members) { |
| String memberName; |
| switch (member) { |
| case ConstructorDeclarationImpl(): |
| var element = member.declaredFragment!.element; |
| memberName = element.lookupName!; |
| if (element.metadata.hasDoNotGenerate) { |
| implClass.doNotGenerateLookupNames.add(memberName); |
| continue; |
| } |
| case MethodDeclarationImpl(): |
| var element = member.declaredFragment!.element; |
| memberName = element.lookupName!; |
| if (element.metadata.hasDoNotGenerate) { |
| implClass.doNotGenerateLookupNames.add(memberName); |
| continue; |
| } |
| case FieldDeclarationImpl(): |
| var field = member.fields.variables.single; |
| memberName = field.declaredFragment!.element.lookupName!; |
| } |
| if (implClass.generatedLookupNames.contains(memberName)) { |
| replacements.add(_Replacement(member.offset, member.end, '')); |
| } |
| } |
| } |
| |
| replacements.sort((a, b) => b.offset - a.offset); |
| for (var replacement in replacements) { |
| newCode = |
| newCode.substring(0, replacement.offset) + |
| replacement.text + |
| newCode.substring(replacement.end); |
| var oldLength = replacement.end - replacement.offset; |
| var deltaOffset = oldLength - replacement.text.length; |
| for (var implClass in implClasses.reversed) { |
| if (implClass.leftBracketOffset > replacement.offset) { |
| implClass.leftBracketOffset -= deltaOffset; |
| } else { |
| break; |
| } |
| } |
| } |
| } |
| |
| static Future<String> _formatSortCode(String path, String code) async { |
| var server = Server(); |
| await server.start(); |
| server.listenToOutput(); |
| |
| await server.send('analysis.setAnalysisRoots', { |
| 'included': [path], |
| 'excluded': [], |
| }); |
| |
| Future<void> updateContent() async { |
| await server.send('analysis.updateContent', { |
| 'files': { |
| path: {'type': 'add', 'content': code}, |
| }, |
| }); |
| } |
| |
| await updateContent(); |
| var formatResponse = await server.send('edit.format', { |
| 'file': path, |
| 'selectionOffset': 0, |
| 'selectionLength': code.length, |
| }); |
| var formatResult = EditFormatResult.fromJson( |
| ResponseDecoder(null), |
| 'result', |
| formatResponse, |
| ); |
| code = SourceEdit.applySequence(code, formatResult.edits); |
| |
| await updateContent(); |
| var sortResponse = await server.send('edit.sortMembers', {'file': path}); |
| var sortResult = EditSortMembersResult.fromJson( |
| ResponseDecoder(null), |
| 'result', |
| sortResponse, |
| ); |
| code = SourceEdit.applySequence(code, sortResult.edit.edits); |
| |
| await server.kill(); |
| return code; |
| } |
| } |
| |
| class _ImplClass { |
| final ClassDeclarationImpl node; |
| final InterfaceElement interfaceElement; |
| final List<_Property> properties; |
| final Set<String> doNotGenerateLookupNames = {}; |
| int leftBracketOffset; |
| |
| late final Set<String> generatedLookupNames = () { |
| var generatedLookupNames = { |
| '_childContainingRange', |
| '_childEntities', |
| 'accept', |
| 'beginToken', |
| 'endToken', |
| 'firstTokenAfterCommentAndMetadata', |
| 'new', |
| 'resolveExpression', |
| 'visitChildren', |
| }; |
| for (var property in properties) { |
| var propertyName = property.name; |
| // We always have a getter. |
| generatedLookupNames.add(propertyName); |
| // For expressions we also have a field, and a setter. |
| if (property.typeKind is _PropertyTypeKindNode) { |
| generatedLookupNames.add('_$propertyName'); |
| generatedLookupNames.add('$propertyName='); |
| } |
| } |
| return generatedLookupNames; |
| }(); |
| |
| _ImplClass({ |
| required this.node, |
| required this.interfaceElement, |
| required this.properties, |
| required this.leftBracketOffset, |
| }); |
| |
| @deprecated |
| bool get hasNotAbstractVisitChildren { |
| var element = node.declaredFragment!.element; |
| return element.inheritanceManager.getMember4( |
| element, |
| Name(null, 'visitChildren'), |
| forSuper: true, |
| ) != |
| null; |
| } |
| |
| String get interfaceName => interfaceElement.name3!; |
| |
| bool get isAnnotatedNodeSubclass { |
| var element = node.declaredFragment!.element; |
| return element.allSupertypes.any( |
| (type) => type.element3.isAnnotatedNodeExactly, |
| ); |
| } |
| |
| bool get isNamedCompilationUnitMemberSubclass { |
| var element = node.declaredFragment!.element; |
| return element.allSupertypes.any( |
| (type) => type.element3.isNamedCompilationUnitMemberNodeExactly, |
| ); |
| } |
| |
| String get name { |
| return node.name.lexeme; |
| } |
| } |
| |
| class _Property { |
| final String name; |
| final InterfaceType type; |
| final _PropertyTypeKind typeKind; |
| final bool isSuper; |
| final bool withOverride; |
| final bool withOverrideSuperNotNull; |
| |
| _Property({ |
| required this.name, |
| required this.type, |
| required this.typeKind, |
| required this.isSuper, |
| required this.withOverride, |
| required this.withOverrideSuperNotNull, |
| }); |
| |
| bool get isNullable { |
| return type.nullabilitySuffix == NullabilitySuffix.question; |
| } |
| |
| String get typeCode { |
| var nullSuffix = isNullable ? '?' : ''; |
| switch (typeKind) { |
| case _PropertyTypeKindToken(): |
| return 'Token$nullSuffix'; |
| case _PropertyTypeKindTokenList(): |
| return 'List<Token>$nullSuffix'; |
| case _PropertyTypeKindNodeList typeKind: |
| var elementTypeCode = typeKind.elementTypeCode; |
| return 'NodeListImpl<$elementTypeCode>$nullSuffix'; |
| case _PropertyTypeKindOther(): |
| return type.asCode; |
| default: |
| return '${type.element3.name3!}Impl$nullSuffix'; |
| } |
| } |
| } |
| |
| sealed class _PropertyTypeKind { |
| static _PropertyTypeKind fromType(DartType type) { |
| if (type.isToken) { |
| return _PropertyTypeKindToken(); |
| } |
| if (type.isTokenListExactly) { |
| return _PropertyTypeKindTokenList(); |
| } |
| if (type.isNodeOrSubtype) { |
| return _PropertyTypeKindNode(); |
| } |
| if (type.isNodeListExactly) { |
| type as InterfaceType; |
| var elementType = type.typeArguments.single; |
| return _PropertyTypeKindNodeList( |
| elementType: elementType as InterfaceType, |
| ); |
| } |
| return _PropertyTypeKindOther(); |
| } |
| } |
| |
| class _PropertyTypeKindNode extends _PropertyTypeKind {} |
| |
| class _PropertyTypeKindNodeList extends _PropertyTypeKind { |
| final InterfaceType elementType; |
| |
| _PropertyTypeKindNodeList({required this.elementType}); |
| |
| String get elementTypeCode { |
| return '${elementType.element3.name3!}Impl'; |
| } |
| } |
| |
| class _PropertyTypeKindOther extends _PropertyTypeKind {} |
| |
| class _PropertyTypeKindToken extends _PropertyTypeKind { |
| bool isWritable = false; |
| int? groupId; |
| } |
| |
| class _PropertyTypeKindTokenList extends _PropertyTypeKind {} |
| |
| class _Replacement { |
| final int offset; |
| final int end; |
| final String text; |
| |
| _Replacement(this.offset, this.end, this.text); |
| } |
| |
| extension _DartTypeExtension on DartType { |
| String get asCode { |
| var nullSuffix = nullabilitySuffix == NullabilitySuffix.question ? '?' : ''; |
| switch (this) { |
| case DynamicType(): |
| return 'dynamic'; |
| case InterfaceType self: |
| var typeArguments = self.typeArguments; |
| if (typeArguments.isEmpty) { |
| return '${self.element3.name3!}$nullSuffix'; |
| } else { |
| var typeArgumentsStr = typeArguments.map((t) => t.asCode).join(', '); |
| return '${self.element3.name3}<$typeArgumentsStr>$nullSuffix'; |
| } |
| case TypeParameterType self: |
| return '${self.element3.name3!}$nullSuffix'; |
| case VoidType(): |
| return 'void'; |
| default: |
| throw UnimplementedError('($runtimeType) $this'); |
| } |
| } |
| |
| bool get isNodeListExactly { |
| if (this case InterfaceType self) { |
| return self.isNodeListExactly; |
| } |
| return false; |
| } |
| |
| bool get isNodeOrSubtype { |
| if (this case InterfaceType self) { |
| return self.isNodeOrSubtype; |
| } |
| return false; |
| } |
| |
| bool get isToken { |
| if (this case InterfaceType self) { |
| return self.isToken; |
| } |
| return false; |
| } |
| |
| bool get isTokenListExactly { |
| if (this case InterfaceType self) { |
| return self.isTokenListExactly; |
| } |
| return false; |
| } |
| } |
| |
| extension _ElementAnnotationExtension on ElementAnnotation { |
| bool get isDoNotGenerate { |
| if (element2 case ConstructorElement constructorElement) { |
| var interfaceElement = constructorElement.enclosingElement; |
| return interfaceElement.isDoNotGenerateExactly; |
| } |
| return false; |
| } |
| } |
| |
| extension _InterfaceElementExtension on InterfaceElement { |
| static final uriAst = Uri.parse('package:analyzer/src/dart/ast/ast.dart'); |
| static final uriToken = Uri.parse( |
| 'package:_fe_analyzer_shared/src/scanner/token.dart', |
| ); |
| |
| bool get isAnnotatedNodeExactly { |
| return library2.uri == uriAst && name3 == 'AnnotatedNode'; |
| } |
| |
| bool get isDoNotGenerateExactly { |
| return library2.uri == uriAst && name3 == 'DoNotGenerate'; |
| } |
| |
| bool get isExpressionExactly { |
| return library2.uri == uriAst && name3 == 'Expression'; |
| } |
| |
| bool get isExpressionOrSubtype { |
| return isExpressionExactly || |
| allSupertypes.any((t) => t.isExpressionExactly); |
| } |
| |
| bool get isListExactly { |
| return library2.uri == Uri.parse('dart:core') && name3 == 'List'; |
| } |
| |
| bool get isNamedCompilationUnitMemberNodeExactly { |
| return library2.uri == uriAst && name3 == 'NamedCompilationUnitMember'; |
| } |
| |
| bool get isNodeExactly { |
| return library2.uri == uriAst && name3 == 'AstNode'; |
| } |
| |
| bool get isNodeListExactly { |
| return library2.uri == uriAst && name3 == 'NodeList'; |
| } |
| |
| bool get isTokenExactly { |
| return library2.uri == uriToken && name3 == 'Token'; |
| } |
| } |
| |
| extension _InterfaceTypeExtension on InterfaceType { |
| bool get isExpressionExactly { |
| return element3.isExpressionExactly; |
| } |
| |
| bool get isNodeExactly { |
| return element3.isNodeExactly; |
| } |
| |
| bool get isNodeListExactly { |
| return element3.isNodeListExactly; |
| } |
| |
| bool get isNodeOrSubtype { |
| return isNodeExactly || allSupertypes.any((t) => t.isNodeExactly); |
| } |
| |
| bool get isToken { |
| return element3.isTokenExactly; |
| } |
| |
| bool get isTokenListExactly { |
| return element3.isListExactly && |
| typeArguments.length == 1 && |
| typeArguments.single.isToken; |
| } |
| } |
| |
| extension _MetadataExtension on Metadata { |
| bool get hasDoNotGenerate { |
| return annotations.any((annotation) => annotation.isDoNotGenerate); |
| } |
| } |