diff --git a/CHANGELOG.md b/CHANGELOG.md index e574e3b4..b55775b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.1 + +* Added `spanForElement`; returns a `SourceSpan` for an analyzer `Element`. + ## 0.6.0 * **Breaking change**: `TypeChecker#annotationsOf|firstAnnotationOf` now diff --git a/lib/source_gen.dart b/lib/source_gen.dart index 98b1c782..186c1cfd 100644 --- a/lib/source_gen.dart +++ b/lib/source_gen.dart @@ -10,4 +10,5 @@ export 'src/generator.dart'; export 'src/generator_for_annotation.dart'; export 'src/library.dart' show LibraryReader; export 'src/revive.dart' show Revivable; +export 'src/span_for_element.dart' show spanForElement; export 'src/type_checker.dart' show TypeChecker; diff --git a/lib/src/span_for_element.dart b/lib/src/span_for_element.dart new file mode 100644 index 00000000..cbc57570 --- /dev/null +++ b/lib/src/span_for_element.dart @@ -0,0 +1,41 @@ +// Copyright (c) 2017, 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:analyzer/dart/element/element.dart'; +import 'package:source_span/source_span.dart'; + +import 'utils.dart'; + +/// Returns a source span that spans the location where [element] is defined. +/// +/// May be used to emit user-friendly warning and error messages: +/// ```dart +/// void invalidClass(ClassElement class) { +/// log.warning(spanForElement.message('Cannot implement "Secret"')); +/// } +/// ``` +/// +/// Not all results from the analyzer API may return source information as part +/// of the element, so [file] may need to be manually provided in those cases. +SourceSpan spanForElement(Element element, [SourceFile file]) { + final url = assetToPackageUrl(element.source.uri); + if (file == null) { + final contents = element?.source?.contents; + if (contents == null) { + return new SourceSpan( + new SourceLocation( + element.nameOffset, + sourceUrl: url, + ), + new SourceLocation( + element.nameOffset + element.nameLength, + sourceUrl: url, + ), + element.name, + ); + } + file = new SourceFile.fromString(contents.data, url: url); + } + return file.span(element.nameOffset, element.nameOffset + element.nameLength); +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 0d041503..60a9377f 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -105,13 +105,13 @@ Uri normalizeDartUrl(Uri url) => url.pathSegments.isNotEmpty ? url.replace(pathSegments: url.pathSegments.take(1)) : url; -/// Returns a `package:` URL into a `asset:` URL. +/// Returns a `package:` URL converted to a `asset:` URL. /// /// This makes internal comparison logic much easier, but still allows users /// to define assets in terms of `package:`, which is something that makes more /// sense to most. /// -/// For example this transforms `package:source_gen/source_gen.dart` into: +/// For example, this transforms `package:source_gen/source_gen.dart` into: /// `asset:source_gen/lib/source_gen.dart`. Uri packageToAssetUrl(Uri url) => url.scheme == 'package' ? url.replace( @@ -122,6 +122,24 @@ Uri packageToAssetUrl(Uri url) => url.scheme == 'package' ..addAll(url.pathSegments.skip(1))) : url; +/// Returns a `asset:` URL converted to a `package:` URL. +/// +/// For example, this transformers `asset:source_gen/lib/source_gen.dart' into: +/// `package:source_gen/source_gen.dart`. Asset URLs that aren't pointing to a +/// file in the 'lib' folder are not modified. +/// +/// Asset URLs come from `package:build`, as they are able to describe URLs that +/// are not describable using `package:...`, such as files in the `bin`, `tool`, +/// `web`, or even root directory of a package - `asset:some_lib/web/main.dart`. +Uri assetToPackageUrl(Uri url) => url.scheme == 'asset' && + url.pathSegments.length >= 1 && + url.pathSegments[1] == 'lib' + ? url.replace( + scheme: 'package', + pathSegments: [url.pathSegments.first] + ..addAll(url.pathSegments.skip(2))) + : url; + /// Returns all of the declarations in [unit], including [unit] as the first /// item. Iterable getElementsFromLibraryElement(LibraryElement unit) sync* { diff --git a/pubspec.yaml b/pubspec.yaml index 3a252fbd..c3043862 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: source_gen -version: 0.6.0 +version: 0.6.1-dev author: Dart Team description: Automated source code generation for Dart. homepage: https://github.com/dart-lang/source_gen @@ -11,6 +11,7 @@ dependencies: collection: ^1.1.2 dart_style: '>=0.1.7 <2.0.0' path: ^1.3.2 + source_span: ^1.0.0 dev_dependencies: build_runner: ^0.3.2 build_test: ^0.6.0 diff --git a/test/span_for_element_test.dart b/test/span_for_element_test.dart new file mode 100644 index 00000000..61b2d00a --- /dev/null +++ b/test/span_for_element_test.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2017, 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:analyzer/dart/element/element.dart'; +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:test/test.dart'; + +void main() { + LibraryElement library; + + setUpAll(() async { + final resolver = await resolveSource(r''' + library test_lib; + + abstract class Example implements List {} + ''', inputId: new AssetId('test_lib', 'lib/test_lib.dart')); + library = resolver.getLibraryByName('test_lib'); + }); + + test('should highlight the use of "class Example"', () { + expect( + spanForElement(library.getType('Example')).message('Here it is'), + '' + 'line 3, column 22 of package:test_lib/test_lib.dart: Here it is\n' + ' abstract class Example implements List {}\n' + ' ^^^^^^^'); + }); +}