| // Copyright (c) 2020, 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" show File, Platform, stdout; | 
 | import "dart:typed_data" show Uint8List; | 
 |  | 
 | import 'package:_fe_analyzer_shared/src/parser/parser.dart' | 
 |     show IdentifierContext; | 
 |  | 
 | import 'package:_fe_analyzer_shared/src/scanner/token.dart' | 
 |     show CommentToken, Token; | 
 |  | 
 | import "package:front_end/src/fasta/util/parser_ast.dart"; | 
 |  | 
 | import 'package:front_end/src/fasta/util/parser_ast_helper.dart'; | 
 |  | 
 | void main(List<String> args) { | 
 |   Uri uri = Platform.script; | 
 |   uri = uri.resolve("../../kernel/lib/ast.dart"); | 
 |   Uint8List bytes = new File.fromUri(uri).readAsBytesSync(); | 
 |   CompilationUnitEnd ast = | 
 |       getAST(bytes, includeBody: true, includeComments: true); | 
 |   Map<String, TopLevelDeclarationEnd> classes = {}; | 
 |   for (TopLevelDeclarationEnd cls in ast.getClasses()) { | 
 |     IdentifierHandle identifier = cls.getIdentifier(); | 
 |     // TODO(srawlins): Look into fixing this code. Right now we get: | 
 |     // "The argument type 'Token' isn't related to 'String'." | 
 |     // ignore: collection_methods_unrelated_type | 
 |     assert(classes[identifier.token] == null); | 
 |     classes[identifier.token.toString()] = cls; | 
 |   } | 
 |  | 
 |   Set<String?> goodNames = {"TreeNode"}; | 
 |   Map<Token, Replacement> replacements = {}; | 
 |   for (MapEntry<String, TopLevelDeclarationEnd> entry in classes.entries) { | 
 |     TopLevelDeclarationEnd cls = entry.value; | 
 |  | 
 |     // Simple "class hierarchy" to only work on TreeNodes. | 
 |     if (goodNames.contains(entry.key)) { | 
 |       // Cached good. | 
 |     } else { | 
 |       // Check if good. | 
 |       String? parent = getExtends(cls); | 
 |       TopLevelDeclarationEnd? parentCls = classes[parent]; | 
 |       List<String?> allParents = [parent]; | 
 |       while ( | 
 |           parent != null && parentCls != null && !goodNames.contains(parent)) { | 
 |         parent = getExtends(parentCls); | 
 |         allParents.add(parent); | 
 |         parentCls = classes[parent]; | 
 |       } | 
 |       if (goodNames.contains(parent)) { | 
 |         goodNames.addAll(allParents); | 
 |       } else { | 
 |         continue; | 
 |       } | 
 |     } | 
 |  | 
 |     ClassDeclarationEnd classDeclaration = cls.getClassDeclaration(); | 
 |     ClassOrMixinOrExtensionBodyEnd classOrMixinBody = | 
 |         classDeclaration.getClassOrMixinOrExtensionBody(); | 
 |  | 
 |     Set<String> namedClassConstructors = {}; | 
 |     Set<String> namedFields = {}; | 
 |     for (MemberEnd member in classOrMixinBody.getMembers()) { | 
 |       if (member.isClassConstructor()) { | 
 |         ClassConstructorEnd constructor = member.getClassConstructor(); | 
 |         Token nameToken = constructor.beginToken; | 
 |         // String name = nameToken.lexeme; | 
 |         if (nameToken.next!.lexeme == ".") { | 
 |           nameToken = nameToken.next!.next!; | 
 |           // name += ".$nameToken"; | 
 |           namedClassConstructors.add(nameToken.lexeme); | 
 |         } | 
 |         if (nameToken.next!.lexeme == ".") { | 
 |           throw "Unexpected"; | 
 |         } | 
 |       } else if (member.isClassFields()) { | 
 |         ClassFieldsEnd classFields = member.getClassFields(); | 
 |         Token identifierToken = classFields.getFieldIdentifiers().single.token; | 
 |         String identifier = identifierToken.toString(); | 
 |         namedFields.add(identifier); | 
 |       } | 
 |     } | 
 |  | 
 |     // If there isn't a `frozen` field in `TreeNode` we insert one. | 
 |     if (entry.key == "TreeNode" && !namedFields.contains("frozen")) { | 
 |       Token classBraceToken = classOrMixinBody.beginToken; | 
 |       assert(classBraceToken.lexeme == "{"); | 
 |       replacements[classBraceToken] = new Replacement( | 
 |           classBraceToken, classBraceToken, "{\n  bool frozen = false;"); | 
 |     } | 
 |  | 
 |     for (MemberEnd member in classOrMixinBody.getMembers()) { | 
 |       if (member.isClassConstructor()) { | 
 |         processConstructor( | 
 |             member, replacements, namedClassConstructors, namedFields); | 
 |       } else if (member.isClassFields()) { | 
 |         processField(member, entry, replacements); | 
 |       } | 
 |     } | 
 |   } | 
 |   Token? token = ast.getBegin().token; | 
 |  | 
 |   int endOfLast = token.end; | 
 |   while (token != null) { | 
 |     CommentToken? comment = token.precedingComments; | 
 |     while (comment != null) { | 
 |       if (comment.offset > endOfLast) { | 
 |         for (int i = endOfLast; i < comment.offset; i++) { | 
 |           int byte = bytes[i]; | 
 |           stdout.writeCharCode(byte); | 
 |         } | 
 |       } | 
 |  | 
 |       stdout.write(comment.value()); | 
 |       endOfLast = comment.end; | 
 |       comment = comment.next as CommentToken?; | 
 |     } | 
 |  | 
 |     if (token.isEof) break; | 
 |     if (token.offset > endOfLast) { | 
 |       for (int i = endOfLast; i < token.offset; i++) { | 
 |         int byte = bytes[i]; | 
 |         stdout.writeCharCode(byte); | 
 |       } | 
 |     } | 
 |  | 
 |     Replacement? replacement = replacements[token]; | 
 |     if (replacement != null) { | 
 |       stdout.write(replacement.replacement); | 
 |       token = replacement.endToken; | 
 |     } else { | 
 |       stdout.write(token.lexeme); | 
 |     } | 
 |     endOfLast = token.end; | 
 |     token = token.next; | 
 |   } | 
 | } | 
 |  | 
 | void processField( | 
 |     MemberEnd member, | 
 |     MapEntry<String, TopLevelDeclarationEnd> entry, | 
 |     Map<Token, Replacement> replacements) { | 
 |   ClassFieldsEnd classFields = member.getClassFields(); | 
 |  | 
 |   if (classFields.count != 1) { | 
 |     throw "Notice ${classFields.count}"; | 
 |   } | 
 |  | 
 |   Token identifierToken = classFields.getFieldIdentifiers().single.token; | 
 |   String identifier = identifierToken.toString(); | 
 |  | 
 |   if (identifier == "frozen" && entry.key == "TreeNode") return; | 
 |  | 
 |   if (classFields.staticToken != null) { | 
 |     return; | 
 |   } | 
 |   bool isFinal = false; | 
 |   if (classFields.varFinalOrConst?.toString() == "final") { | 
 |     isFinal = true; | 
 |   } | 
 |  | 
 |   TypeHandle? type = classFields.getFirstType(); | 
 |   String typeString = "dynamic"; | 
 |   if (type != null) { | 
 |     Token token = type.beginToken; | 
 |     typeString = ""; | 
 |     while (token != identifierToken) { | 
 |       typeString += " ${token.lexeme}"; | 
 |       token = token.next!; | 
 |     } | 
 |     typeString = typeString.trim(); | 
 |   } | 
 |  | 
 |   FieldInitializerEnd? initializer = classFields.getFieldInitializer(); | 
 |   String initializerString = ""; | 
 |   if (initializer != null) { | 
 |     Token token = initializer.assignment; | 
 |     Token endToken = initializer.token; | 
 |     while (token != endToken) { | 
 |       initializerString += " ${token.lexeme}"; | 
 |       token = token.next!; | 
 |     } | 
 |     initializerString = initializerString.trim(); | 
 |   } | 
 |  | 
 |   Token beginToken = classFields.beginToken; | 
 |   Token endToken = classFields.endToken; | 
 |   // ignore: unnecessary_null_comparison | 
 |   assert(beginToken != null); | 
 |   // ignore: unnecessary_null_comparison | 
 |   assert(endToken != null); | 
 |  | 
 |   String frozenCheckCode = | 
 |       """if (frozen) throw "Trying to modify frozen node!";"""; | 
 |  | 
 |   if (identifier == "parent" && entry.key == "TreeNode") { | 
 |     // We update the parent for libraries for instance all the time (e.g. | 
 |     // when we link). | 
 |     frozenCheckCode = ""; | 
 |   } else if (identifier == "transformerFlags" && entry.key == "Member") { | 
 |     // The verifier changes this for all libraries | 
 |     // (and then change it back again). | 
 |     frozenCheckCode = ""; | 
 |   } else if (identifier == "initializer" && entry.key == "Field") { | 
 |     // The constant evaluator does some stuff here. Only allow that | 
 |     // when it's basically a no-op though! | 
 |     frozenCheckCode = """ | 
 |     if (frozen) { | 
 |       if (_initializer is ConstantExpression && newValue is ConstantExpression) { | 
 |         if ((_initializer as ConstantExpression).constant == newValue.constant) { | 
 |           _initializer = newValue; | 
 |           return; | 
 |         } | 
 |       } | 
 |       throw "Trying to modify frozen node!"; | 
 |     }"""; | 
 |   } | 
 |  | 
 |   if (!isFinal) { | 
 |     replacements[beginToken] = new Replacement(beginToken, endToken, """ | 
 | $typeString _$identifier$initializerString; | 
 | $typeString get $identifier => _$identifier; | 
 | void set $identifier($typeString newValue) { | 
 |     $frozenCheckCode | 
 |   _$identifier = newValue; | 
 | }"""); | 
 |   } else { | 
 |     // Don't create setter for final field. | 
 |     // TODO: Possibly wrap a list for instance of a non-writable one. | 
 |     replacements[beginToken] = new Replacement(beginToken, endToken, """ | 
 | final $typeString _$identifier$initializerString; | 
 | $typeString get $identifier => _$identifier;"""); | 
 |   } | 
 | } | 
 |  | 
 | void processConstructor(MemberEnd member, Map<Token, Replacement> replacements, | 
 |     Set<String> namedClassConstructors, Set<String> namedFields) { | 
 |   ClassConstructorEnd constructor = member.getClassConstructor(); | 
 |   FormalParametersEnd formalParameters = constructor.getFormalParameters(); | 
 |   List<FormalParameterEnd> parameters = formalParameters.getFormalParameters(); | 
 |  | 
 |   for (FormalParameterEnd parameter in parameters) { | 
 |     Token token = parameter.getBegin().token; | 
 |     if (token.lexeme != "this") { | 
 |       continue; | 
 |     } | 
 |     // Here `this.foo` can just be replace with `this._foo`. | 
 |     Token afterDot = token.next!.next!; | 
 |     replacements[afterDot] = new Replacement(afterDot, afterDot, "_$afterDot"); | 
 |   } | 
 |  | 
 |   OptionalFormalParametersEnd? optionalFormalParameters = | 
 |       formalParameters.getOptionalFormalParameters(); | 
 |   Set<String> addInitializers = {}; | 
 |   if (optionalFormalParameters != null) { | 
 |     List<FormalParameterEnd> parameters = | 
 |         optionalFormalParameters.getFormalParameters(); | 
 |  | 
 |     for (FormalParameterEnd parameter in parameters) { | 
 |       Token token = parameter.getBegin().token; | 
 |       if (token.lexeme != "this") { | 
 |         continue; | 
 |       } | 
 |       // Here `this.foo` can't just be replace with `this._foo` as it is | 
 |       // (possibly) named and we can't use private stuff in named. | 
 |       // Instead we replace it with `dynamic foo` here and add an | 
 |       // initializer `this._foo = foo`. | 
 |       Token afterDot = token.next!.next!; | 
 |       addInitializers.add(afterDot.lexeme); | 
 |       replacements[token] = new Replacement(token, token.next!, "dynamic "); | 
 |     } | 
 |   } | 
 |  | 
 |   InitializersEnd? initializers = constructor.getInitializers(); | 
 |  | 
 |   // First patch up any existing initializers. | 
 |   if (initializers != null) { | 
 |     List<InitializerEnd> actualInitializers = initializers.getInitializers(); | 
 |     for (InitializerEnd initializer in actualInitializers) { | 
 |       Token token = initializer.getBegin().token; | 
 |       // This is only afterDot if there's a dot --- which (probably) is | 
 |       // only there if there's a `this`. | 
 |       Token afterDot = token.next!.next!; | 
 |  | 
 |       // We need to check it's not a redirecting call! | 
 |       // TODO(jensj): Handle redirects like this: | 
 |       //  class C { | 
 |       //    C(); | 
 |       //    C.redirect() : this(); | 
 |       //  } | 
 |       if (token.lexeme == "this" && | 
 |           namedClassConstructors.contains(afterDot.lexeme)) { | 
 |         // Redirect! | 
 |         continue; | 
 |       } | 
 |  | 
 |       if (token.lexeme == "this") { | 
 |         // Here `this.foo` can just be replace with `this._foo`. | 
 |         assert(namedFields.contains(afterDot.lexeme)); | 
 |         replacements[afterDot] = | 
 |             new Replacement(afterDot, afterDot, "_$afterDot"); | 
 |       } else if (token.lexeme == "super") { | 
 |         // Don't try to patch this one. | 
 |       } else if (token.lexeme == "assert") { | 
 |         List<IdentifierHandle> identifiers = | 
 |             initializer.recursivelyFind<IdentifierHandle>(); | 
 |         for (Token token in identifiers.map((e) => e.token)) { | 
 |           if (namedFields.contains(token.lexeme)) { | 
 |             replacements[token] = new Replacement(token, token, "_$token"); | 
 |           } | 
 |         } | 
 |       } else { | 
 |         assert(namedFields.contains(token.lexeme), | 
 |             "${token.lexeme} isn't a known field among ${namedFields}"); | 
 |         replacements[token] = new Replacement(token, token, "_$token"); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Then add any new ones. | 
 |   if (addInitializers.isNotEmpty && initializers == null) { | 
 |     // No initializers => Fake one by inserting `:` and all `_foo = foo` | 
 |     // entries. | 
 |     Token endToken = formalParameters.endToken; | 
 |     String initializerString = | 
 |         addInitializers.map((e) => "this._$e = $e").join(",\n"); | 
 |     replacements[endToken] = | 
 |         new Replacement(endToken, endToken, ") : $initializerString"); | 
 |   } else if (addInitializers.isNotEmpty) { | 
 |     // Add to existing initializer list. We add them as the first one(s) | 
 |     // so we don't have to insert before the potential super call. | 
 |     InitializersBegin firstOne = initializers!.getBegin(); | 
 |     Token colon = firstOne.token; | 
 |     assert(colon.lexeme == ":"); | 
 |     String initializerString = | 
 |         addInitializers.map((e) => "this._$e = $e").join(", "); | 
 |     replacements[colon] = | 
 |         new Replacement(colon, colon, ": $initializerString,"); | 
 |   } | 
 |  | 
 |   // If there are anything in addInitializers we need to patch | 
 |   // up the body too -- if we replace `{this.foo}` with `{dynamic foo}` | 
 |   // and the body says `foo = 42;` before that would change the field, | 
 |   // now it will change the parameter value. We must patch up all usages | 
 |   // - even reads to work on things like | 
 |   //  class C { | 
 |   //    int field1; | 
 |   //    int field2; | 
 |   //    C(this.field1) : field2 = field1 + 1; | 
 |   //  } | 
 |   if (addInitializers.isNotEmpty) { | 
 |     BlockFunctionBodyEnd? blockFunctionBody = | 
 |         constructor.getBlockFunctionBody(); | 
 |     if (blockFunctionBody != null) { | 
 |       List<IdentifierHandle> identifiers = | 
 |           blockFunctionBody.recursivelyFind<IdentifierHandle>(); | 
 |       for (IdentifierHandle identifier in identifiers) { | 
 |         Token token = identifier.token; | 
 |         IdentifierContext context = identifier.context; | 
 |         if (namedFields.contains(token.lexeme) && | 
 |             addInitializers.contains(token.lexeme)) { | 
 |           // For now naively assume that if it's a continuation it says | 
 |           // `this.` | 
 |           if (!context.isContinuation) { | 
 |             replacements[token] = new Replacement(token, token, "_$token"); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | class Replacement { | 
 |   final Token beginToken; | 
 |   final Token endToken; | 
 |   final String replacement; | 
 |  | 
 |   Replacement(this.beginToken, this.endToken, this.replacement); | 
 | } | 
 |  | 
 | String? getExtends(TopLevelDeclarationEnd cls) { | 
 |   ClassDeclarationEnd classDeclaration = cls.getClassDeclaration(); | 
 |  | 
 |   ClassExtendsHandle classExtends = classDeclaration.getClassExtends(); | 
 |   Token? extendsKeyword = classExtends.extendsKeyword; | 
 |   if (extendsKeyword == null) { | 
 |     return null; | 
 |   } else { | 
 |     return extendsKeyword.next.toString(); | 
 |   } | 
 | } | 
 |  | 
 | void debugDumpNode(ParserAstNode node) { | 
 |   node.children!.forEach((element) { | 
 |     print("${element.what} (${element.deprecatedArguments}) " | 
 |         "(${element.children})"); | 
 |   }); | 
 | } | 
 |  | 
 | void debugDumpNodeRecursively(ParserAstNode node, {String indent = ""}) { | 
 |   print("$indent${node.what} (${node.deprecatedArguments})"); | 
 |   if (node.children == null) return; | 
 |   node.children!.forEach((element) { | 
 |     print("$indent${element.what} (${element.deprecatedArguments})"); | 
 |     if (element.children != null) { | 
 |       element.children!.forEach((element) { | 
 |         debugDumpNodeRecursively(element, indent: "  $indent"); | 
 |       }); | 
 |     } | 
 |   }); | 
 | } |