| // 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. |
| |
| import 'dart:convert'; |
| |
| import 'package:_fe_analyzer_shared/src/scanner/token.dart'; |
| import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart' |
| show ScannerConfiguration; |
| import 'package:front_end/src/api_prototype/compiler_options.dart'; |
| import 'package:front_end/src/api_prototype/file_system.dart'; |
| import 'package:front_end/src/base/processed_options.dart'; |
| import 'package:front_end/src/fasta/compiler_context.dart'; |
| import 'package:front_end/src/fasta/uri_translator.dart'; |
| import 'package:front_end/src/fasta/util/parser_ast_helper.dart'; |
| import 'package:front_end/src/fasta/util/textual_outline.dart'; |
| import 'package:_fe_analyzer_shared/src/parser/identifier_context.dart'; |
| import 'package:kernel/target/targets.dart'; |
| |
| import "parser_ast.dart"; |
| import "abstracted_ast_nodes.dart"; |
| |
| // Overall TODO(s): |
| // * If entry is given as fileuri but exists as different import uri... |
| // Does that matter? |
| // * Setters vs non-setters with naming conflicts. |
| // * -> also these might be found on "different levels", e.g. the setter might |
| // be in the class and the getter might be in an import. |
| // * show/hide on imports and exports. |
| // * Handle importing/exporting non-existing files. |
| // * Tests. |
| // * Maybe bypass the direct-from-parser-ast stuff for speed? |
| // * Probably some of the special classes can be combined if we want to |
| // (e.g. Class and Mixin). |
| // * Extensions --- we currently basically mark all we see. |
| // => Could be perhaps only include them if the class they're talking about |
| // is included? (or we don't know). |
| // * E.g. "factory Abc.b() => Abc3();" is the same as |
| // "factory Abc.b() { return Abc3(); }" and Abc3 shouldn't be marked by it. |
| // -> This is basically a rough edge on the textual outline though. |
| // -> Also, the same applies to other instances of "=>". |
| // * It shouldn't lookup private stuff in other libraries. |
| // * Could there be made a distinction between for instance |
| // `IdentifierContext.typeReference` and `IdentifierContext.expression`? |
| // => one might not have to include content of classes that only talk about |
| // typeReference I think. |
| |
| Future<void> main(List<String> args) async { |
| if (args.length != 2) { |
| throw "Needs 2 arguments: packages file/dir and file to process."; |
| } |
| Uri packages = Uri.base.resolve(args[0]); |
| Uri file = Uri.base.resolve(args[1]); |
| for (int i = 0; i < 1; i++) { |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await extractOutline([file], packages: packages, verbosityLevel: 40); |
| print("Finished in ${stopwatch.elapsedMilliseconds} ms " |
| "(textual outline was " |
| "${latestProcessor!.textualOutlineStopwatch.elapsedMilliseconds} ms)" |
| "(get ast was " |
| "${latestProcessor!.getAstStopwatch.elapsedMilliseconds} ms)" |
| "(extract identifier was " |
| "${latestProcessor!.extractIdentifierStopwatch.elapsedMilliseconds} ms)" |
| ""); |
| } |
| } |
| |
| _Processor? latestProcessor; |
| |
| Future<Map<Uri, String>> extractOutline(List<Uri> entryPointUris, |
| {Uri? sdk, |
| required Uri? packages, |
| Uri? platform, |
| Target? target, |
| int verbosityLevel: 0}) { |
| CompilerOptions options = new CompilerOptions() |
| ..target = target |
| ..packagesFileUri = packages |
| ..sdkSummary = platform |
| ..sdkRoot = sdk; |
| ProcessedOptions pOptions = |
| new ProcessedOptions(options: options, inputs: entryPointUris); |
| return CompilerContext.runWithOptions(pOptions, (CompilerContext c) async { |
| FileSystem fileSystem = c.options.fileSystem; |
| UriTranslator uriTranslator = await c.options.getUriTranslator(); |
| _Processor processor = |
| new _Processor(verbosityLevel, fileSystem, uriTranslator); |
| latestProcessor = processor; |
| List<TopLevel> entryPoints = []; |
| for (Uri entryPointUri in entryPointUris) { |
| TopLevel entryPoint = await processor.preprocessUri(entryPointUri); |
| entryPoints.add(entryPoint); |
| } |
| return await processor.calculate(entryPoints); |
| }); |
| } |
| |
| class _Processor { |
| final FileSystem fileSystem; |
| final UriTranslator uriTranslator; |
| final int verbosityLevel; |
| |
| final Stopwatch textualOutlineStopwatch = new Stopwatch(); |
| final Stopwatch getAstStopwatch = new Stopwatch(); |
| final Stopwatch extractIdentifierStopwatch = new Stopwatch(); |
| |
| Map<Uri, TopLevel> parsed = {}; |
| |
| _Processor(this.verbosityLevel, this.fileSystem, this.uriTranslator); |
| |
| void log(String s) { |
| if (verbosityLevel <= 0) return; |
| print(s); |
| } |
| |
| Future<TopLevel> preprocessUri(Uri importUri, {Uri? partOf}) async { |
| if (verbosityLevel >= 20) log("$importUri =>"); |
| Uri fileUri = importUri; |
| if (importUri.scheme == "package") { |
| fileUri = uriTranslator.translate(importUri)!; |
| } |
| if (verbosityLevel >= 20) log("$fileUri"); |
| final List<int> bytes = |
| await fileSystem.entityForUri(fileUri).readAsBytes(); |
| // TODO: Support updating the configuration; also default it to match |
| // the package version. |
| final ScannerConfiguration configuration = new ScannerConfiguration( |
| enableExtensionMethods: true, |
| enableNonNullable: true, |
| enableTripleShift: true); |
| textualOutlineStopwatch.start(); |
| final String? outlined = textualOutline(bytes, configuration); |
| textualOutlineStopwatch.stop(); |
| if (outlined == null) throw "Textual outline returned null"; |
| final List<int> bytes2 = utf8.encode(outlined); |
| getAstStopwatch.start(); |
| List<Token> languageVersionsSeen = []; |
| final ParserAstNode ast = getAST(bytes2, |
| enableExtensionMethods: configuration.enableExtensionMethods, |
| enableNonNullable: configuration.enableNonNullable, |
| enableTripleShift: configuration.enableTripleShift, |
| languageVersionsSeen: languageVersionsSeen); |
| getAstStopwatch.stop(); |
| |
| _ParserAstVisitor visitor = new _ParserAstVisitor( |
| verbosityLevel, outlined, importUri, partOf, ast, languageVersionsSeen); |
| TopLevel topLevel = visitor.currentContainer as TopLevel; |
| if (parsed[importUri] != null) throw "$importUri already set?!?"; |
| parsed[importUri] = topLevel; |
| visitor.accept(ast); |
| topLevel.buildScope(); |
| |
| _IdentifierExtractor identifierExtractor = new _IdentifierExtractor(); |
| extractIdentifierStopwatch.start(); |
| identifierExtractor.extract(ast); |
| extractIdentifierStopwatch.stop(); |
| for (IdentifierHandle identifier in identifierExtractor.identifiers) { |
| if (identifier.context == IdentifierContext.typeVariableDeclaration) { |
| // Hack: Put type variable declarations into scope so any overlap in |
| // name doesn't mark usages (e.g. a class E shouldn't be marked if we're |
| // talking about the type variable E). |
| ParserAstNode content = identifier; |
| AstNode? nearestAstNode = visitor.map[content]; |
| while (nearestAstNode == null && content.parent != null) { |
| content = content.parent!; |
| nearestAstNode = visitor.map[content]; |
| } |
| if (nearestAstNode == null) { |
| content = identifier; |
| nearestAstNode = visitor.map[content]; |
| while (nearestAstNode == null && content.parent != null) { |
| content = content.parent!; |
| nearestAstNode = visitor.map[content]; |
| } |
| |
| StringBuffer sb = new StringBuffer(); |
| Token t = identifier.token; |
| // for(int i = 0; i < 10; i++) { |
| // t = t.previous!; |
| // } |
| for (int i = 0; i < 20; i++) { |
| sb.write("$t "); |
| t = t.next!; |
| } |
| throw "$fileUri --- couldn't even find nearest ast node for " |
| "${identifier.token} :( -- context $sb"; |
| } |
| (nearestAstNode.scope[identifier.token.lexeme] ??= []) |
| .add(nearestAstNode); |
| } |
| } |
| |
| return topLevel; |
| } |
| |
| Future<void> _premarkTopLevel(List<_TopLevelAndAstNode> worklist, |
| Set<TopLevel> closed, TopLevel entrypointish) async { |
| if (!closed.add(entrypointish)) return; |
| |
| for (AstNode child in entrypointish.children) { |
| child.marked = Coloring.Marked; |
| worklist.add(new _TopLevelAndAstNode(entrypointish, child)); |
| |
| if (child is Part) { |
| if (child.uri.scheme != "dart") { |
| TopLevel partTopLevel = parsed[child.uri] ?? |
| await preprocessUri(child.uri, partOf: entrypointish.uri); |
| await _premarkTopLevel(worklist, closed, partTopLevel); |
| } |
| } else if (child is Export) { |
| for (Uri importedUri in child.uris) { |
| if (importedUri.scheme != "dart") { |
| TopLevel exportTopLevel = |
| parsed[importedUri] ?? await preprocessUri(importedUri); |
| await _premarkTopLevel(worklist, closed, exportTopLevel); |
| } |
| } |
| } |
| } |
| } |
| |
| Future<List<TopLevel>> _preprocessImportsAsNeeded( |
| Map<TopLevel, List<TopLevel>> imports, TopLevel topLevel) async { |
| List<TopLevel>? imported = imports[topLevel]; |
| if (imported == null) { |
| // Process all imports. |
| imported = []; |
| imports[topLevel] = imported; |
| for (AstNode child in topLevel.children) { |
| if (child is Import) { |
| child.marked = Coloring.Marked; |
| for (Uri importedUri in child.uris) { |
| if (importedUri.scheme != "dart") { |
| TopLevel importedTopLevel = |
| parsed[importedUri] ?? await preprocessUri(importedUri); |
| imported.add(importedTopLevel); |
| } |
| } |
| } else if (child is PartOf) { |
| child.marked = Coloring.Marked; |
| if (child.partOfUri.scheme != "dart") { |
| TopLevel part = parsed[child.partOfUri]!; |
| List<TopLevel> importsFromPart = |
| await _preprocessImportsAsNeeded(imports, part); |
| imported.addAll(importsFromPart); |
| } |
| } |
| } |
| } |
| return imported; |
| } |
| |
| Future<Map<Uri, String>> calculate(List<TopLevel> entryPoints) async { |
| List<_TopLevelAndAstNode> worklist = []; |
| Map<TopLevel, List<TopLevel>> imports = {}; |
| |
| // Mark all top-level in entry point. Also include parts and exports (and |
| // exports exports etc) of the entry point. |
| Set<TopLevel> closed = {}; |
| for (TopLevel entryPoint in entryPoints) { |
| await _premarkTopLevel(worklist, closed, entryPoint); |
| } |
| |
| Map<TopLevel, Set<String>> lookupsAll = {}; |
| Map<TopLevel, List<String>> lookupsWorklist = {}; |
| while (worklist.isNotEmpty || lookupsWorklist.isNotEmpty) { |
| while (worklist.isNotEmpty) { |
| _TopLevelAndAstNode entry = worklist.removeLast(); |
| if (verbosityLevel >= 20) { |
| log("\n-----\nProcessing ${entry.entry.node.toString()}"); |
| } |
| _IdentifierExtractor identifierExtractor = new _IdentifierExtractor(); |
| identifierExtractor.extract(entry.entry.node); |
| if (verbosityLevel >= 20) { |
| log("Found ${identifierExtractor.identifiers}"); |
| } |
| List<AstNode>? prevLookupResult; |
| nextIdentifier: |
| for (IdentifierHandle identifier in identifierExtractor.identifiers) { |
| ParserAstNode content = identifier; |
| AstNode? nearestAstNode = entry.topLevel.map[content]; |
| while (nearestAstNode == null && content.parent != null) { |
| content = content.parent!; |
| nearestAstNode = entry.topLevel.map[content]; |
| } |
| if (nearestAstNode == null) { |
| throw "couldn't even find nearest ast node for " |
| "${identifier.token} :("; |
| } |
| |
| if (identifier.context == IdentifierContext.typeReference || |
| identifier.context == IdentifierContext.prefixedTypeReference || |
| identifier.context == |
| IdentifierContext.typeReferenceContinuation || |
| identifier.context == IdentifierContext.constructorReference || |
| identifier.context == |
| IdentifierContext.constructorReferenceContinuation || |
| identifier.context == IdentifierContext.expression || |
| identifier.context == IdentifierContext.expressionContinuation || |
| identifier.context == IdentifierContext.metadataReference || |
| identifier.context == IdentifierContext.metadataContinuation) { |
| bool lookupInThisScope = true; |
| if (!identifier.context.isContinuation) { |
| prevLookupResult = null; |
| } else if (prevLookupResult != null) { |
| // In continuation. |
| // either 0 or all should be imports. |
| for (AstNode prevResult in prevLookupResult) { |
| if (prevResult is Import) { |
| lookupInThisScope = false; |
| } else { |
| continue nextIdentifier; |
| } |
| } |
| } else { |
| // Still in continuation --- but prev lookup didn't yield |
| // anything. We shouldn't search for the continuation part in this |
| // scope (and thus skip looking in imports). |
| lookupInThisScope = false; |
| } |
| if (verbosityLevel >= 20) { |
| log("${identifier.token} (${identifier.context})"); |
| } |
| |
| // Now we need parts at this point. Either we're in the entry point |
| // in which case parts was read by [_premarkTopLevel], or we're here |
| // via lookups on an import, where parts were read too. |
| List<AstNode>? lookedUp; |
| if (lookupInThisScope) { |
| lookedUp = findInScope( |
| identifier.token.lexeme, nearestAstNode, entry.topLevel); |
| prevLookupResult = lookedUp; |
| } |
| if (lookedUp != null) { |
| for (AstNode found in lookedUp) { |
| if (verbosityLevel >= 20) log(" => found $found"); |
| if (found.marked == Coloring.Untouched) { |
| found.marked = Coloring.Marked; |
| TopLevel foundTopLevel = entry.topLevel; |
| if (found.parent is TopLevel) { |
| foundTopLevel = found.parent as TopLevel; |
| } |
| worklist.add(new _TopLevelAndAstNode(foundTopLevel, found)); |
| } |
| } |
| } else { |
| if (verbosityLevel >= 20) { |
| log("=> Should find this via an import probably?"); |
| } |
| |
| List<TopLevel> imported = |
| await _preprocessImportsAsNeeded(imports, entry.topLevel); |
| |
| Set<Uri>? wantedImportUrls; |
| if (!lookupInThisScope && prevLookupResult != null) { |
| for (AstNode castMeAsImport in prevLookupResult) { |
| Import import = castMeAsImport as Import; |
| assert(import.asName != null); |
| (wantedImportUrls ??= {}).addAll(import.uris); |
| } |
| } |
| |
| for (TopLevel other in imported) { |
| if (!lookupInThisScope && prevLookupResult != null) { |
| assert(wantedImportUrls != null); |
| if (!wantedImportUrls!.contains(other.uri)) continue; |
| } |
| |
| Set<String> lookupStrings = lookupsAll[other] ??= {}; |
| if (lookupStrings.add(identifier.token.lexeme)) { |
| List<String> lookupStringsWorklist = |
| lookupsWorklist[other] ??= []; |
| lookupStringsWorklist.add(identifier.token.lexeme); |
| } |
| } |
| } |
| } else { |
| if (verbosityLevel >= 30) { |
| log("Ignoring ${identifier.token} as it's a " |
| "${identifier.context}"); |
| } |
| } |
| } |
| } |
| Map<TopLevel, List<String>> lookupsWorklistTmp = {}; |
| for (MapEntry<TopLevel, List<String>> lookups |
| in lookupsWorklist.entries) { |
| TopLevel topLevel = lookups.key; |
| // We have to make the same lookups in parts and exports too. |
| for (AstNode child in topLevel.children) { |
| TopLevel? other; |
| if (child is Part) { |
| child.marked = Coloring.Marked; |
| // do stuff to part. |
| if (child.uri.scheme != "dart") { |
| other = parsed[child.uri] ?? |
| await preprocessUri(child.uri, partOf: topLevel.uri); |
| } |
| } else if (child is Export) { |
| child.marked = Coloring.Marked; |
| // do stuff to export. |
| for (Uri importedUri in child.uris) { |
| if (importedUri.scheme != "dart") { |
| other = parsed[importedUri] ?? await preprocessUri(importedUri); |
| } |
| } |
| } else if (child is Extension) { |
| // TODO: Maybe put on a list to process later and only include if |
| // the on-class is included? |
| if (child.marked == Coloring.Untouched) { |
| child.marked = Coloring.Marked; |
| worklist.add(new _TopLevelAndAstNode(topLevel, child)); |
| } |
| } |
| if (other != null) { |
| Set<String> lookupStrings = lookupsAll[other] ??= {}; |
| for (String identifier in lookups.value) { |
| if (lookupStrings.add(identifier)) { |
| List<String> lookupStringsWorklist = |
| lookupsWorklistTmp[other] ??= []; |
| lookupStringsWorklist.add(identifier); |
| } |
| } |
| } |
| } |
| |
| for (String identifier in lookups.value) { |
| List<AstNode>? foundInScope = topLevel.findInScope(identifier); |
| if (foundInScope != null) { |
| for (AstNode found in foundInScope) { |
| if (found.marked == Coloring.Untouched) { |
| found.marked = Coloring.Marked; |
| worklist.add(new _TopLevelAndAstNode(topLevel, found)); |
| } |
| if (verbosityLevel >= 20) { |
| log(" => found $found via import (${found.marked})"); |
| } |
| } |
| } |
| } |
| } |
| lookupsWorklist = lookupsWorklistTmp; |
| } |
| |
| if (verbosityLevel >= 40) { |
| log("\n\n---------\n\n"); |
| log(parsed.toString()); |
| log("\n\n---------\n\n"); |
| } |
| |
| // Extract. |
| int count = 0; |
| Map<Uri, String> result = {}; |
| // We only read imports if we need to lookup in them, but if a import |
| // statement is included in the output the file has to exist if it actually |
| // exists to not get a compilation error. |
| Set<Uri> imported = {}; |
| for (MapEntry<Uri, TopLevel> entry in parsed.entries) { |
| if (verbosityLevel >= 40) log("${entry.key}:"); |
| StringBuffer sb = new StringBuffer(); |
| for (AstNode child in entry.value.children) { |
| if (child.marked == Coloring.Marked) { |
| String substring = entry.value.sourceText.substring( |
| child.startInclusive.charOffset, child.endInclusive.charEnd); |
| sb.writeln(substring); |
| if (verbosityLevel >= 40) { |
| log(substring); |
| } |
| if (child is Import) { |
| for (Uri importedUri in child.uris) { |
| if (importedUri.scheme != "dart") { |
| imported.add(importedUri); |
| } |
| } |
| } |
| } |
| } |
| if (sb.isNotEmpty) count++; |
| Uri uri = entry.key; |
| Uri fileUri = uri; |
| if (uri.scheme == "package") { |
| fileUri = uriTranslator.translate(uri)!; |
| } |
| result[fileUri] = sb.toString(); |
| } |
| for (Uri uri in imported) { |
| TopLevel? topLevel = parsed[uri]; |
| if (topLevel != null) continue; |
| // uri imports a file we haven't read. Check if it exists and include it |
| // as an empty file if it does. |
| Uri fileUri = uri; |
| if (uri.scheme == "package") { |
| fileUri = uriTranslator.translate(uri)!; |
| } |
| if (await fileSystem.entityForUri(fileUri).exists()) { |
| result[fileUri] = ""; |
| } |
| } |
| |
| print("=> Long story short got it to $count non-empty files..."); |
| |
| return result; |
| } |
| |
| List<AstNode>? findInScope( |
| String name, AstNode nearestAstNode, TopLevel topLevel, |
| {Set<TopLevel>? visited}) { |
| List<AstNode>? result; |
| result = nearestAstNode.findInScope(name); |
| if (result != null) return result; |
| for (AstNode child in topLevel.children) { |
| if (child is Part) { |
| visited ??= {topLevel}; |
| TopLevel partTopLevel = parsed[child.uri]!; |
| if (visited.add(partTopLevel)) { |
| result = |
| findInScope(name, partTopLevel, partTopLevel, visited: visited); |
| if (result != null) return result; |
| } |
| } else if (child is PartOf) { |
| visited ??= {topLevel}; |
| TopLevel partOwnerTopLevel = parsed[child.partOfUri]!; |
| if (visited.add(partOwnerTopLevel)) { |
| result = findInScope(name, partOwnerTopLevel, partOwnerTopLevel, |
| visited: visited); |
| if (result != null) return result; |
| } |
| } |
| } |
| return null; |
| } |
| } |
| |
| class _TopLevelAndAstNode { |
| final TopLevel topLevel; |
| final AstNode entry; |
| |
| _TopLevelAndAstNode(this.topLevel, this.entry); |
| } |
| |
| class _IdentifierExtractor { |
| List<IdentifierHandle> identifiers = []; |
| |
| void extract(ParserAstNode ast) { |
| if (ast is IdentifierHandle) { |
| identifiers.add(ast); |
| } |
| List<ParserAstNode>? children = ast.children; |
| if (children != null) { |
| for (ParserAstNode child in children) { |
| extract(child); |
| } |
| } |
| } |
| } |
| |
| class _ParserAstVisitor extends ParserAstVisitor { |
| final Uri uri; |
| final Uri? partOfUri; |
| late Container currentContainer; |
| final Map<ParserAstNode, AstNode> map = {}; |
| final int verbosityLevel; |
| final List<Token> languageVersionsSeen; |
| |
| _ParserAstVisitor(this.verbosityLevel, String sourceText, this.uri, |
| this.partOfUri, ParserAstNode rootAst, this.languageVersionsSeen) { |
| currentContainer = new TopLevel(sourceText, uri, rootAst, map); |
| if (languageVersionsSeen.isNotEmpty) { |
| // Use first one. |
| Token languageVersion = languageVersionsSeen.first; |
| ParserAstNode dummyNode = new NoInitializersHandle(ParserAstType.HANDLE); |
| LanguageVersion version = |
| new LanguageVersion(dummyNode, languageVersion, languageVersion); |
| version.marked = Coloring.Marked; |
| currentContainer.addChild(version, map); |
| } |
| } |
| |
| void log(String s) { |
| if (verbosityLevel <= 0) return; |
| Container? x = currentContainer.parent; |
| int level = 0; |
| while (x != null) { |
| level++; |
| x = x.parent; |
| } |
| print(" " * level + s); |
| } |
| |
| @override |
| void visitClass( |
| ClassDeclarationEnd node, Token startInclusive, Token endInclusive) { |
| TopLevelDeclarationEnd parent = node.parent! as TopLevelDeclarationEnd; |
| IdentifierHandle identifier = parent.getIdentifier(); |
| |
| log("Hello from class ${identifier.token}"); |
| |
| Class cls = new Class( |
| parent, identifier.token.lexeme, startInclusive, endInclusive); |
| currentContainer.addChild(cls, map); |
| |
| Container previousContainer = currentContainer; |
| currentContainer = cls; |
| super.visitClass(node, startInclusive, endInclusive); |
| currentContainer = previousContainer; |
| } |
| |
| @override |
| void visitClassConstructor( |
| ClassConstructorEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Class); |
| List<IdentifierHandle> ids = node.getIdentifiers(); |
| if (ids.length == 1) { |
| ClassConstructor classConstructor = new ClassConstructor( |
| node, ids.single.token.lexeme, startInclusive, endInclusive); |
| currentContainer.addChild(classConstructor, map); |
| log("Hello from constructor ${ids.single.token}"); |
| } else if (ids.length == 2) { |
| ClassConstructor classConstructor = new ClassConstructor(node, |
| "${ids.first.token}.${ids.last.token}", startInclusive, endInclusive); |
| map[node] = classConstructor; |
| currentContainer.addChild(classConstructor, map); |
| log("Hello from constructor ${ids.first.token}.${ids.last.token}"); |
| } else { |
| throw "Unexpected identifiers in class constructor"; |
| } |
| |
| super.visitClassConstructor(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitClassFactoryMethod( |
| ClassFactoryMethodEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Class); |
| List<IdentifierHandle> ids = node.getIdentifiers(); |
| if (ids.length == 1) { |
| ClassFactoryMethod classFactoryMethod = new ClassFactoryMethod( |
| node, ids.single.token.lexeme, startInclusive, endInclusive); |
| currentContainer.addChild(classFactoryMethod, map); |
| log("Hello from factory method ${ids.single.token}"); |
| } else if (ids.length == 2) { |
| ClassFactoryMethod classFactoryMethod = new ClassFactoryMethod(node, |
| "${ids.first.token}.${ids.last.token}", startInclusive, endInclusive); |
| map[node] = classFactoryMethod; |
| currentContainer.addChild(classFactoryMethod, map); |
| log("Hello from factory method ${ids.first.token}.${ids.last.token}"); |
| } else { |
| Container findTopLevel = currentContainer; |
| while (findTopLevel is! TopLevel) { |
| findTopLevel = findTopLevel.parent!; |
| } |
| String src = findTopLevel.sourceText |
| .substring(startInclusive.charOffset, endInclusive.charEnd); |
| throw "Unexpected identifiers in class factory method: $ids " |
| "(${ids.map((e) => e.token.lexeme).toList()}) --- " |
| "error on source ${src} --- " |
| "${node.children}"; |
| } |
| |
| super.visitClassFactoryMethod(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitClassFields( |
| ClassFieldsEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Class); |
| List<String> fields = |
| node.getFieldIdentifiers().map((e) => e.token.lexeme).toList(); |
| ClassFields classFields = |
| new ClassFields(node, fields, startInclusive, endInclusive); |
| currentContainer.addChild(classFields, map); |
| log("Hello from class fields ${fields.join(", ")}"); |
| super.visitClassFields(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitClassMethod( |
| ClassMethodEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Class); |
| |
| String identifier; |
| try { |
| identifier = node.getNameIdentifier(); |
| } catch (e) { |
| Container findTopLevel = currentContainer; |
| while (findTopLevel is! TopLevel) { |
| findTopLevel = findTopLevel.parent!; |
| } |
| String src = findTopLevel.sourceText |
| .substring(startInclusive.charOffset, endInclusive.charEnd); |
| throw "Unexpected identifiers in visitClassMethod --- " |
| "error on source ${src} --- " |
| "${node.children}"; |
| } |
| ClassMethod classMethod = |
| new ClassMethod(node, identifier, startInclusive, endInclusive); |
| currentContainer.addChild(classMethod, map); |
| log("Hello from class method $identifier"); |
| super.visitClassMethod(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitEnum(EnumEnd node, Token startInclusive, Token endInclusive) { |
| List<IdentifierHandle> ids = node.getIdentifiers(); |
| |
| Enum e = new Enum( |
| node, |
| ids.first.token.lexeme, |
| ids.skip(1).map((e) => e.token.lexeme).toList(), |
| startInclusive, |
| endInclusive); |
| currentContainer.addChild(e, map); |
| |
| log("Hello from enum ${ids.first.token} with content " |
| "${ids.skip(1).map((e) => e.token).join(", ")}"); |
| super.visitEnum(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitExport(ExportEnd node, Token startInclusive, Token endInclusive) { |
| String uriString = node.getExportUriString(); |
| Uri exportUri = uri.resolve(uriString); |
| List<String>? conditionalUriStrings = node.getConditionalExportUriStrings(); |
| List<Uri>? conditionalUris; |
| if (conditionalUriStrings != null) { |
| conditionalUris = []; |
| for (String conditionalUri in conditionalUriStrings) { |
| conditionalUris.add(uri.resolve(conditionalUri)); |
| } |
| } |
| // TODO: Use 'show' and 'hide' stuff. |
| Export e = new Export( |
| node, exportUri, conditionalUris, startInclusive, endInclusive); |
| currentContainer.addChild(e, map); |
| log("Hello export"); |
| } |
| |
| @override |
| void visitExtension( |
| ExtensionDeclarationEnd node, Token startInclusive, Token endInclusive) { |
| ExtensionDeclarationBegin begin = |
| node.children!.first as ExtensionDeclarationBegin; |
| TopLevelDeclarationEnd parent = node.parent! as TopLevelDeclarationEnd; |
| log("Hello from extension ${begin.name}"); |
| Extension extension = |
| new Extension(parent, begin.name?.lexeme, startInclusive, endInclusive); |
| currentContainer.addChild(extension, map); |
| |
| Container previousContainer = currentContainer; |
| currentContainer = extension; |
| super.visitExtension(node, startInclusive, endInclusive); |
| currentContainer = previousContainer; |
| } |
| |
| @override |
| void visitExtensionConstructor( |
| ExtensionConstructorEnd node, Token startInclusive, Token endInclusive) { |
| // TODO: implement visitExtensionConstructor |
| throw node; |
| } |
| |
| @override |
| void visitExtensionFactoryMethod(ExtensionFactoryMethodEnd node, |
| Token startInclusive, Token endInclusive) { |
| // TODO: implement visitExtensionFactoryMethod |
| throw node; |
| } |
| |
| @override |
| void visitExtensionFields( |
| ExtensionFieldsEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Extension); |
| List<String> fields = |
| node.getFieldIdentifiers().map((e) => e.token.lexeme).toList(); |
| ExtensionFields classFields = |
| new ExtensionFields(node, fields, startInclusive, endInclusive); |
| currentContainer.addChild(classFields, map); |
| log("Hello from extension fields ${fields.join(", ")}"); |
| super.visitExtensionFields(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitExtensionMethod( |
| ExtensionMethodEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Extension); |
| ExtensionMethod extensionMethod = new ExtensionMethod( |
| node, node.getNameIdentifier(), startInclusive, endInclusive); |
| currentContainer.addChild(extensionMethod, map); |
| log("Hello from extension method ${node.getNameIdentifier()}"); |
| super.visitExtensionMethod(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitImport(ImportEnd node, Token startInclusive, Token? endInclusive) { |
| IdentifierHandle? prefix = node.getImportPrefix(); |
| String uriString = node.getImportUriString(); |
| Uri importUri = uri.resolve(uriString); |
| List<String>? conditionalUriStrings = node.getConditionalImportUriStrings(); |
| List<Uri>? conditionalUris; |
| if (conditionalUriStrings != null) { |
| conditionalUris = []; |
| for (String conditionalUri in conditionalUriStrings) { |
| conditionalUris.add(uri.resolve(conditionalUri)); |
| } |
| } |
| // TODO: Use 'show' and 'hide' stuff. |
| |
| // endInclusive can be null on syntax errors and there's recovery of the |
| // import. For now we'll ignore this. |
| Import i = new Import(node, importUri, conditionalUris, |
| prefix?.token.lexeme, startInclusive, endInclusive!); |
| currentContainer.addChild(i, map); |
| if (prefix == null) { |
| log("Hello import"); |
| } else { |
| log("Hello import as '${prefix.token}'"); |
| } |
| } |
| |
| @override |
| void visitLibraryName( |
| LibraryNameEnd node, Token startInclusive, Token endInclusive) { |
| LibraryName name = new LibraryName(node, startInclusive, endInclusive); |
| name.marked = Coloring.Marked; |
| currentContainer.addChild(name, map); |
| } |
| |
| @override |
| void visitMetadata( |
| MetadataEnd node, Token startInclusive, Token endInclusive) { |
| Metadata m = new Metadata(node, startInclusive, endInclusive); |
| currentContainer.addChild(m, map); |
| } |
| |
| @override |
| void visitMixin( |
| MixinDeclarationEnd node, Token startInclusive, Token endInclusive) { |
| TopLevelDeclarationEnd parent = node.parent! as TopLevelDeclarationEnd; |
| IdentifierHandle identifier = parent.getIdentifier(); |
| log("Hello from mixin ${identifier.token}"); |
| |
| Mixin mixin = new Mixin( |
| parent, identifier.token.lexeme, startInclusive, endInclusive); |
| currentContainer.addChild(mixin, map); |
| |
| Container previousContainer = currentContainer; |
| currentContainer = mixin; |
| super.visitMixin(node, startInclusive, endInclusive); |
| currentContainer = previousContainer; |
| } |
| |
| @override |
| void visitMixinFields( |
| MixinFieldsEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Mixin); |
| List<String> fields = |
| node.getFieldIdentifiers().map((e) => e.token.lexeme).toList(); |
| MixinFields mixinFields = |
| new MixinFields(node, fields, startInclusive, endInclusive); |
| currentContainer.addChild(mixinFields, map); |
| log("Hello from mixin fields ${fields.join(", ")}"); |
| super.visitMixinFields(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitMixinMethod( |
| MixinMethodEnd node, Token startInclusive, Token endInclusive) { |
| assert(currentContainer is Mixin); |
| MixinMethod classMethod = new MixinMethod( |
| node, node.getNameIdentifier(), startInclusive, endInclusive); |
| currentContainer.addChild(classMethod, map); |
| log("Hello from mixin method ${node.getNameIdentifier()}"); |
| super.visitMixinMethod(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitNamedMixin( |
| NamedMixinApplicationEnd node, Token startInclusive, Token endInclusive) { |
| TopLevelDeclarationEnd parent = node.parent! as TopLevelDeclarationEnd; |
| IdentifierHandle identifier = parent.getIdentifier(); |
| log("Hello from named mixin ${identifier.token}"); |
| |
| Mixin mixin = new Mixin( |
| parent, identifier.token.lexeme, startInclusive, endInclusive); |
| currentContainer.addChild(mixin, map); |
| |
| Container previousContainer = currentContainer; |
| currentContainer = mixin; |
| super.visitNamedMixin(node, startInclusive, endInclusive); |
| currentContainer = previousContainer; |
| } |
| |
| @override |
| void visitPart(PartEnd node, Token startInclusive, Token endInclusive) { |
| String uriString = node.getPartUriString(); |
| Uri partUri = uri.resolve(uriString); |
| |
| Part i = new Part(node, partUri, startInclusive, endInclusive); |
| currentContainer.addChild(i, map); |
| log("Hello part"); |
| } |
| |
| @override |
| void visitPartOf(PartOfEnd node, Token startInclusive, Token endInclusive) { |
| // We'll assume we've gotten here via a "part" so we'll ignore that for now. |
| // TODO: partOfUri could - in an error case - be null. |
| PartOf partof = new PartOf(node, partOfUri!, startInclusive, endInclusive); |
| partof.marked = Coloring.Marked; |
| currentContainer.addChild(partof, map); |
| } |
| |
| @override |
| void visitTopLevelFields( |
| TopLevelFieldsEnd node, Token startInclusive, Token endInclusive) { |
| List<String> fields = |
| node.getFieldIdentifiers().map((e) => e.token.lexeme).toList(); |
| TopLevelFields f = |
| new TopLevelFields(node, fields, startInclusive, endInclusive); |
| currentContainer.addChild(f, map); |
| log("Hello from top level fields ${fields.join(", ")}"); |
| super.visitTopLevelFields(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitTopLevelMethod( |
| TopLevelMethodEnd node, Token startInclusive, Token endInclusive) { |
| TopLevelMethod m = new TopLevelMethod(node, |
| node.getNameIdentifier().token.lexeme, startInclusive, endInclusive); |
| currentContainer.addChild(m, map); |
| log("Hello from top level method ${node.getNameIdentifier().token}"); |
| super.visitTopLevelMethod(node, startInclusive, endInclusive); |
| } |
| |
| @override |
| void visitTypedef(TypedefEnd node, Token startInclusive, Token endInclusive) { |
| Typedef t = new Typedef(node, node.getNameIdentifier().token.lexeme, |
| startInclusive, endInclusive); |
| currentContainer.addChild(t, map); |
| log("Hello from typedef ${node.getNameIdentifier().token}"); |
| super.visitTypedef(node, startInclusive, endInclusive); |
| } |
| } |