Prototype fix to convert URI to the shorthand syntax proposed by:
https://github.com/dart-lang/language/issues/649
There are some differences between the semantics in the issue and in the
proposal: https://github.com/dart-lang/language/tree/master/working/0649%20-%20Import%20shorthand
This branch follows the issue's proposal because I think a shorthand
for relative imports is worth doing.
diff --git a/lib/src/io.dart b/lib/src/io.dart
index 66d56b8..75f14c4 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -5,6 +5,7 @@
import 'dart:convert';
import 'dart:io';
+import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
import 'cli/formatter_options.dart';
@@ -12,6 +13,37 @@
import 'exceptions.dart';
import 'source_code.dart';
+/// Gets the name of the package that contains [uri] or `null` if it is not in
+/// a package.
+String? packageForUri(String? uri) {
+ if (uri == null) return null;
+
+ if (!Uri.parse(uri).isAbsolute) {
+ uri = p.url.normalize(p.url.join(Directory.current.uri.toString(), uri));
+ }
+
+ var dir = p.url.dirname(p.fromUri(uri));
+
+ // TODO: Could cache some of this work for performance.
+
+ // Find the package config containing this file in order to determine what
+ // package the file is in.
+ while (Directory(p.fromUri(dir)).existsSync()) {
+ var configFile =
+ File(p.join(p.fromUri(dir), '.dart_tool', 'package_config.json'));
+ if (configFile.existsSync()) {
+ var packageConfig = PackageConfig.parseBytes(configFile.readAsBytesSync(),
+ Uri.file(p.fromUri(p.url.join(dir, '.dart_tool'))));
+
+ return packageConfig.packageOf(Uri.parse(uri))?.name;
+ }
+
+ dir = p.url.dirname(dir);
+ }
+
+ return null;
+}
+
/// Reads and formats input from stdin until closed.
Future<void> formatStdin(
FormatterOptions options, List<int>? selection, String name) async {
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 4a53ae6..0cc3927 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -6,6 +6,7 @@
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/src/generated/source.dart';
+import 'package:dart_style/src/io.dart';
import 'argument_list_visitor.dart';
import 'call_chain_visitor.dart';
@@ -192,6 +193,8 @@
/// from the output.
final Set<Token> _suppressPrecedingCommentsAndNewLines = {};
+ late final String? _currentPackage = packageForUri(_source.uri);
+
/// Initialize a newly created visitor to write source code representing
/// the visited nodes to the given [writer].
SourceVisitor(this._formatter, this._lineInfo, this._source)
@@ -931,7 +934,7 @@
token(node.rightParenthesis);
space();
- visit(node.uri);
+ _visitUri(node.uri);
}
@override
@@ -1190,7 +1193,7 @@
_simpleStatement(node, () {
token(node.keyword);
space();
- visit(node.uri);
+ _visitUri(node.uri);
_visitConfigurations(node.configurations);
@@ -2082,7 +2085,7 @@
_simpleStatement(node, () {
token(node.keyword);
space();
- visit(node.uri);
+ _visitUri(node.uri);
_visitConfigurations(node.configurations);
@@ -2100,6 +2103,68 @@
});
}
+ void _visitUri(StringLiteral uriLiteral) {
+ if (_formatter.fixes.contains(StyleFix.uriShorthand)) {
+ var uri = Uri.parse(uriLiteral.stringValue!);
+
+ String? result;
+ if (uri.scheme == 'package' &&
+ uri.pathSegments.length > 1 &&
+ uri.path.endsWith('.dart')) {
+ var packageName = uri.pathSegments.first;
+ var packageShortName = packageName.split('.').last;
+
+ var path = uri.pathSegments.skip(1).join('/');
+ // Remove ".dart".
+ path = path.substring(0, path.length - 5);
+
+ if (packageShortName == path) {
+ // package:name/name.dart -> name
+ // package:dotted.name/name.dart -> name
+ result = packageName;
+ } else if (packageName == _currentPackage) {
+ // TODO: The issue uses "/" here but the proposal says ":". Using
+ // "/" to match relative paths below.
+ result = '/$path';
+ } else {
+ // package:name/path.dart -> name:path
+ result = '$packageName:$path';
+ }
+
+ // TODO: Detect if package is same as current package and use ":path".
+ // package:current_package/path.dart -> :path
+ } else if (uri.scheme == 'dart') {
+ result = 'dart:${uri.path}';
+ } else if (uri.scheme == '' && uri.path.endsWith('.dart')) {
+ // Shorthand for relative imports.
+
+ // TODO: This is described in the issue, but not the proposal.
+
+ var path = uri.path;
+ // Remove ".dart".
+ path = path.substring(0, path.length - 5);
+
+ if (!path.startsWith('.')) {
+ // TODO: If path is in root directory, should just do "/".
+ path = './$path';
+ }
+
+ result = path;
+ } else {
+ // No shorthand.
+ }
+
+ if (result != null) {
+ writePrecedingCommentsAndNewlines(uriLiteral.beginToken);
+ _writeText(result, uriLiteral.offset);
+ } else {
+ visit(uriLiteral);
+ }
+ } else {
+ visit(uriLiteral);
+ }
+ }
+
@override
void visitIndexExpression(IndexExpression node) {
builder.nestExpression();
@@ -2411,7 +2476,7 @@
_simpleStatement(node, () {
token(node.keyword);
space();
- visit(node.uri);
+ _visitUri(node.uri);
});
}
@@ -2427,7 +2492,7 @@
// Part-of may have either a name or a URI. Only one of these will be
// non-null. We visit both since visit() ignores null.
visit(node.libraryName);
- visit(node.uri);
+ if (node.uri != null) _visitUri(node.uri!);
});
}
diff --git a/lib/src/style_fix.dart b/lib/src/style_fix.dart
index 032cd31..ffc99bd 100644
--- a/lib/src/style_fix.dart
+++ b/lib/src/style_fix.dart
@@ -24,6 +24,9 @@
static const singleCascadeStatements = StyleFix._('single-cascade-statements',
'Remove unnecessary single cascades from expression statements.');
+ static const uriShorthand = StyleFix._('uri-shorthand',
+ 'Convert URIs in directives to shorthand syntax.');
+
static const all = [
docComments,
functionTypedefs,
@@ -31,6 +34,7 @@
optionalConst,
optionalNew,
singleCascadeStatements,
+ uriShorthand,
];
final String name;
diff --git a/pubspec.lock b/pubspec.lock
index 62aa5bd..4a54c17 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -170,7 +170,7 @@
source: hosted
version: "2.0.1"
package_config:
- dependency: transitive
+ dependency: "direct main"
description:
name: package_config
url: "https://pub.dartlang.org"
diff --git a/pubspec.yaml b/pubspec.yaml
index d858cac..5ccd86b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -12,6 +12,7 @@
dependencies:
analyzer: '>=2.0.0 <3.0.0'
args: '>=1.0.0 <3.0.0'
+ package_config: ^2.0.2
path: ^1.0.0
pub_semver: '>=1.4.4 <3.0.0'
source_span: ^1.4.0