mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 09:58:32 +00:00
[CFE] First stab at prototype for textually extracting outlines
Change-Id: Ib84b47f0acb553cee6d891d7bbcf865f4a57a65a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221080 Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
parent
4f6d813c3c
commit
ad6ffaab95
|
@ -4,6 +4,7 @@
|
|||
|
||||
analyzer:
|
||||
exclude:
|
||||
- outline_extraction_testcases/**
|
||||
- parser_testcases/**
|
||||
- test/analyser_ignored/**
|
||||
- test/class_hierarchy/data/**
|
||||
|
|
864
pkg/front_end/lib/src/fasta/util/abstracted_ast_nodes.dart
Normal file
864
pkg/front_end/lib/src/fasta/util/abstracted_ast_nodes.dart
Normal file
|
@ -0,0 +1,864 @@
|
|||
// 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 'package:_fe_analyzer_shared/src/scanner/token.dart';
|
||||
import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart';
|
||||
|
||||
enum Coloring { Untouched, Marked }
|
||||
|
||||
abstract class AstNode {
|
||||
Map<String, List<AstNode>> scope = {};
|
||||
Container? parent;
|
||||
DirectParserASTContent get node;
|
||||
Token get startInclusive;
|
||||
Token get endInclusive;
|
||||
|
||||
Coloring marked = Coloring.Untouched;
|
||||
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent);
|
||||
|
||||
void buildScope();
|
||||
Map<String, AstNode> selfScope();
|
||||
|
||||
List<AstNode>? findInScope(String name) {
|
||||
return scope[name] ?? parent?.findInScope(name);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Container extends AstNode {
|
||||
List<AstNode> _children = [];
|
||||
Iterable<AstNode> get children => _children;
|
||||
|
||||
void addChild(AstNode child, Map<DirectParserASTContent, AstNode> map) {
|
||||
child.parent = this;
|
||||
_children.add(child);
|
||||
map[child.node] = child;
|
||||
}
|
||||
}
|
||||
|
||||
class TopLevel extends Container {
|
||||
final String sourceText;
|
||||
final Uri uri;
|
||||
|
||||
@override
|
||||
final DirectParserASTContent node;
|
||||
|
||||
final Map<DirectParserASTContent, AstNode> map;
|
||||
|
||||
TopLevel(this.sourceText, this.uri, this.node, this.map);
|
||||
|
||||
@override
|
||||
String toString() => toStringInternal(new StringBuffer(), 0).toString();
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
if (_children.isEmpty) {
|
||||
String stringIndent = " " * ((indent + 1) * 2);
|
||||
sb.write(stringIndent);
|
||||
sb.writeln("(empty)");
|
||||
} else {
|
||||
for (AstNode node in _children) {
|
||||
sb.write(stringIndent);
|
||||
node.toStringInternal(sb, indent + 1);
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {
|
||||
for (AstNode child in _children) {
|
||||
child.buildScope();
|
||||
for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
|
||||
(scope[entry.key] ??= []).add(entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return const {};
|
||||
}
|
||||
|
||||
@override
|
||||
Token get endInclusive => throw new UnimplementedError();
|
||||
|
||||
@override
|
||||
Token get startInclusive => throw new UnimplementedError();
|
||||
}
|
||||
|
||||
class Class extends Container {
|
||||
@override
|
||||
final DirectParserASTContentTopLevelDeclarationEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Class(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Class $name");
|
||||
if (_children.isEmpty) {
|
||||
String stringIndent = " " * ((indent + 1) * 2);
|
||||
sb.write(stringIndent);
|
||||
sb.writeln("(empty)");
|
||||
} else {
|
||||
for (AstNode node in _children) {
|
||||
node.toStringInternal(sb, indent + 1);
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {
|
||||
for (AstNode child in _children) {
|
||||
child.buildScope();
|
||||
for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
|
||||
(scope[entry.key] ??= []).add(entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class Mixin extends Container {
|
||||
@override
|
||||
final DirectParserASTContentTopLevelDeclarationEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Mixin(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Mixin $name");
|
||||
if (_children.isEmpty) {
|
||||
String stringIndent = " " * ((indent + 1) * 2);
|
||||
sb.write(stringIndent);
|
||||
sb.writeln("(empty)");
|
||||
} else {
|
||||
for (AstNode node in _children) {
|
||||
node.toStringInternal(sb, indent + 1);
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {
|
||||
for (AstNode child in _children) {
|
||||
child.buildScope();
|
||||
for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
|
||||
(scope[entry.key] ??= []).add(entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class Extension extends Container {
|
||||
@override
|
||||
final DirectParserASTContentTopLevelDeclarationEnd node;
|
||||
final String? name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Extension(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Extension $name");
|
||||
if (_children.isEmpty) {
|
||||
String stringIndent = " " * ((indent + 1) * 2);
|
||||
sb.write(stringIndent);
|
||||
sb.writeln("(empty)");
|
||||
} else {
|
||||
for (AstNode node in _children) {
|
||||
node.toStringInternal(sb, indent + 1);
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {
|
||||
for (AstNode child in _children) {
|
||||
child.buildScope();
|
||||
for (MapEntry<String, AstNode> entry in child.selfScope().entries) {
|
||||
(scope[entry.key] ??= []).add(entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
if (name != null) {
|
||||
return {name!: this};
|
||||
} else {
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ClassConstructor extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentClassConstructorEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
ClassConstructor(
|
||||
this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Class constructor $name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
// TODO: Possibly this should be different...
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class ClassFactoryMethod extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentClassFactoryMethodEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
ClassFactoryMethod(
|
||||
this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Class factory constructor $name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
// TODO: Possibly this should be different...
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class ClassMethod extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentClassMethodEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
ClassMethod(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Class method $name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionMethod extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentExtensionMethodEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
ExtensionMethod(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Extension method $name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class MixinMethod extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentMixinMethodEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
MixinMethod(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Mixin method $name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class Enum extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentEnumEnd node;
|
||||
final String name;
|
||||
final List<String> members;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Enum(this.node, this.name, this.members, this.startInclusive,
|
||||
this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Enum $name with members $members");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {
|
||||
for (String child in members) {
|
||||
scope[child] = [this];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class Import extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentImportEnd node;
|
||||
final Uri firstUri;
|
||||
final List<Uri>? conditionalUris;
|
||||
final String? asName;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Import(this.node, this.firstUri, this.conditionalUris, this.asName,
|
||||
this.startInclusive, this.endInclusive);
|
||||
|
||||
List<Uri> get uris => [firstUri, ...?conditionalUris];
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
if (asName == null) {
|
||||
sb.writeln("Import of $uris");
|
||||
} else {
|
||||
sb.writeln("Import of $uris as '$asName'");
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
if (asName != null) {
|
||||
return {asName!: this};
|
||||
}
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
|
||||
class Export extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentExportEnd node;
|
||||
final Uri firstUri;
|
||||
final List<Uri>? conditionalUris;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Export(this.node, this.firstUri, this.conditionalUris, this.startInclusive,
|
||||
this.endInclusive);
|
||||
|
||||
List<Uri> get uris => [firstUri, ...?conditionalUris];
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Export of $uris");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
|
||||
class Part extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentPartEnd node;
|
||||
final Uri uri;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Part(this.node, this.uri, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Part $uri");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
|
||||
class TopLevelFields extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentTopLevelFieldsEnd node;
|
||||
final List<String> names;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
TopLevelFields(this.node, this.names, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Top level field(s) ${names.join(", ")}");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
Map<String, AstNode> scope = {};
|
||||
for (String name in names) {
|
||||
scope[name] = this;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
class TopLevelMethod extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentTopLevelMethodEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
TopLevelMethod(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Top level method $name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class Typedef extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentTypedefEnd node;
|
||||
final String name;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Typedef(this.node, this.name, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Top level method $name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return {name: this};
|
||||
}
|
||||
}
|
||||
|
||||
class ClassFields extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentClassFieldsEnd node;
|
||||
final List<String> names;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
ClassFields(this.node, this.names, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Class field(s) ${names.join(", ")}");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
Map<String, AstNode> scope = {};
|
||||
for (String name in names) {
|
||||
scope[name] = this;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
class MixinFields extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentMixinFieldsEnd node;
|
||||
final List<String> names;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
MixinFields(this.node, this.names, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Mixin field(s) ${names.join(", ")}");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
Map<String, AstNode> scope = {};
|
||||
for (String name in names) {
|
||||
scope[name] = this;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionFields extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentExtensionFieldsEnd node;
|
||||
final List<String> names;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
ExtensionFields(
|
||||
this.node, this.names, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("Extension field(s) ${names.join(", ")}");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
Map<String, AstNode> scope = {};
|
||||
for (String name in names) {
|
||||
scope[name] = this;
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
class Metadata extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentMetadataEnd node;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
Metadata(this.node, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("metadata");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
|
||||
class LibraryName extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentLibraryNameEnd node;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
LibraryName(this.node, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("library name");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
|
||||
class PartOf extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContentPartOfEnd node;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
final Uri partOfUri;
|
||||
|
||||
PartOf(this.node, this.partOfUri, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("part of");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageVersion extends AstNode {
|
||||
@override
|
||||
final DirectParserASTContent node;
|
||||
@override
|
||||
final Token startInclusive;
|
||||
@override
|
||||
final Token endInclusive;
|
||||
|
||||
LanguageVersion(this.node, this.startInclusive, this.endInclusive);
|
||||
|
||||
@override
|
||||
StringBuffer toStringInternal(StringBuffer sb, int indent) {
|
||||
String stringIndent = " " * (indent * 2);
|
||||
sb.write(stringIndent);
|
||||
if (marked != Coloring.Untouched) {
|
||||
sb.write("(marked) ");
|
||||
}
|
||||
sb.writeln("$startInclusive");
|
||||
return sb;
|
||||
}
|
||||
|
||||
@override
|
||||
void buildScope() {}
|
||||
|
||||
@override
|
||||
Map<String, AstNode> selfScope() {
|
||||
return const {};
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import 'dart:typed_data' show Uint8List;
|
|||
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/messages/codes.dart';
|
||||
import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
|
||||
show ScannerConfiguration;
|
||||
|
||||
|
@ -17,6 +18,13 @@ import 'package:_fe_analyzer_shared/src/scanner/utf8_bytes_scanner.dart'
|
|||
|
||||
import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token;
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/parser/listener.dart'
|
||||
show UnescapeErrorListener;
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/parser/identifier_context.dart';
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/parser/quote.dart' show unescapeString;
|
||||
|
||||
import '../source/diet_parser.dart';
|
||||
|
||||
import 'direct_parser_ast_helper.dart';
|
||||
|
@ -26,7 +34,8 @@ DirectParserASTContentCompilationUnitEnd getAST(List<int> rawBytes,
|
|||
bool includeComments: false,
|
||||
bool enableExtensionMethods: false,
|
||||
bool enableNonNullable: false,
|
||||
bool enableTripleShift: false}) {
|
||||
bool enableTripleShift: false,
|
||||
List<Token>? languageVersionsSeen}) {
|
||||
Uint8List bytes = new Uint8List(rawBytes.length + 1);
|
||||
bytes.setRange(0, rawBytes.length, rawBytes);
|
||||
|
||||
|
@ -42,6 +51,7 @@ DirectParserASTContentCompilationUnitEnd getAST(List<int> rawBytes,
|
|||
languageVersionChanged: (scanner, languageVersion) {
|
||||
// For now don't do anything, but having it (making it non-null) means the
|
||||
// configuration won't be reset.
|
||||
languageVersionsSeen?.add(languageVersion);
|
||||
},
|
||||
);
|
||||
Token firstToken = scanner.tokenize();
|
||||
|
@ -1018,15 +1028,60 @@ extension MemberExtension on DirectParserASTContentMemberEnd {
|
|||
}
|
||||
}
|
||||
|
||||
extension ClassFieldsExtension on DirectParserASTContentClassFieldsEnd {
|
||||
List<DirectParserASTContentIdentifierHandle?> getFieldIdentifiers() {
|
||||
// For now blindly assume that the last count identifiers are the names
|
||||
// of the fields.
|
||||
extension MixinFieldsExtension on DirectParserASTContentMixinFieldsEnd {
|
||||
List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
|
||||
int countLeft = count;
|
||||
List<DirectParserASTContentIdentifierHandle>? identifiers;
|
||||
for (int i = children!.length - 1; i >= 0; i--) {
|
||||
DirectParserASTContent child = children![i];
|
||||
if (child is DirectParserASTContentIdentifierHandle) {
|
||||
if (child is DirectParserASTContentIdentifierHandle &&
|
||||
child.context == IdentifierContext.fieldDeclaration) {
|
||||
countLeft--;
|
||||
if (identifiers == null) {
|
||||
identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
|
||||
count, child);
|
||||
} else {
|
||||
identifiers[countLeft] = child;
|
||||
}
|
||||
if (countLeft == 0) break;
|
||||
}
|
||||
}
|
||||
if (countLeft != 0) throw "Didn't find the expected number of identifiers";
|
||||
return identifiers ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtensionFieldsExtension on DirectParserASTContentExtensionFieldsEnd {
|
||||
List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
|
||||
int countLeft = count;
|
||||
List<DirectParserASTContentIdentifierHandle>? identifiers;
|
||||
for (int i = children!.length - 1; i >= 0; i--) {
|
||||
DirectParserASTContent child = children![i];
|
||||
if (child is DirectParserASTContentIdentifierHandle &&
|
||||
child.context == IdentifierContext.fieldDeclaration) {
|
||||
countLeft--;
|
||||
if (identifiers == null) {
|
||||
identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
|
||||
count, child);
|
||||
} else {
|
||||
identifiers[countLeft] = child;
|
||||
}
|
||||
if (countLeft == 0) break;
|
||||
}
|
||||
}
|
||||
if (countLeft != 0) throw "Didn't find the expected number of identifiers";
|
||||
return identifiers ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
extension ClassFieldsExtension on DirectParserASTContentClassFieldsEnd {
|
||||
List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
|
||||
int countLeft = count;
|
||||
List<DirectParserASTContentIdentifierHandle>? identifiers;
|
||||
for (int i = children!.length - 1; i >= 0; i--) {
|
||||
DirectParserASTContent child = children![i];
|
||||
if (child is DirectParserASTContentIdentifierHandle &&
|
||||
child.context == IdentifierContext.fieldDeclaration) {
|
||||
countLeft--;
|
||||
if (identifiers == null) {
|
||||
identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
|
||||
|
@ -1056,6 +1111,190 @@ extension ClassFieldsExtension on DirectParserASTContentClassFieldsEnd {
|
|||
}
|
||||
}
|
||||
|
||||
extension EnumExtension on DirectParserASTContentEnumEnd {
|
||||
List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
|
||||
List<DirectParserASTContentIdentifierHandle> ids = [];
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentIdentifierHandle) ids.add(child);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtensionDeclarationExtension
|
||||
on DirectParserASTContentExtensionDeclarationEnd {
|
||||
List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
|
||||
List<DirectParserASTContentIdentifierHandle> ids = [];
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentIdentifierHandle) ids.add(child);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
||||
extension TopLevelMethodExtension on DirectParserASTContentTopLevelMethodEnd {
|
||||
DirectParserASTContentIdentifierHandle getNameIdentifier() {
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentIdentifierHandle) {
|
||||
if (child.context == IdentifierContext.topLevelFunctionDeclaration) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw "Didn't find the name identifier!";
|
||||
}
|
||||
}
|
||||
|
||||
extension TypedefExtension on DirectParserASTContentTypedefEnd {
|
||||
DirectParserASTContentIdentifierHandle getNameIdentifier() {
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentIdentifierHandle) {
|
||||
if (child.context == IdentifierContext.typedefDeclaration) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw "Didn't find the name identifier!";
|
||||
}
|
||||
}
|
||||
|
||||
extension ImportExtension on DirectParserASTContentImportEnd {
|
||||
DirectParserASTContentIdentifierHandle? getImportPrefix() {
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentIdentifierHandle) {
|
||||
if (child.context == IdentifierContext.importPrefixDeclaration) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getImportUriString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
bool foundOne = false;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentLiteralStringEnd) {
|
||||
DirectParserASTContentLiteralStringBegin uri =
|
||||
child.children!.single as DirectParserASTContentLiteralStringBegin;
|
||||
sb.write(unescapeString(
|
||||
uri.token.lexeme, uri.token, const UnescapeErrorListenerDummy()));
|
||||
foundOne = true;
|
||||
}
|
||||
}
|
||||
if (!foundOne) throw "Didn't find any";
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
List<String>? getConditionalImportUriStrings() {
|
||||
List<String>? result;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentConditionalUrisEnd) {
|
||||
for (DirectParserASTContent child2 in child.children!) {
|
||||
if (child2 is DirectParserASTContentConditionalUriEnd) {
|
||||
DirectParserASTContentLiteralStringEnd end =
|
||||
child2.children!.last as DirectParserASTContentLiteralStringEnd;
|
||||
DirectParserASTContentLiteralStringBegin uri = end.children!.single
|
||||
as DirectParserASTContentLiteralStringBegin;
|
||||
(result ??= []).add(unescapeString(uri.token.lexeme, uri.token,
|
||||
const UnescapeErrorListenerDummy()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
extension ExportExtension on DirectParserASTContentExportEnd {
|
||||
String getExportUriString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
bool foundOne = false;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentLiteralStringEnd) {
|
||||
DirectParserASTContentLiteralStringBegin uri =
|
||||
child.children!.single as DirectParserASTContentLiteralStringBegin;
|
||||
sb.write(unescapeString(
|
||||
uri.token.lexeme, uri.token, const UnescapeErrorListenerDummy()));
|
||||
foundOne = true;
|
||||
}
|
||||
}
|
||||
if (!foundOne) throw "Didn't find any";
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
List<String>? getConditionalExportUriStrings() {
|
||||
List<String>? result;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentConditionalUrisEnd) {
|
||||
for (DirectParserASTContent child2 in child.children!) {
|
||||
if (child2 is DirectParserASTContentConditionalUriEnd) {
|
||||
DirectParserASTContentLiteralStringEnd end =
|
||||
child2.children!.last as DirectParserASTContentLiteralStringEnd;
|
||||
DirectParserASTContentLiteralStringBegin uri = end.children!.single
|
||||
as DirectParserASTContentLiteralStringBegin;
|
||||
(result ??= []).add(unescapeString(uri.token.lexeme, uri.token,
|
||||
const UnescapeErrorListenerDummy()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
extension PartExtension on DirectParserASTContentPartEnd {
|
||||
String getPartUriString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
bool foundOne = false;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentLiteralStringEnd) {
|
||||
DirectParserASTContentLiteralStringBegin uri =
|
||||
child.children!.single as DirectParserASTContentLiteralStringBegin;
|
||||
sb.write(unescapeString(
|
||||
uri.token.lexeme, uri.token, const UnescapeErrorListenerDummy()));
|
||||
foundOne = true;
|
||||
}
|
||||
}
|
||||
if (!foundOne) throw "Didn't find any";
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class UnescapeErrorListenerDummy implements UnescapeErrorListener {
|
||||
const UnescapeErrorListenerDummy();
|
||||
|
||||
@override
|
||||
void handleUnescapeError(
|
||||
Message message, covariant location, int offset, int length) {
|
||||
// Purposely doesn't do anything.
|
||||
}
|
||||
}
|
||||
|
||||
extension TopLevelFieldsExtension on DirectParserASTContentTopLevelFieldsEnd {
|
||||
List<DirectParserASTContentIdentifierHandle> getFieldIdentifiers() {
|
||||
int countLeft = count;
|
||||
List<DirectParserASTContentIdentifierHandle>? identifiers;
|
||||
for (int i = children!.length - 1; i >= 0; i--) {
|
||||
DirectParserASTContent child = children![i];
|
||||
if (child is DirectParserASTContentIdentifierHandle &&
|
||||
child.context == IdentifierContext.topLevelVariableDeclaration) {
|
||||
countLeft--;
|
||||
if (identifiers == null) {
|
||||
identifiers = new List<DirectParserASTContentIdentifierHandle>.filled(
|
||||
count, child);
|
||||
} else {
|
||||
identifiers[countLeft] = child;
|
||||
}
|
||||
if (countLeft == 0) break;
|
||||
}
|
||||
}
|
||||
if (countLeft != 0) throw "Didn't find the expected number of identifiers";
|
||||
return identifiers ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
extension ClassMethodExtension on DirectParserASTContentClassMethodEnd {
|
||||
DirectParserASTContentBlockFunctionBodyEnd? getBlockFunctionBody() {
|
||||
for (DirectParserASTContent child in children!) {
|
||||
|
@ -1065,6 +1304,80 @@ extension ClassMethodExtension on DirectParserASTContentClassMethodEnd {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String getNameIdentifier() {
|
||||
bool foundType = false;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentTypeHandle ||
|
||||
child is DirectParserASTContentNoTypeHandle ||
|
||||
child is DirectParserASTContentVoidKeywordHandle ||
|
||||
child is DirectParserASTContentFunctionTypeEnd) {
|
||||
foundType = true;
|
||||
}
|
||||
if (foundType && child is DirectParserASTContentIdentifierHandle) {
|
||||
return child.token.lexeme;
|
||||
} else if (foundType &&
|
||||
child is DirectParserASTContentOperatorNameHandle) {
|
||||
return child.token.lexeme;
|
||||
}
|
||||
}
|
||||
throw "No identifier found: $children";
|
||||
}
|
||||
}
|
||||
|
||||
extension MixinMethodExtension on DirectParserASTContentMixinMethodEnd {
|
||||
String getNameIdentifier() {
|
||||
bool foundType = false;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentTypeHandle ||
|
||||
child is DirectParserASTContentNoTypeHandle ||
|
||||
child is DirectParserASTContentVoidKeywordHandle) {
|
||||
foundType = true;
|
||||
}
|
||||
if (foundType && child is DirectParserASTContentIdentifierHandle) {
|
||||
return child.token.lexeme;
|
||||
} else if (foundType &&
|
||||
child is DirectParserASTContentOperatorNameHandle) {
|
||||
return child.token.lexeme;
|
||||
}
|
||||
}
|
||||
throw "No identifier found: $children";
|
||||
}
|
||||
}
|
||||
|
||||
extension ExtensionMethodExtension on DirectParserASTContentExtensionMethodEnd {
|
||||
String getNameIdentifier() {
|
||||
bool foundType = false;
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentTypeHandle ||
|
||||
child is DirectParserASTContentNoTypeHandle ||
|
||||
child is DirectParserASTContentVoidKeywordHandle) {
|
||||
foundType = true;
|
||||
}
|
||||
if (foundType && child is DirectParserASTContentIdentifierHandle) {
|
||||
return child.token.lexeme;
|
||||
} else if (foundType &&
|
||||
child is DirectParserASTContentOperatorNameHandle) {
|
||||
return child.token.lexeme;
|
||||
}
|
||||
}
|
||||
throw "No identifier found: $children";
|
||||
}
|
||||
}
|
||||
|
||||
extension ClassFactoryMethodExtension
|
||||
on DirectParserASTContentClassFactoryMethodEnd {
|
||||
List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
|
||||
List<DirectParserASTContentIdentifierHandle> result = [];
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentIdentifierHandle) {
|
||||
result.add(child);
|
||||
} else if (child is DirectParserASTContentFormalParametersEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
extension ClassConstructorExtension
|
||||
|
@ -1095,6 +1408,16 @@ extension ClassConstructorExtension
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<DirectParserASTContentIdentifierHandle> getIdentifiers() {
|
||||
List<DirectParserASTContentIdentifierHandle> result = [];
|
||||
for (DirectParserASTContent child in children!) {
|
||||
if (child is DirectParserASTContentIdentifierHandle) {
|
||||
result.add(child);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
extension FormalParametersExtension
|
||||
|
@ -1271,6 +1594,9 @@ class DirectParserASTListener extends AbstractDirectParserASTListener {
|
|||
throw "Unknown combination: begin$begin and end$end";
|
||||
}
|
||||
List<DirectParserASTContent> children = data.sublist(beginIndex);
|
||||
for (DirectParserASTContent child in children) {
|
||||
child.parent = entry;
|
||||
}
|
||||
data.length = beginIndex;
|
||||
data.add(entry..children = children);
|
||||
break;
|
||||
|
|
|
@ -26,6 +26,7 @@ abstract class DirectParserASTContent {
|
|||
final DirectParserASTType type;
|
||||
Map<String, Object?> get deprecatedArguments;
|
||||
List<DirectParserASTContent>? children;
|
||||
DirectParserASTContent? parent;
|
||||
|
||||
DirectParserASTContent(this.what, this.type);
|
||||
|
||||
|
|
964
pkg/front_end/lib/src/fasta/util/outline_extractor.dart
Normal file
964
pkg/front_end/lib/src/fasta/util/outline_extractor.dart
Normal file
|
@ -0,0 +1,964 @@
|
|||
// 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/direct_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 "direct_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 DirectParserASTContent 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 (DirectParserASTContentIdentifierHandle 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).
|
||||
DirectParserASTContent 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 (DirectParserASTContentIdentifierHandle identifier
|
||||
in identifierExtractor.identifiers) {
|
||||
DirectParserASTContent 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _TopLevelAndAstNode {
|
||||
final TopLevel topLevel;
|
||||
final AstNode entry;
|
||||
|
||||
_TopLevelAndAstNode(this.topLevel, this.entry);
|
||||
}
|
||||
|
||||
class _IdentifierExtractor {
|
||||
List<DirectParserASTContentIdentifierHandle> identifiers = [];
|
||||
|
||||
void extract(DirectParserASTContent ast) {
|
||||
if (ast is DirectParserASTContentIdentifierHandle) {
|
||||
identifiers.add(ast);
|
||||
}
|
||||
List<DirectParserASTContent>? children = ast.children;
|
||||
if (children != null) {
|
||||
for (DirectParserASTContent child in children) {
|
||||
extract(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ParserAstVisitor extends DirectParserASTContentVisitor {
|
||||
final Uri uri;
|
||||
final Uri? partOfUri;
|
||||
late Container currentContainer;
|
||||
final Map<DirectParserASTContent, AstNode> map = {};
|
||||
final int verbosityLevel;
|
||||
final List<Token> languageVersionsSeen;
|
||||
|
||||
_ParserAstVisitor(
|
||||
this.verbosityLevel,
|
||||
String sourceText,
|
||||
this.uri,
|
||||
this.partOfUri,
|
||||
DirectParserASTContent rootAst,
|
||||
this.languageVersionsSeen) {
|
||||
currentContainer = new TopLevel(sourceText, uri, rootAst, map);
|
||||
if (languageVersionsSeen.isNotEmpty) {
|
||||
// Use first one.
|
||||
Token languageVersion = languageVersionsSeen.first;
|
||||
DirectParserASTContent dummyNode =
|
||||
new DirectParserASTContentNoInitializersHandle(
|
||||
DirectParserASTType.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(DirectParserASTContentClassDeclarationEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
DirectParserASTContentTopLevelDeclarationEnd parent =
|
||||
node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
|
||||
DirectParserASTContentIdentifierHandle 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(DirectParserASTContentClassConstructorEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
assert(currentContainer is Class);
|
||||
List<DirectParserASTContentIdentifierHandle> 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(DirectParserASTContentClassFactoryMethodEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
assert(currentContainer is Class);
|
||||
List<DirectParserASTContentIdentifierHandle> 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(DirectParserASTContentClassFieldsEnd 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(DirectParserASTContentClassMethodEnd 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(DirectParserASTContentEnumEnd node, Token startInclusive,
|
||||
Token endInclusive) {
|
||||
List<DirectParserASTContentIdentifierHandle> 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(DirectParserASTContentExportEnd 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(DirectParserASTContentExtensionDeclarationEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
DirectParserASTContentExtensionDeclarationBegin begin =
|
||||
node.children!.first as DirectParserASTContentExtensionDeclarationBegin;
|
||||
DirectParserASTContentTopLevelDeclarationEnd parent =
|
||||
node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
|
||||
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(
|
||||
DirectParserASTContentExtensionConstructorEnd node,
|
||||
Token startInclusive,
|
||||
Token endInclusive) {
|
||||
// TODO: implement visitExtensionConstructor
|
||||
throw node;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExtensionFactoryMethod(
|
||||
DirectParserASTContentExtensionFactoryMethodEnd node,
|
||||
Token startInclusive,
|
||||
Token endInclusive) {
|
||||
// TODO: implement visitExtensionFactoryMethod
|
||||
throw node;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitExtensionFields(DirectParserASTContentExtensionFieldsEnd 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(DirectParserASTContentExtensionMethodEnd 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(DirectParserASTContentImportEnd node, Token startInclusive,
|
||||
Token? endInclusive) {
|
||||
DirectParserASTContentIdentifierHandle? 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(DirectParserASTContentLibraryNameEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
LibraryName name = new LibraryName(node, startInclusive, endInclusive);
|
||||
name.marked = Coloring.Marked;
|
||||
currentContainer.addChild(name, map);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMetadata(DirectParserASTContentMetadataEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
Metadata m = new Metadata(node, startInclusive, endInclusive);
|
||||
currentContainer.addChild(m, map);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitMixin(DirectParserASTContentMixinDeclarationEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
DirectParserASTContentTopLevelDeclarationEnd parent =
|
||||
node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
|
||||
DirectParserASTContentIdentifierHandle 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(DirectParserASTContentMixinFieldsEnd 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(DirectParserASTContentMixinMethodEnd 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(DirectParserASTContentNamedMixinApplicationEnd node,
|
||||
Token startInclusive, Token endInclusive) {
|
||||
DirectParserASTContentTopLevelDeclarationEnd parent =
|
||||
node.parent! as DirectParserASTContentTopLevelDeclarationEnd;
|
||||
DirectParserASTContentIdentifierHandle 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(DirectParserASTContentPartEnd 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(DirectParserASTContentPartOfEnd 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(DirectParserASTContentTopLevelFieldsEnd 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(DirectParserASTContentTopLevelMethodEnd 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(DirectParserASTContentTypedefEnd 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
class Foo {}
|
|
@ -0,0 +1 @@
|
|||
class Foo2 {}
|
|
@ -0,0 +1 @@
|
|||
class Foo {}
|
|
@ -0,0 +1 @@
|
|||
class Foo2 {}
|
|
@ -0,0 +1 @@
|
|||
class Foo {}
|
|
@ -0,0 +1 @@
|
|||
class Foo2 {}
|
|
@ -0,0 +1,8 @@
|
|||
import 'a.dart' if (dart.library.html) 'b.dart' if (dart.library.io) 'c.dart';
|
||||
export 'a2.dart'
|
||||
if (dart.library.html) 'b2.dart'
|
||||
if (dart.library.io) 'c2.dart';
|
||||
|
||||
Foo x() {
|
||||
return new Foo();
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import 'a.dart' if (dart.library.html) 'b.dart' if (dart.library.io) 'c.dart';
|
||||
export 'a2.dart' if (dart.library.html) 'b2.dart' if (dart.library.io) 'c2.dart';
|
||||
Foo x() {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///a2.dart:
|
||||
class Foo2 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///b2.dart:
|
||||
class Foo2 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///c2.dart:
|
||||
class Foo2 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///a.dart:
|
||||
class Foo {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///b.dart:
|
||||
class Foo {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///c.dart:
|
||||
class Foo {}
|
|
@ -0,0 +1,3 @@
|
|||
import "test8.dart";
|
||||
|
||||
ClassFromImportsExportsExport? zyx____xyz;
|
|
@ -0,0 +1,22 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "test8.dart";
|
||||
ClassFromImportsExportsExport? zyx____xyz;
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test8.dart:
|
||||
export "test9.dart";
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test9.dart:
|
||||
export "test10.dart";
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test10.dart:
|
||||
export "dart:async";
|
||||
class ClassFromImportsExportsExport {}
|
|
@ -0,0 +1,5 @@
|
|||
export "dart:async";
|
||||
|
||||
void test10Method() {}
|
||||
|
||||
class ClassFromImportsExportsExport {}
|
|
@ -0,0 +1,3 @@
|
|||
export "test9.dart";
|
||||
|
||||
void test8Method() {}
|
|
@ -0,0 +1,3 @@
|
|||
export "test10.dart";
|
||||
|
||||
void test9Method() {}
|
|
@ -0,0 +1 @@
|
|||
export "test6.dart";
|
|
@ -0,0 +1,15 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
export "test6.dart";
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test6.dart:
|
||||
export "test7.dart";
|
||||
void test6() {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test7.dart:
|
||||
void test7() {}
|
|
@ -0,0 +1,3 @@
|
|||
export "test7.dart";
|
||||
|
||||
void test6() {}
|
|
@ -0,0 +1 @@
|
|||
void test7() {}
|
|
@ -0,0 +1 @@
|
|||
class Foo {}
|
|
@ -0,0 +1,3 @@
|
|||
import "foo.dart";
|
||||
|
||||
class X extends Foo {}
|
|
@ -0,0 +1,9 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "foo.dart";
|
||||
class X extends Foo {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///foo.dart:
|
||||
class Foo {}
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:foo/test11.dart';
|
||||
|
||||
class Abc {
|
||||
Abc() {}
|
||||
factory Abc.a() {
|
||||
return Abc2();
|
||||
}
|
||||
// Abc3 currently gets in --- it doesn't have to.
|
||||
factory Abc.b() => Abc3();
|
||||
var v1 = Abc4();
|
||||
var v2 = new Abc5();
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import 'package:foo/test11.dart';
|
||||
class Abc {
|
||||
Abc() {}
|
||||
factory Abc.a() {}
|
||||
factory Abc.b() => Abc3();
|
||||
var v1 = Abc4();
|
||||
var v2 = new Abc5();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test11.dart:
|
||||
import 'package:foo/main.dart';
|
||||
class Abc3 extends Abc {
|
||||
Abc3() {}
|
||||
}
|
||||
class Abc4 extends Abc {
|
||||
Abc4() {}
|
||||
}
|
||||
class Abc5 extends Abc {
|
||||
Abc5() {}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:foo/main.dart';
|
||||
|
||||
class Abc2 extends Abc {
|
||||
Abc2() {}
|
||||
}
|
||||
|
||||
class Abc3 extends Abc {
|
||||
Abc3() {}
|
||||
}
|
||||
|
||||
class Abc4 extends Abc {
|
||||
Abc4() {}
|
||||
}
|
||||
|
||||
class Abc5 extends Abc {
|
||||
Abc5() {}
|
||||
}
|
||||
|
||||
class Abc6 extends Abc {
|
||||
Abc6() {}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import "c.dart";
|
||||
|
||||
class B {
|
||||
C c() {
|
||||
return new C();
|
||||
}
|
||||
}
|
||||
|
||||
class BPrime {}
|
|
@ -0,0 +1,9 @@
|
|||
import "d.dart";
|
||||
|
||||
class C {
|
||||
D d() {
|
||||
return new D();
|
||||
}
|
||||
}
|
||||
|
||||
class CPrime {}
|
|
@ -0,0 +1,7 @@
|
|||
class D {
|
||||
String d() {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
|
||||
class DPrime {}
|
|
@ -0,0 +1,9 @@
|
|||
import "b.dart";
|
||||
|
||||
var x = A.b().c().d;
|
||||
|
||||
class A {
|
||||
static B b() {
|
||||
return new B();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "b.dart";
|
||||
var x = A.b().c().d;
|
||||
class A {
|
||||
static B b() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///b.dart:
|
||||
import "c.dart";
|
||||
class B {
|
||||
C c() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///c.dart:
|
||||
import "d.dart";
|
||||
class C {
|
||||
D d() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///d.dart:
|
||||
class D {
|
||||
String d() {}
|
||||
}
|
11
pkg/front_end/outline_extraction_testcases/fields/bar.dart
Normal file
11
pkg/front_end/outline_extraction_testcases/fields/bar.dart
Normal file
|
@ -0,0 +1,11 @@
|
|||
class Bar {}
|
||||
|
||||
class Bar2 {}
|
||||
|
||||
class Bar3 {}
|
||||
|
||||
class Bar4 {}
|
||||
|
||||
class Foo3 {}
|
||||
|
||||
class Foo4 {}
|
13
pkg/front_end/outline_extraction_testcases/fields/foo.dart
Normal file
13
pkg/front_end/outline_extraction_testcases/fields/foo.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
export "bar.dart";
|
||||
|
||||
class Baz {}
|
||||
|
||||
class Baz2 {}
|
||||
|
||||
class Baz3 {}
|
||||
|
||||
class Baz4 {}
|
||||
|
||||
class Foo {}
|
||||
|
||||
class Foo2 {}
|
19
pkg/front_end/outline_extraction_testcases/fields/main.dart
Normal file
19
pkg/front_end/outline_extraction_testcases/fields/main.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
import "foo.dart";
|
||||
|
||||
class A {
|
||||
Bar field = new Bar();
|
||||
Bar2 field2 = new Bar2();
|
||||
var field3 = new Bar3(), field4 = new Bar4();
|
||||
}
|
||||
|
||||
mixin A2 {
|
||||
Baz field = new Baz();
|
||||
Baz2 field2 = new Baz2();
|
||||
var field3 = new Baz3(), field4 = new Baz4();
|
||||
}
|
||||
|
||||
extension A3 on Object {
|
||||
static Foo field = new Foo();
|
||||
static Foo2 field2 = new Foo2();
|
||||
static var field3 = new Foo3(), field4 = new Foo4();
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "foo.dart";
|
||||
class A {
|
||||
Bar field = new Bar();
|
||||
Bar2 field2 = new Bar2();
|
||||
var field3 = new Bar3(), field4 = new Bar4();
|
||||
}
|
||||
mixin A2 {
|
||||
Baz field = new Baz();
|
||||
Baz2 field2 = new Baz2();
|
||||
var field3 = new Baz3(), field4 = new Baz4();
|
||||
}
|
||||
extension A3 on Object {
|
||||
static Foo field = new Foo();
|
||||
static Foo2 field2 = new Foo2();
|
||||
static var field3 = new Foo3(), field4 = new Foo4();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///foo.dart:
|
||||
export "bar.dart";
|
||||
class Baz {}
|
||||
class Baz2 {}
|
||||
class Baz3 {}
|
||||
class Baz4 {}
|
||||
class Foo {}
|
||||
class Foo2 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///bar.dart:
|
||||
class Bar {}
|
||||
class Bar2 {}
|
||||
class Bar3 {}
|
||||
class Bar4 {}
|
||||
class Foo3 {}
|
||||
class Foo4 {}
|
|
@ -0,0 +1,6 @@
|
|||
import "test16.dart" as test16;
|
||||
|
||||
class Test16ClassHelper {
|
||||
// the naming matching is what makes it annoying!
|
||||
final test16toplevel = test16.test16toplevel("hello");
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "test16.dart" as test16;
|
||||
class Test16ClassHelper {
|
||||
final test16toplevel = test16.test16toplevel("hello");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test16.dart:
|
||||
String test16toplevel(String s) {}
|
|
@ -0,0 +1,3 @@
|
|||
String test16toplevel(String s) {
|
||||
return s * 2;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
int bar() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
class Baz {}
|
|
@ -0,0 +1,5 @@
|
|||
String bar() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
class Baz {}
|
|
@ -0,0 +1,7 @@
|
|||
import 'foo.dart' as foo;
|
||||
// This import isn't used --- foo.bar below explicitly wants bar from foo.
|
||||
import 'bar.dart';
|
||||
|
||||
var x = foo.bar();
|
||||
|
||||
foo.Baz? baz;
|
|
@ -0,0 +1,17 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import 'foo.dart' as foo;
|
||||
import 'bar.dart';
|
||||
var x = foo.bar();
|
||||
foo.Baz? baz;
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///foo.dart:
|
||||
String bar() {}
|
||||
class Baz {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///bar.dart:
|
|
@ -0,0 +1,3 @@
|
|||
String foo() {
|
||||
return "foo";
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
String bar() {
|
||||
return "bar";
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import 'a.dart' as x;
|
||||
import 'b.dart' as x;
|
||||
|
||||
var foo = x.foo();
|
||||
var bar = x.bar();
|
|
@ -0,0 +1,17 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import 'a.dart' as x;
|
||||
import 'b.dart' as x;
|
||||
var foo = x.foo();
|
||||
var bar = x.bar();
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///a.dart:
|
||||
String foo() {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///b.dart:
|
||||
String bar() {}
|
|
@ -0,0 +1,28 @@
|
|||
import "main.dart" as self;
|
||||
import "test3.dart";
|
||||
|
||||
class Foo<E> extends Bar<int> with Qux1<int> implements Baz<Bar<int>> {
|
||||
Foo<E>? parent;
|
||||
|
||||
Foo() {}
|
||||
Foo.bar() {}
|
||||
F? fooMethod1<F>() {
|
||||
print(foo);
|
||||
print(F);
|
||||
print(x.A);
|
||||
}
|
||||
|
||||
E? fooMethod2() {
|
||||
print(E);
|
||||
print(x.A);
|
||||
}
|
||||
|
||||
self.Foo? fooMethod3() {
|
||||
print(E);
|
||||
print(x.A);
|
||||
}
|
||||
|
||||
x fooMethod4() {
|
||||
return x.A;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "main.dart" as self;
|
||||
import "test3.dart";
|
||||
class Foo<E> extends Bar<int> with Qux1<int> implements Baz<Bar<int>> {
|
||||
Foo<E>? parent;
|
||||
Foo() {}
|
||||
Foo.bar() {}
|
||||
F? fooMethod1<F>() {}
|
||||
E? fooMethod2() {}
|
||||
self.Foo? fooMethod3() {}
|
||||
x fooMethod4() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test3.dart:
|
||||
class Bar<E> {}
|
||||
class Baz<E> {}
|
||||
class Qux1<E> {
|
||||
Qux1AndAHalf? qux1AndAHalf() {}
|
||||
}
|
||||
class Qux1AndAHalf<E> {}
|
||||
enum x { A, B, C }
|
|
@ -0,0 +1,25 @@
|
|||
import "test4.dart";
|
||||
|
||||
class Bar<E> {}
|
||||
|
||||
class Baz<E> {}
|
||||
|
||||
class Qux1<E> {
|
||||
Qux1AndAHalf? qux1AndAHalf() {
|
||||
// nothing...
|
||||
}
|
||||
}
|
||||
|
||||
class Qux1AndAHalf<E> {}
|
||||
|
||||
class Qux2<E> {
|
||||
Qux3? foo() {}
|
||||
}
|
||||
|
||||
enum x { A, B, C }
|
||||
|
||||
int foo() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
int foo2 = foo() * 2, foo3 = foo() * 3;
|
|
@ -0,0 +1,8 @@
|
|||
import "test5.dart";
|
||||
export "test5.dart";
|
||||
|
||||
class Qux3<E> {
|
||||
Qux4? foo() {}
|
||||
}
|
||||
|
||||
class Qux4<E> {}
|
|
@ -0,0 +1,5 @@
|
|||
class Qux3x<E> {
|
||||
Qux4x? foo() {}
|
||||
}
|
||||
|
||||
class Qux4x<E> {}
|
|
@ -0,0 +1,5 @@
|
|||
// @dart = 2.12
|
||||
|
||||
class Bar {}
|
||||
|
||||
class Baz {}
|
|
@ -0,0 +1,3 @@
|
|||
import "bar.dart";
|
||||
|
||||
void foo(Bar bar) {}
|
|
@ -0,0 +1,10 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "bar.dart";
|
||||
void foo(Bar bar) {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///bar.dart:
|
||||
// @dart = 2.12
|
||||
class Bar {}
|
|
@ -0,0 +1 @@
|
|||
class AUnused {}
|
|
@ -0,0 +1,9 @@
|
|||
const AbcX = const _AbcX();
|
||||
|
||||
class _AbcX {
|
||||
const _AbcX();
|
||||
}
|
||||
|
||||
class AbcX2 {
|
||||
const AbcX2();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import "a.dart";
|
||||
import "b.dart";
|
||||
|
||||
@AbcX
|
||||
void foo() {}
|
|
@ -0,0 +1,19 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "a.dart";
|
||||
import "b.dart";
|
||||
@AbcX
|
||||
void foo() {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///a.dart:
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///b.dart:
|
||||
const AbcX = const _AbcX();
|
||||
class _AbcX {
|
||||
const _AbcX();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import "test15.dart" as $test15;
|
||||
import "nottest15.dart";
|
||||
|
||||
@$test15.Test15()
|
||||
void test15thing() {}
|
|
@ -0,0 +1,18 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "test15.dart" as $test15;
|
||||
import "nottest15.dart";
|
||||
@$test15.Test15()
|
||||
void test15thing() {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test15.dart:
|
||||
class Test15 {
|
||||
const Test15();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///nottest15.dart:
|
|
@ -0,0 +1,7 @@
|
|||
class Test15 {
|
||||
const Test15();
|
||||
}
|
||||
|
||||
class Test16 {
|
||||
const Test16();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
class Test15 {
|
||||
const Test15();
|
||||
}
|
||||
|
||||
class Test16 {
|
||||
const Test16();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
library foo.a;
|
||||
|
||||
export 'b.dart';
|
||||
export 'c.dart';
|
||||
export 'd.dart';
|
|
@ -0,0 +1 @@
|
|||
class B {}
|
|
@ -0,0 +1,7 @@
|
|||
class C<T> {
|
||||
const C.b();
|
||||
}
|
||||
|
||||
class C2<T> {
|
||||
const C2.b();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
class D {}
|
|
@ -0,0 +1,6 @@
|
|||
import 'package:foo/a.dart' as foo;
|
||||
|
||||
class A {
|
||||
var c1 = foo.C<A>.b();
|
||||
var c2 = new foo.C2<A>.b();
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import 'package:foo/a.dart' as foo;
|
||||
class A {
|
||||
var c1 = foo.C<A>.b();
|
||||
var c2 = new foo.C2<A>.b();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///a.dart:
|
||||
library foo.a;
|
||||
export 'b.dart';
|
||||
export 'c.dart';
|
||||
export 'd.dart';
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///b.dart:
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///c.dart:
|
||||
class C<T> {
|
||||
const C.b();
|
||||
}
|
||||
class C2<T> {
|
||||
const C2.b();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///d.dart:
|
|
@ -0,0 +1,3 @@
|
|||
export "baz.dart";
|
||||
|
||||
class Bar {}
|
|
@ -0,0 +1 @@
|
|||
class Baz {}
|
|
@ -0,0 +1,3 @@
|
|||
import "bar.dart";
|
||||
|
||||
class Foo = Object with Bar implements Baz;
|
|
@ -0,0 +1,16 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "bar.dart";
|
||||
class Foo = Object with Bar implements Baz;
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///bar.dart:
|
||||
export "baz.dart";
|
||||
class Bar {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///baz.dart:
|
||||
class Baz {}
|
|
@ -0,0 +1,4 @@
|
|||
import "test12.dart";
|
||||
|
||||
void test12part1usage(Test12Part1 x) {}
|
||||
void secondtest12part1usage(SecondTest12 x) {}
|
|
@ -0,0 +1,47 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "test12.dart";
|
||||
void test12part1usage(Test12Part1 x) {}
|
||||
void secondtest12part1usage(SecondTest12 x) {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test12.dart:
|
||||
import "test13.dart";
|
||||
import "test14.dart";
|
||||
part 'test12_part1.dart';
|
||||
part 'test12_part2.dart';
|
||||
class Test12 {}
|
||||
class SecondTest12 {
|
||||
void foo(SecondTest12Part1 x) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test12_part1.dart:
|
||||
part of "test12.dart";
|
||||
class Test12Part1 {
|
||||
void foo(Test12 x) {}
|
||||
void bar(Test12Part2 x) {}
|
||||
void baz(Test13 x) {}
|
||||
}
|
||||
class SecondTest12Part1 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test12_part2.dart:
|
||||
part of "test12.dart";
|
||||
class Test12Part2 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test13.dart:
|
||||
class Test13 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test14.dart:
|
|
@ -0,0 +1,11 @@
|
|||
import "test13.dart";
|
||||
import "test14.dart";
|
||||
|
||||
part 'test12_part1.dart';
|
||||
part 'test12_part2.dart';
|
||||
|
||||
class Test12 {}
|
||||
|
||||
class SecondTest12 {
|
||||
void foo(SecondTest12Part1 x) {}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
part of "test12.dart";
|
||||
|
||||
class Test12Part1 {
|
||||
void foo(Test12 x) {}
|
||||
void bar(Test12Part2 x) {}
|
||||
void baz(Test13 x) {}
|
||||
}
|
||||
|
||||
class SecondTest12Part1 {}
|
|
@ -0,0 +1,3 @@
|
|||
part of "test12.dart";
|
||||
|
||||
class Test12Part2 {}
|
|
@ -0,0 +1,3 @@
|
|||
import "test13andahalf.dart";
|
||||
|
||||
class Test13 {}
|
|
@ -0,0 +1 @@
|
|||
class Test13AndAHalf {}
|
|
@ -0,0 +1 @@
|
|||
class Test14 {}
|
|
@ -0,0 +1,3 @@
|
|||
import 'test3.dart';
|
||||
|
||||
var x = test3partfoo();
|
|
@ -0,0 +1,17 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import 'test3.dart';
|
||||
var x = test3partfoo();
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test3.dart:
|
||||
library test3;
|
||||
part "test3_part.dart";
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///test3_part.dart:
|
||||
part of test3;
|
||||
void test3partfoo() {}
|
|
@ -0,0 +1,5 @@
|
|||
library test3;
|
||||
|
||||
part "test3_part.dart";
|
||||
|
||||
class Foo {}
|
|
@ -0,0 +1,5 @@
|
|||
part of test3;
|
||||
|
||||
void test3partfoo() {}
|
||||
|
||||
class Bar {}
|
|
@ -0,0 +1 @@
|
|||
class Foo {}
|
|
@ -0,0 +1,25 @@
|
|||
import "package:f"
|
||||
"oobar"
|
||||
"/"
|
||||
"foo"
|
||||
".dart";
|
||||
|
||||
export "package:f"
|
||||
"oobar"
|
||||
"/"
|
||||
"foo"
|
||||
".dart";
|
||||
|
||||
part "package:f"
|
||||
"oobar"
|
||||
"/"
|
||||
"part"
|
||||
".dart";
|
||||
|
||||
Foo giveFoo() {
|
||||
return new Foo();
|
||||
}
|
||||
|
||||
Bar giveBar() {
|
||||
return new Bar();
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "package:f" "oobar" "/" "foo" ".dart";
|
||||
export "package:f" "oobar" "/" "foo" ".dart";
|
||||
part "package:f" "oobar" "/" "part" ".dart";
|
||||
Foo giveFoo() {}
|
||||
Bar giveBar() {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///foo.dart:
|
||||
class Foo {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///part.dart:
|
||||
part of "package:f" "oobar" "/" "main" ".dart";
|
||||
class Bar {}
|
|
@ -0,0 +1,7 @@
|
|||
part of "package:f"
|
||||
"oobar"
|
||||
"/"
|
||||
"main"
|
||||
".dart";
|
||||
|
||||
class Bar {}
|
|
@ -0,0 +1,5 @@
|
|||
class Bar {}
|
||||
|
||||
class Bar2 {}
|
||||
|
||||
class Bar3 {}
|
|
@ -0,0 +1,7 @@
|
|||
export "bar.dart";
|
||||
|
||||
class Baz {}
|
||||
|
||||
class Baz2 {}
|
||||
|
||||
class Baz3 {}
|
|
@ -0,0 +1,16 @@
|
|||
import "foo.dart";
|
||||
|
||||
class A<T extends Bar, U extends Baz> {
|
||||
T? aMethod1() {}
|
||||
U? aMethod2() {}
|
||||
}
|
||||
|
||||
mixin A2<T extends Bar2, U extends Baz2> {
|
||||
T? aMethod1() {}
|
||||
U? aMethod2() {}
|
||||
}
|
||||
|
||||
extension A3<T extends Bar3, U extends Baz3> on Object {
|
||||
T? aMethod1() {}
|
||||
U? aMethod2() {}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "foo.dart";
|
||||
class A<T extends Bar, U extends Baz> {
|
||||
T? aMethod1() {}
|
||||
U? aMethod2() {}
|
||||
}
|
||||
mixin A2<T extends Bar2, U extends Baz2> {
|
||||
T? aMethod1() {}
|
||||
U? aMethod2() {}
|
||||
}
|
||||
extension A3<T extends Bar3, U extends Baz3> on Object {
|
||||
T? aMethod1() {}
|
||||
U? aMethod2() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///foo.dart:
|
||||
export "bar.dart";
|
||||
class Baz {}
|
||||
class Baz2 {}
|
||||
class Baz3 {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///bar.dart:
|
||||
class Bar {}
|
||||
class Bar2 {}
|
||||
class Bar3 {}
|
|
@ -0,0 +1,5 @@
|
|||
class Foo<E> {}
|
||||
|
||||
extension HiExtension<T extends Foo> on T {
|
||||
void sayHi() {}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
class Foo<E> {}
|
||||
extension HiExtension<T extends Foo> on T {
|
||||
void sayHi() {}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
class Foo {}
|
|
@ -0,0 +1,6 @@
|
|||
import "foo.dart";
|
||||
|
||||
void main() {
|
||||
Foo foo = new Foo();
|
||||
print(foo);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
org-dartlang-testcase:///main.dart:
|
||||
import "foo.dart";
|
||||
void main() {}
|
||||
|
||||
|
||||
|
||||
|
||||
org-dartlang-testcase:///foo.dart:
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue