mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 21:20:36 +00:00
Change MirrorSystem.libraries to Map<Uri, String>
BUG= Review URL: https://codereview.chromium.org//13797002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21812 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
e4f262142e
commit
f4f3958af1
|
@ -80,10 +80,9 @@ void parseCommandLine(List<OptionHandler> handlers, List<String> argv) {
|
|||
|
||||
void compile(List<String> argv) {
|
||||
bool isWindows = (Platform.operatingSystem == 'windows');
|
||||
Uri cwd = getCurrentDirectory();
|
||||
Uri libraryRoot = cwd;
|
||||
Uri out = cwd.resolve('out.js');
|
||||
Uri sourceMapOut = cwd.resolve('out.js.map');
|
||||
Uri libraryRoot = currentDirectory;
|
||||
Uri out = currentDirectory.resolve('out.js');
|
||||
Uri sourceMapOut = currentDirectory.resolve('out.js.map');
|
||||
Uri packageRoot = null;
|
||||
List<String> options = new List<String>();
|
||||
bool explicitOut = false;
|
||||
|
@ -102,16 +101,16 @@ void compile(List<String> argv) {
|
|||
}
|
||||
|
||||
setLibraryRoot(String argument) {
|
||||
libraryRoot = cwd.resolve(extractPath(argument));
|
||||
libraryRoot = currentDirectory.resolve(extractPath(argument));
|
||||
}
|
||||
|
||||
setPackageRoot(String argument) {
|
||||
packageRoot = cwd.resolve(extractPath(argument));
|
||||
packageRoot = currentDirectory.resolve(extractPath(argument));
|
||||
}
|
||||
|
||||
setOutput(String argument) {
|
||||
explicitOut = true;
|
||||
out = cwd.resolve(nativeToUriPath(extractParameter(argument)));
|
||||
out = currentDirectory.resolve(nativeToUriPath(extractParameter(argument)));
|
||||
sourceMapOut = Uri.parse('$out.map');
|
||||
}
|
||||
|
||||
|
@ -119,8 +118,8 @@ void compile(List<String> argv) {
|
|||
if (argument == '--output-type=dart') {
|
||||
outputLanguage = OUTPUT_LANGUAGE_DART;
|
||||
if (!explicitOut) {
|
||||
out = cwd.resolve('out.dart');
|
||||
sourceMapOut = cwd.resolve('out.dart.map');
|
||||
out = currentDirectory.resolve('out.dart');
|
||||
sourceMapOut = currentDirectory.resolve('out.dart.map');
|
||||
}
|
||||
}
|
||||
passThrough(argument);
|
||||
|
@ -254,7 +253,7 @@ void compile(List<String> argv) {
|
|||
diagnosticHandler.diagnosticHandler(uri, begin, end, message, kind);
|
||||
}
|
||||
|
||||
Uri uri = cwd.resolve(arguments[0]);
|
||||
Uri uri = currentDirectory.resolve(arguments[0]);
|
||||
if (packageRoot == null) {
|
||||
packageRoot = uri.resolve('./packages/');
|
||||
}
|
||||
|
@ -273,10 +272,10 @@ void compile(List<String> argv) {
|
|||
diagnosticHandler.info(
|
||||
'compiled ${inputProvider.dartCharactersRead} characters Dart '
|
||||
'-> $charactersWritten characters $outputLanguage '
|
||||
'in ${relativize(cwd, out, isWindows)}');
|
||||
'in ${relativize(currentDirectory, out, isWindows)}');
|
||||
if (!explicitOut) {
|
||||
String input = uriPathToNative(arguments[0]);
|
||||
String output = relativize(cwd, out, isWindows);
|
||||
String output = relativize(currentDirectory, out, isWindows);
|
||||
print('Dart file $input compiled to $outputLanguage: $output');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,8 @@ String uriPathToNative(String path) {
|
|||
return new Path(path).toNativePath();
|
||||
}
|
||||
|
||||
Uri getCurrentDirectory() {
|
||||
final String dir = nativeToUriPath(new File('.').fullPathSync());
|
||||
return new Uri.fromComponents(scheme: 'file', path: appendSlash(dir));
|
||||
}
|
||||
final Uri currentDirectory = new Uri.fromComponents(
|
||||
scheme: 'file',
|
||||
path: appendSlash(nativeToUriPath(new File('.').fullPathSync())));
|
||||
|
||||
String appendSlash(String path) => path.endsWith('/') ? path : '$path/';
|
||||
|
|
|
@ -209,17 +209,16 @@ Future<String> compile(Path script,
|
|||
{Path packageRoot,
|
||||
List<String> options: const <String>[],
|
||||
api.DiagnosticHandler diagnosticHandler}) {
|
||||
Uri cwd = getCurrentDirectory();
|
||||
SourceFileProvider provider = new SourceFileProvider();
|
||||
if (diagnosticHandler == null) {
|
||||
diagnosticHandler =
|
||||
new FormattingDiagnosticHandler(provider).diagnosticHandler;
|
||||
}
|
||||
Uri scriptUri = cwd.resolve(script.toString());
|
||||
Uri libraryUri = cwd.resolve(appendSlash('$libraryRoot'));
|
||||
Uri scriptUri = currentDirectory.resolve(script.toString());
|
||||
Uri libraryUri = currentDirectory.resolve(appendSlash('$libraryRoot'));
|
||||
Uri packageUri = null;
|
||||
if (packageRoot != null) {
|
||||
packageUri = cwd.resolve(appendSlash('$packageRoot'));
|
||||
packageUri = currentDirectory.resolve(appendSlash('$packageRoot'));
|
||||
}
|
||||
return api.compile(scriptUri, libraryUri, packageUri,
|
||||
provider.readStringFromUri, diagnosticHandler, options);
|
||||
|
@ -282,20 +281,19 @@ Future<MirrorSystem> analyze(List<Path> libraries,
|
|||
{Path packageRoot,
|
||||
List<String> options: const <String>[],
|
||||
api.DiagnosticHandler diagnosticHandler}) {
|
||||
Uri cwd = getCurrentDirectory();
|
||||
SourceFileProvider provider = new SourceFileProvider();
|
||||
if (diagnosticHandler == null) {
|
||||
diagnosticHandler =
|
||||
new FormattingDiagnosticHandler(provider).diagnosticHandler;
|
||||
}
|
||||
Uri libraryUri = cwd.resolve(appendSlash('$libraryRoot'));
|
||||
Uri libraryUri = currentDirectory.resolve(appendSlash('$libraryRoot'));
|
||||
Uri packageUri = null;
|
||||
if (packageRoot != null) {
|
||||
packageUri = cwd.resolve(appendSlash('$packageRoot'));
|
||||
packageUri = currentDirectory.resolve(appendSlash('$packageRoot'));
|
||||
}
|
||||
List<Uri> librariesUri = <Uri>[];
|
||||
for (Path library in libraries) {
|
||||
librariesUri.add(cwd.resolve(library.toString()));
|
||||
librariesUri.add(currentDirectory.resolve(library.toString()));
|
||||
}
|
||||
return analyzeUri(librariesUri, libraryUri, packageUri,
|
||||
provider.readStringFromUri, diagnosticHandler, options);
|
||||
|
@ -465,9 +463,9 @@ abstract class Dart2JsMemberMirror extends Dart2JsElementMirror
|
|||
// Mirror system implementation.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Dart2JsMirrorSystem implements MirrorSystem {
|
||||
class Dart2JsMirrorSystem extends MirrorSystem {
|
||||
final Compiler compiler;
|
||||
Map<String, Dart2JsLibraryMirror> _libraries;
|
||||
Map<Uri, Dart2JsLibraryMirror> _libraries;
|
||||
Map<LibraryElement, Dart2JsLibraryMirror> _libraryMap;
|
||||
|
||||
Dart2JsMirrorSystem(this.compiler)
|
||||
|
@ -475,18 +473,18 @@ class Dart2JsMirrorSystem implements MirrorSystem {
|
|||
|
||||
void _ensureLibraries() {
|
||||
if (_libraries == null) {
|
||||
_libraries = <String, Dart2JsLibraryMirror>{};
|
||||
_libraries = new Map<Uri, Dart2JsLibraryMirror>();
|
||||
compiler.libraries.forEach((_, LibraryElement v) {
|
||||
var mirror = new Dart2JsLibraryMirror(mirrors, v);
|
||||
_libraries[mirror.simpleName] = mirror;
|
||||
_libraries[mirror.uri] = mirror;
|
||||
_libraryMap[v] = mirror;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, LibraryMirror> get libraries {
|
||||
Map<Uri, LibraryMirror> get libraries {
|
||||
_ensureLibraries();
|
||||
return new ImmutableMapWrapper<String, LibraryMirror>(_libraries);
|
||||
return new ImmutableMapWrapper<Uri, LibraryMirror>(_libraries);
|
||||
}
|
||||
|
||||
Dart2JsLibraryMirror _getLibrary(LibraryElement element) =>
|
||||
|
|
|
@ -17,8 +17,16 @@ abstract class MirrorSystem {
|
|||
/**
|
||||
* Returns an unmodifiable map of all libraries in this mirror system.
|
||||
*/
|
||||
// TODO(johnniwinther): Change to Map<Uri, LibraryMirror>.
|
||||
Map<String, LibraryMirror> get libraries;
|
||||
Map<Uri, LibraryMirror> get libraries;
|
||||
|
||||
/**
|
||||
* Returns an iterable of all libraries in the mirror system whose library
|
||||
* name is [libraryName].
|
||||
*/
|
||||
Iterable<LibraryMirror> findLibrary(String libraryName) {
|
||||
return libraries.values.where(
|
||||
(library) => library.simpleName == libraryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* A mirror on the [:dynamic:] type.
|
||||
|
|
|
@ -27,7 +27,7 @@ String readAll(String filename) {
|
|||
|
||||
class SourceFileProvider {
|
||||
bool isWindows = (Platform.operatingSystem == 'windows');
|
||||
Uri cwd = getCurrentDirectory();
|
||||
Uri cwd = currentDirectory;
|
||||
Map<String, SourceFile> sourceFiles = <String, SourceFile>{};
|
||||
int dartCharactersRead = 0;
|
||||
|
||||
|
@ -43,8 +43,8 @@ class SourceFileProvider {
|
|||
'(${ex.osError}).';
|
||||
}
|
||||
dartCharactersRead += source.length;
|
||||
sourceFiles[resourceUri.toString()] =
|
||||
new SourceFile(relativize(cwd, resourceUri, isWindows), source);
|
||||
sourceFiles[resourceUri.toString()] = new SourceFile(
|
||||
relativize(cwd, resourceUri, isWindows), source);
|
||||
return new Future.value(source);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,8 +107,7 @@ class CollectingDiagnosticHandler extends FormattingDiagnosticHandler {
|
|||
}
|
||||
|
||||
void main() {
|
||||
Uri currentWorkingDirectory = getCurrentDirectory();
|
||||
var libraryRoot = currentWorkingDirectory.resolve('sdk/');
|
||||
var libraryRoot = currentDirectory.resolve('sdk/');
|
||||
var uriList = new List<Uri>();
|
||||
LIBRARIES.forEach((String name, LibraryInfo info) {
|
||||
if (info.documented) {
|
||||
|
|
|
@ -9,8 +9,7 @@ import '../../../sdk/lib/_internal/compiler/compiler.dart'
|
|||
show Diagnostic;
|
||||
|
||||
main() {
|
||||
Uri cwd = getCurrentDirectory();
|
||||
Uri script = cwd.resolve(nativeToUriPath(new Options().script));
|
||||
Uri script = currentDirectory.resolve(nativeToUriPath(new Options().script));
|
||||
Uri libraryRoot = script.resolve('../../../sdk/');
|
||||
Uri packageRoot = script.resolve('./packages/');
|
||||
|
||||
|
|
|
@ -13,8 +13,7 @@ import '../../../sdk/lib/_internal/compiler/implementation/dart2jslib.dart'
|
|||
as dart2js;
|
||||
|
||||
void main() {
|
||||
Uri cwd = getCurrentDirectory();
|
||||
Uri script = cwd.resolve(nativeToUriPath(new Options().script));
|
||||
Uri script = currentDirectory.resolve(nativeToUriPath(new Options().script));
|
||||
Uri libraryRoot = script.resolve('../../../sdk/');
|
||||
Uri packageRoot = script.resolve('./packages/');
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export '../../../sdk/lib/_internal/compiler/implementation/apiimpl.dart'
|
|||
show Compiler;
|
||||
|
||||
export '../../../sdk/lib/_internal/compiler/implementation/filenames.dart'
|
||||
show getCurrentDirectory, nativeToUriPath;
|
||||
show currentDirectory, nativeToUriPath;
|
||||
|
||||
import '../../../sdk/lib/_internal/compiler/implementation/source_file.dart'
|
||||
show SourceFile;
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:expect/expect.dart';
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:uri';
|
||||
import 'package:expect/expect.dart';
|
||||
import '../../../sdk/lib/_internal/compiler/implementation/filenames.dart';
|
||||
import '../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart';
|
||||
import '../../../sdk/lib/_internal/compiler/implementation/mirrors/dart2js_mirror.dart';
|
||||
|
@ -13,15 +14,15 @@ import '../../../sdk/lib/_internal/compiler/implementation/source_file_provider.
|
|||
import 'mock_compiler.dart';
|
||||
|
||||
const String SOURCE = 'source';
|
||||
const Uri SOURCE_URI = const Uri.fromComponents(scheme: SOURCE, path: SOURCE);
|
||||
|
||||
MirrorSystem createMirrorSystem(String source) {
|
||||
Uri sourceUri = new Uri.fromComponents(scheme: SOURCE, path: SOURCE);
|
||||
MockCompiler compiler = new MockCompiler(
|
||||
analyzeOnly: true,
|
||||
analyzeAll: true,
|
||||
preserveComments: true);
|
||||
compiler.registerSource(sourceUri, source);
|
||||
compiler.librariesToAnalyzeWhenRun = <Uri>[sourceUri];
|
||||
compiler.registerSource(SOURCE_URI, source);
|
||||
compiler.librariesToAnalyzeWhenRun = <Uri>[SOURCE_URI];
|
||||
compiler.runCompiler(null);
|
||||
return new Dart2JsMirrorSystem(compiler);
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ void validateDeclarationComment(String code,
|
|||
bool isDocComment,
|
||||
List<String> declarationNames) {
|
||||
MirrorSystem mirrors = createMirrorSystem(code);
|
||||
LibraryMirror library = mirrors.libraries[SOURCE];
|
||||
LibraryMirror library = mirrors.libraries[SOURCE_URI];
|
||||
Expect.isNotNull(library);
|
||||
for (String declarationName in declarationNames) {
|
||||
DeclarationMirror declaration = library.members[declarationName];
|
||||
|
|
|
@ -8,6 +8,10 @@ import '../../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.
|
|||
import '../../../sdk/lib/_internal/compiler/implementation/mirrors/dart2js_mirror.dart';
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:uri';
|
||||
|
||||
const Uri DART_MIRRORS_URI =
|
||||
const Uri.fromComponents(scheme: 'dart', path: 'mirrors');
|
||||
|
||||
int count(Iterable iterable) {
|
||||
var count = 0;
|
||||
|
@ -26,7 +30,7 @@ bool containsType(TypeMirror expected, Iterable<TypeMirror> iterable) {
|
|||
return false;
|
||||
}
|
||||
|
||||
DeclarationMirror findMirror(List<DeclarationMirror> list, String name) {
|
||||
DeclarationMirror findMirror(Iterable<DeclarationMirror> list, String name) {
|
||||
for (DeclarationMirror mirror in list) {
|
||||
if (mirror.simpleName == name) {
|
||||
return mirror;
|
||||
|
@ -54,12 +58,13 @@ void test(MirrorSystem mirrors) {
|
|||
Expect.isNotNull(libraries, "No libraries map returned");
|
||||
Expect.isFalse(libraries.isEmpty, "Empty libraries map returned");
|
||||
|
||||
var helperLibrary = libraries["mirrors_helper"];
|
||||
var helperLibrary = findMirror(libraries.values, "mirrors_helper");
|
||||
Expect.isNotNull(helperLibrary, "Library 'mirrors_helper' not found");
|
||||
Expect.stringEquals("mirrors_helper", helperLibrary.simpleName,
|
||||
"Unexpected library simple name");
|
||||
Expect.stringEquals("mirrors_helper", helperLibrary.qualifiedName,
|
||||
"Unexpected library qualified name");
|
||||
Expect.equals(helperLibrary, mirrors.findLibrary('mirrors_helper').single);
|
||||
|
||||
var helperLibraryLocation = helperLibrary.location;
|
||||
Expect.isNotNull(helperLibraryLocation);
|
||||
|
@ -181,7 +186,7 @@ void testFoo(MirrorSystem system, LibraryMirror helperLibrary,
|
|||
var metadataListIndex = 0;
|
||||
var metadata;
|
||||
|
||||
var dartMirrorsLibrary = system.libraries['dart.mirrors'];
|
||||
var dartMirrorsLibrary = system.libraries[DART_MIRRORS_URI];
|
||||
Expect.isNotNull(dartMirrorsLibrary);
|
||||
var commentType = dartMirrorsLibrary.classes['Comment'];
|
||||
Expect.isNotNull(commentType);
|
||||
|
|
|
@ -24,6 +24,7 @@ import 'html_diff.dart';
|
|||
// TODO(rnystrom): Use "package:" URL (#4968).
|
||||
import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart';
|
||||
import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.dart';
|
||||
import '../../sdk/lib/_internal/compiler/implementation/filenames.dart';
|
||||
import '../../sdk/lib/_internal/dartdoc/lib/dartdoc.dart';
|
||||
import '../../sdk/lib/_internal/libraries.dart';
|
||||
import 'package:pathos/path.dart' as pathos;
|
||||
|
@ -101,7 +102,7 @@ void main() {
|
|||
// TODO(amouravski): move HtmlDiff inside of the future chain below to re-use
|
||||
// the MirrorSystem already analyzed.
|
||||
_diff = new HtmlDiff(printWarnings:false);
|
||||
Future htmlDiff = _diff.run(libPath);
|
||||
Future htmlDiff = _diff.run(currentDirectory.resolve(libPath.toString()));
|
||||
|
||||
// TODO(johnniwinther): Libraries for the compilation seem to be more like
|
||||
// URIs. Perhaps Path should have a toURI() method.
|
||||
|
@ -353,7 +354,7 @@ class Apidoc extends Dartdoc {
|
|||
}
|
||||
|
||||
var typeString = '';
|
||||
if (HTML_LIBRARY_NAMES.contains(displayName(type.library))) {
|
||||
if (HTML_LIBRARY_URIS.contains(type.library.uri)) {
|
||||
// If it's an HTML type, try to map it to a base DOM type so we can find
|
||||
// the MDN docs.
|
||||
final domTypes = _diff.htmlTypesToDom[type.qualifiedName];
|
||||
|
@ -390,7 +391,7 @@ class Apidoc extends Dartdoc {
|
|||
MdnComment includeMdnMemberComment(MemberMirror member) {
|
||||
var library = findLibrary(member);
|
||||
var memberString = '';
|
||||
if (HTML_LIBRARY_NAMES.contains(displayName(library))) {
|
||||
if (HTML_LIBRARY_URIS.contains(library.uri)) {
|
||||
// If it's an HTML type, try to map it to a DOM type name so we can find
|
||||
// the MDN docs.
|
||||
final domMembers = _diff.htmlToDom[member.qualifiedName];
|
||||
|
@ -443,7 +444,7 @@ class Apidoc extends Dartdoc {
|
|||
String _linkMember(MemberMirror member) {
|
||||
final typeName = member.owner.simpleName;
|
||||
var memberName = '$typeName.${member.simpleName}';
|
||||
if (member is MethodMirror && (member.isConstructor || member.isFactory)) {
|
||||
if (member is MethodMirror && member.isConstructor) {
|
||||
final separator = member.constructorName == '' ? '' : '.';
|
||||
memberName = 'new $typeName$separator${member.constructorName}';
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ library html_diff;
|
|||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:uri';
|
||||
|
||||
import 'lib/metadata.dart';
|
||||
|
||||
|
@ -17,22 +18,18 @@ import 'lib/metadata.dart';
|
|||
import '../../sdk/lib/_internal/compiler/implementation/mirrors/dart2js_mirror.dart';
|
||||
import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors.dart';
|
||||
import '../../sdk/lib/_internal/compiler/implementation/mirrors/mirrors_util.dart';
|
||||
import '../../sdk/lib/_internal/compiler/implementation/source_file_provider.dart';
|
||||
import '../../sdk/lib/_internal/dartdoc/lib/dartdoc.dart';
|
||||
import '../../sdk/lib/html/html_common/metadata.dart';
|
||||
|
||||
// TODO(amouravski): There is currently magic that looks at dart:* libraries
|
||||
// rather than the declared library names. This changed due to recent syntax
|
||||
// changes. We should only need to look at the library 'html'.
|
||||
const List<String> HTML_LIBRARY_NAMES = const [
|
||||
'dart:html',
|
||||
'dart:indexed_db',
|
||||
'dart:svg',
|
||||
'dart:web_audio'];
|
||||
const List<String> HTML_DECLARED_NAMES = const [
|
||||
'dart.dom.html',
|
||||
'dart.dom.indexed_db',
|
||||
'dart.dom.svg',
|
||||
'dart.dom.web_audio'];
|
||||
const List<Uri> HTML_LIBRARY_URIS = const [
|
||||
const Uri.fromComponents(scheme: 'dart', path: 'html'),
|
||||
const Uri.fromComponents(scheme: 'dart', path: 'indexed_db'),
|
||||
const Uri.fromComponents(scheme: 'dart', path: 'svg'),
|
||||
const Uri.fromComponents(scheme: 'dart', path: 'web_audio')];
|
||||
|
||||
/**
|
||||
* A class for computing a many-to-many mapping between the types and
|
||||
|
@ -92,17 +89,19 @@ class HtmlDiff {
|
|||
* should be initialized (via [parseOptions] and [initializeWorld]) and
|
||||
* [HtmlDiff.initialize] should be called.
|
||||
*/
|
||||
Future run(Path libDir) {
|
||||
Future run(Uri libraryRoot) {
|
||||
var result = new Completer();
|
||||
var paths = <Path>[];
|
||||
for (var libraryName in HTML_LIBRARY_NAMES) {
|
||||
paths.add(new Path(libraryName));
|
||||
}
|
||||
analyze(paths, libDir).then((MirrorSystem mirrors) {
|
||||
for (var libraryName in HTML_DECLARED_NAMES) {
|
||||
var library = mirrors.libraries[libraryName];
|
||||
var provider = new SourceFileProvider();
|
||||
var handler = new FormattingDiagnosticHandler(provider);
|
||||
Future<MirrorSystem> analysis = analyzeUri(
|
||||
HTML_LIBRARY_URIS, libraryRoot, null,
|
||||
provider.readStringFromUri,
|
||||
handler.diagnosticHandler);
|
||||
analysis.then((MirrorSystem mirrors) {
|
||||
for (var libraryUri in HTML_LIBRARY_URIS) {
|
||||
var library = mirrors.libraries[libraryUri];
|
||||
if (library == null) {
|
||||
warn('Could not find $libraryName');
|
||||
warn('Could not find $libraryUri');
|
||||
result.complete(false);
|
||||
}
|
||||
for (ClassMirror type in library.classes.values) {
|
||||
|
|
Loading…
Reference in a new issue