Skip to content

Commit 43244b9

Browse files
authored
Add spanForElement, for nicer debugging messages. (#206)
* WIP: 0.6.1 features. * Better test case. * Address feedback.
1 parent 4f5da56 commit 43244b9

File tree

6 files changed

+99
-3
lines changed

6 files changed

+99
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.6.1
2+
3+
* Added `spanForElement`; returns a `SourceSpan` for an analyzer `Element`.
4+
15
## 0.6.0
26

37
* **Breaking change**: `TypeChecker#annotationsOf|firstAnnotationOf` now

lib/source_gen.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export 'src/generator.dart';
1010
export 'src/generator_for_annotation.dart';
1111
export 'src/library.dart' show LibraryReader;
1212
export 'src/revive.dart' show Revivable;
13+
export 'src/span_for_element.dart' show spanForElement;
1314
export 'src/type_checker.dart' show TypeChecker;

lib/src/span_for_element.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:source_span/source_span.dart';
7+
8+
import 'utils.dart';
9+
10+
/// Returns a source span that spans the location where [element] is defined.
11+
///
12+
/// May be used to emit user-friendly warning and error messages:
13+
/// ```dart
14+
/// void invalidClass(ClassElement class) {
15+
/// log.warning(spanForElement.message('Cannot implement "Secret"'));
16+
/// }
17+
/// ```
18+
///
19+
/// Not all results from the analyzer API may return source information as part
20+
/// of the element, so [file] may need to be manually provided in those cases.
21+
SourceSpan spanForElement(Element element, [SourceFile file]) {
22+
final url = assetToPackageUrl(element.source.uri);
23+
if (file == null) {
24+
final contents = element?.source?.contents;
25+
if (contents == null) {
26+
return new SourceSpan(
27+
new SourceLocation(
28+
element.nameOffset,
29+
sourceUrl: url,
30+
),
31+
new SourceLocation(
32+
element.nameOffset + element.nameLength,
33+
sourceUrl: url,
34+
),
35+
element.name,
36+
);
37+
}
38+
file = new SourceFile.fromString(contents.data, url: url);
39+
}
40+
return file.span(element.nameOffset, element.nameOffset + element.nameLength);
41+
}

lib/src/utils.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ Uri normalizeDartUrl(Uri url) => url.pathSegments.isNotEmpty
105105
? url.replace(pathSegments: url.pathSegments.take(1))
106106
: url;
107107

108-
/// Returns a `package:` URL into a `asset:` URL.
108+
/// Returns a `package:` URL converted to a `asset:` URL.
109109
///
110110
/// This makes internal comparison logic much easier, but still allows users
111111
/// to define assets in terms of `package:`, which is something that makes more
112112
/// sense to most.
113113
///
114-
/// For example this transforms `package:source_gen/source_gen.dart` into:
114+
/// For example, this transforms `package:source_gen/source_gen.dart` into:
115115
/// `asset:source_gen/lib/source_gen.dart`.
116116
Uri packageToAssetUrl(Uri url) => url.scheme == 'package'
117117
? url.replace(
@@ -122,6 +122,24 @@ Uri packageToAssetUrl(Uri url) => url.scheme == 'package'
122122
..addAll(url.pathSegments.skip(1)))
123123
: url;
124124

125+
/// Returns a `asset:` URL converted to a `package:` URL.
126+
///
127+
/// For example, this transformers `asset:source_gen/lib/source_gen.dart' into:
128+
/// `package:source_gen/source_gen.dart`. Asset URLs that aren't pointing to a
129+
/// file in the 'lib' folder are not modified.
130+
///
131+
/// Asset URLs come from `package:build`, as they are able to describe URLs that
132+
/// are not describable using `package:...`, such as files in the `bin`, `tool`,
133+
/// `web`, or even root directory of a package - `asset:some_lib/web/main.dart`.
134+
Uri assetToPackageUrl(Uri url) => url.scheme == 'asset' &&
135+
url.pathSegments.length >= 1 &&
136+
url.pathSegments[1] == 'lib'
137+
? url.replace(
138+
scheme: 'package',
139+
pathSegments: [url.pathSegments.first]
140+
..addAll(url.pathSegments.skip(2)))
141+
: url;
142+
125143
/// Returns all of the declarations in [unit], including [unit] as the first
126144
/// item.
127145
Iterable<Element> getElementsFromLibraryElement(LibraryElement unit) sync* {

pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: source_gen
2-
version: 0.6.0
2+
version: 0.6.1-dev
33
author: Dart Team <[email protected]>
44
description: Automated source code generation for Dart.
55
homepage: https://github.com/dart-lang/source_gen
@@ -11,6 +11,7 @@ dependencies:
1111
collection: ^1.1.2
1212
dart_style: '>=0.1.7 <2.0.0'
1313
path: ^1.3.2
14+
source_span: ^1.0.0
1415
dev_dependencies:
1516
build_runner: ^0.3.2
1617
build_test: ^0.6.0

test/span_for_element_test.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:build/build.dart';
7+
import 'package:build_test/build_test.dart';
8+
import 'package:source_gen/source_gen.dart';
9+
import 'package:test/test.dart';
10+
11+
void main() {
12+
LibraryElement library;
13+
14+
setUpAll(() async {
15+
final resolver = await resolveSource(r'''
16+
library test_lib;
17+
18+
abstract class Example implements List {}
19+
''', inputId: new AssetId('test_lib', 'lib/test_lib.dart'));
20+
library = resolver.getLibraryByName('test_lib');
21+
});
22+
23+
test('should highlight the use of "class Example"', () {
24+
expect(
25+
spanForElement(library.getType('Example')).message('Here it is'),
26+
''
27+
'line 3, column 22 of package:test_lib/test_lib.dart: Here it is\n'
28+
' abstract class Example implements List {}\n'
29+
' ^^^^^^^');
30+
});
31+
}

0 commit comments

Comments
 (0)