Skip to content

Commit 7232910

Browse files
authored
Add LibraryReader.pathToUrl. (#297)
* Add LibraryReader.pathToUrl. * Address feedback.
1 parent caffaef commit 7232910

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.7.4-dev
2+
3+
* Added `LibraryReader.pathToUrl(Uri|String)`, which computes the `import` or
4+
`export` path necessary to reach the provided URL from the current library.
5+
16
## 0.7.3
27

38
* Allow null and empty outputs form `GeneratorForAnnotation`.

lib/src/library.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import 'package:analyzer/dart/ast/standard_resolution_map.dart';
77
import 'package:analyzer/dart/element/element.dart';
88
// ignore: implementation_imports
99
import 'package:analyzer/src/dart/resolver/scope.dart';
10+
import 'package:path/path.dart' as p;
1011

1112
import 'constants/reader.dart';
1213
import 'type_checker.dart';
14+
import 'utils.dart';
1315

1416
/// Result of finding an [annotation] on [element] through [LibraryReader].
1517
class AnnotatedElement {
@@ -70,6 +72,82 @@ class LibraryReader {
7072
}
7173
}
7274

75+
/// Returns a [Uri] from the current library to the one provided.
76+
///
77+
/// If possible, a `package:` or `dart:` URL scheme will be used to reference
78+
/// the library, falling back to relative paths if required (such as in the
79+
/// `test` directory).
80+
///
81+
/// The support [Uri.scheme]s are (others throw [ArgumentError]):
82+
/// * `dart`
83+
/// * `package`
84+
/// * `asset`
85+
///
86+
/// May throw [ArgumentError] if it is not possible to resolve a path.
87+
Uri pathToUrl(dynamic toUrlOrString) {
88+
final to = toUrlOrString is Uri
89+
? toUrlOrString
90+
: Uri.parse(toUrlOrString as String);
91+
if (to.scheme == 'dart') {
92+
// Convert dart:core/map.dart to dart:core.
93+
return normalizeDartUrl(to);
94+
}
95+
if (to.scheme == 'package') {
96+
// Identity (no-op).
97+
return to;
98+
}
99+
if (to.scheme == 'asset') {
100+
// This is the same thing as a package: URL.
101+
//
102+
// i.e.
103+
// asset:foo/lib/foo.dart ===
104+
// package:foo/foo.dart
105+
if (to.pathSegments.length > 1 && to.pathSegments[1] == 'lib') {
106+
return assetToPackageUrl(to);
107+
}
108+
var from = element.source.uri;
109+
if (from == null) {
110+
throw new StateError('Current library has no source URL');
111+
}
112+
// Normalize (convert to an asset: URL).
113+
from = normalizeUrl(from);
114+
if (_isRelative(from, to)) {
115+
if (from == to) {
116+
// Edge-case: p.relative('a.dart', 'a.dart') == '.', but that is not
117+
// a valid import URL in Dart source code.
118+
return new Uri(path: to.pathSegments.last);
119+
}
120+
final relative = Uri.parse(p.relative(
121+
to.toString(),
122+
from: from.toString(),
123+
));
124+
// We now have a URL like "../b.dart", but we just want "b.dart".
125+
return relative.replace(
126+
pathSegments: relative.pathSegments.skip(1),
127+
);
128+
}
129+
throw new ArgumentError.value(to, 'to', 'Not relative to $from');
130+
}
131+
throw new ArgumentError.value(to, 'to', 'Cannot use scheme "${to.scheme}"');
132+
}
133+
134+
/// Returns whether both [from] and [to] are in the same package and folder.
135+
///
136+
/// For example these are considered relative:
137+
/// * `asset:foo/test/foo.dart` and `asset:foo/test/bar.dart`.
138+
///
139+
/// But these are not:
140+
/// * `asset:foo/test/foo.dart` and `asset:foo/bin/bar.dart`.
141+
/// * `asset:foo/test/foo.dart` and `asset:bar/test/foo.dart`.
142+
static bool _isRelative(Uri from, Uri to) {
143+
final fromSegments = from.pathSegments;
144+
final toSegments = to.pathSegments;
145+
return fromSegments.length >= 2 &&
146+
toSegments.length >= 2 &&
147+
fromSegments[0] == toSegments[0] &&
148+
fromSegments[1] == toSegments[1];
149+
}
150+
73151
/// All of the `class` elements in this library.
74152
Iterable<ClassElement> get classElements =>
75153
element.definingCompilationUnit.types;

test/library/path_to_url_test.dart

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Increase timeouts on this test which resolves source code and can be slow.
6+
import 'package:analyzer/dart/element/element.dart';
7+
import 'package:analyzer/src/generated/source_io.dart';
8+
import 'package:source_gen/source_gen.dart';
9+
import 'package:test/test.dart';
10+
11+
void main() {
12+
LibraryReader reader;
13+
14+
final packageA = Uri.parse('package:a/a.dart');
15+
final packageB = Uri.parse('package:b/b.dart');
16+
final assetPackageA = Uri.parse('asset:a/lib/a.dart');
17+
final assetPackageB = Uri.parse('asset:b/lib/b.dart');
18+
final packageATestDir = Uri.parse('asset:a/test/a.dart');
19+
final packageATestDirFileB = Uri.parse('asset:a/test/b.dart');
20+
final packageATestDirDeepFile = Uri.parse('asset:a/test/in/a/folder/a.dart');
21+
final packageBTestDir = Uri.parse('asset:b/test/b.dart');
22+
final dartAsync = Uri.parse('dart:async');
23+
final dartAsyncPrivate = Uri.parse('dart:async/zone.dart');
24+
25+
group('from a package URL to', () {
26+
setUpAll(() {
27+
reader = new LibraryReader(new _FakeLibraryElement(packageA));
28+
});
29+
30+
test('a dart SDK library', () {
31+
expect(reader.pathToUrl(dartAsync), dartAsync);
32+
});
33+
34+
test('a dart SDK private library', () {
35+
expect(reader.pathToUrl(dartAsyncPrivate), dartAsync);
36+
});
37+
38+
test('the same package', () {
39+
expect(reader.pathToUrl(packageA), packageA);
40+
});
41+
42+
test('the same package as an asset URL', () {
43+
expect(reader.pathToUrl(assetPackageA), packageA);
44+
});
45+
46+
test('another package', () {
47+
expect(reader.pathToUrl(packageB), packageB);
48+
});
49+
50+
test('another package as an asset URL', () {
51+
expect(reader.pathToUrl(assetPackageB), packageB);
52+
});
53+
54+
test('the same package outside of lib should throw', () {
55+
expect(() => reader.pathToUrl(packageATestDir), throwsArgumentError);
56+
});
57+
58+
test('another package outside of lib should throw', () {
59+
expect(() => reader.pathToUrl(packageBTestDir), throwsArgumentError);
60+
});
61+
});
62+
63+
group('from an asset URL representing a package to', () {
64+
setUpAll(() {
65+
reader = new LibraryReader(new _FakeLibraryElement(assetPackageA));
66+
});
67+
68+
test('a dart SDK library', () {
69+
expect(reader.pathToUrl(dartAsync), dartAsync);
70+
});
71+
72+
test('a dart SDK private library', () {
73+
expect(reader.pathToUrl(dartAsyncPrivate), dartAsync);
74+
});
75+
76+
test('the same package', () {
77+
expect(reader.pathToUrl(packageA), packageA);
78+
});
79+
80+
test('the same package as an asset URL', () {
81+
expect(reader.pathToUrl(assetPackageA), packageA);
82+
});
83+
84+
test('another package', () {
85+
expect(reader.pathToUrl(packageB), packageB);
86+
});
87+
88+
test('another package as an asset URL', () {
89+
expect(reader.pathToUrl(assetPackageB), packageB);
90+
});
91+
92+
test('the same package outside of lib should throw', () {
93+
expect(() => reader.pathToUrl(packageATestDir), throwsArgumentError);
94+
});
95+
96+
test('another package outside of lib should throw', () {
97+
expect(() => reader.pathToUrl(packageBTestDir), throwsArgumentError);
98+
});
99+
});
100+
101+
group('from an asset URL representing a test directory to', () {
102+
setUpAll(() {
103+
reader = new LibraryReader(new _FakeLibraryElement(packageATestDir));
104+
});
105+
106+
test('a dart SDK library', () {
107+
expect(reader.pathToUrl(dartAsync), dartAsync);
108+
});
109+
110+
test('a dart SDK private library', () {
111+
expect(reader.pathToUrl(dartAsyncPrivate), dartAsync);
112+
});
113+
114+
test('the same package', () {
115+
expect(reader.pathToUrl(packageA), packageA);
116+
});
117+
118+
test('the same package as an asset URL', () {
119+
expect(reader.pathToUrl(assetPackageA), packageA);
120+
});
121+
122+
test('another package', () {
123+
expect(reader.pathToUrl(packageB), packageB);
124+
});
125+
126+
test('another package as an asset URL', () {
127+
expect(reader.pathToUrl(assetPackageB), packageB);
128+
});
129+
130+
test('the same package in the test directory', () {
131+
expect(reader.pathToUrl(packageATestDir), Uri.parse('a.dart'));
132+
});
133+
134+
test('the same package in the test directory, different file', () {
135+
expect(reader.pathToUrl(packageATestDirFileB), Uri.parse('b.dart'));
136+
});
137+
138+
test('the same package in the test directory, different deeper file', () {
139+
expect(
140+
reader.pathToUrl(packageATestDirDeepFile),
141+
Uri.parse('in/a/folder/a.dart'),
142+
);
143+
});
144+
145+
test('in the same package in the test directory, a shallow file', () {
146+
reader = new LibraryReader(
147+
new _FakeLibraryElement(packageATestDirDeepFile),
148+
);
149+
expect(
150+
reader.pathToUrl(packageATestDir),
151+
Uri.parse('../../../a.dart'),
152+
);
153+
});
154+
155+
test('the same package in the tool directory should throw', () {
156+
final packageAToolDir = Uri.parse('asset:a/tool/a.dart');
157+
expect(() => reader.pathToUrl(packageAToolDir), throwsArgumentError);
158+
});
159+
160+
test('another package in the test directory should throw', () {
161+
expect(() => reader.pathToUrl(packageBTestDir), throwsArgumentError);
162+
});
163+
});
164+
}
165+
166+
class _FakeLibraryElement implements LibraryElement {
167+
final Uri _sourceUri;
168+
169+
_FakeLibraryElement(this._sourceUri);
170+
171+
@override
172+
noSuchMethod(i) => super.noSuchMethod(i);
173+
174+
@override
175+
Source get source => new _FakeSource(_sourceUri);
176+
}
177+
178+
class _FakeSource implements Source {
179+
@override
180+
final Uri uri;
181+
182+
const _FakeSource(this.uri);
183+
184+
@override
185+
noSuchMethod(i) => super.noSuchMethod(i);
186+
}

0 commit comments

Comments
 (0)