Skip to content
This repository was archived by the owner on Nov 20, 2024. It is now read-only.

Commit c62ec70

Browse files
committed
1 parent c4c465c commit c62ec70

File tree

3 files changed

+158
-0
lines changed

3 files changed

+158
-0
lines changed

lib/src/rules.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:linter/src/rules/always_specify_types.dart';
1212
import 'package:linter/src/rules/camel_case_types.dart';
1313
import 'package:linter/src/rules/constant_identifier_names.dart';
1414
import 'package:linter/src/rules/empty_constructor_bodies.dart';
15+
import 'package:linter/src/rules/implementation_imports.dart';
1516
import 'package:linter/src/rules/library_names.dart';
1617
import 'package:linter/src/rules/library_prefixes.dart';
1718
import 'package:linter/src/rules/non_constant_identifier_names.dart';
@@ -30,6 +31,7 @@ final Registry ruleRegistry = new Registry()
3031
..register(new CamelCaseTypes())
3132
..register(new ConstantIdentifierNames())
3233
..register(new EmptyConstructorBodies())
34+
..register(new ImplementationImports())
3335
..register(new LibraryNames())
3436
..register(new LibraryPrefixes())
3537
..register(new NonConstantIdentifierNames())
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) 2015, 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+
library linter.src.rules.implementation_imports;
6+
7+
import 'package:analyzer/src/generated/ast.dart'
8+
show AstVisitor, ImportDirective, SimpleAstVisitor;
9+
import 'package:linter/src/linter.dart';
10+
11+
const desc = "Don't import implementation files from another package.";
12+
13+
const details = '''
14+
**DON'T** import implementation files from another package.
15+
16+
From the the [pub package layout doc]
17+
(https://www.dartlang.org/tools/pub/package-layout.html#implementation-files):
18+
19+
The libraries inside `lib` are publicly visible: other packages are free to
20+
import them. But much of a package's code is internal implementation libraries
21+
that should only be imported and used by the package itself. Those go inside a
22+
subdirectory of `lib` called `src`. You can create subdirectories in there if
23+
it helps you organize things.
24+
25+
You are free to import libraries that live in `lib/src` from within other Dart
26+
code in the same package (like other libraries in `lib`, scripts in `bin`,
27+
and tests) but you should never import from another package's `lib/src`
28+
directory. Those files are not part of the package’s public API, and they
29+
might change in ways that could break your code.
30+
31+
**Bad:**
32+
33+
```
34+
// In 'road_runner'
35+
import 'package:acme/lib/src/internals.dart;
36+
```
37+
''';
38+
39+
bool isImplementation(Uri uri) {
40+
List<String> segments = uri?.pathSegments ?? const [];
41+
if (segments.length > 2) {
42+
if (segments[1] == 'src') {
43+
return true;
44+
}
45+
}
46+
return false;
47+
}
48+
49+
bool isPackage(Uri uri) => uri?.scheme == 'package';
50+
51+
bool samePackage(Uri uri1, Uri uri2) {
52+
var segments1 = uri1.pathSegments;
53+
var segments2 = uri2.pathSegments;
54+
if (segments1.length < 1 || segments2.length < 1) {
55+
return false;
56+
}
57+
return segments1[0] == segments2[0];
58+
}
59+
60+
class ImplementationImports extends LintRule {
61+
ImplementationImports()
62+
: super(
63+
name: 'implementation_imports',
64+
description: desc,
65+
details: details,
66+
group: Group.style);
67+
68+
@override
69+
AstVisitor getVisitor() => new Visitor(this);
70+
}
71+
72+
class Visitor extends SimpleAstVisitor {
73+
final LintRule rule;
74+
Visitor(this.rule);
75+
76+
@override
77+
visitImportDirective(ImportDirective node) {
78+
Uri importUri = node?.source?.uri;
79+
Uri sourceUri = node?.element?.source?.uri;
80+
81+
// Test for 'package:*/src/'.
82+
if (!isImplementation(importUri)) {
83+
return;
84+
}
85+
86+
// If the source URI is not a `package` URI bail out.
87+
if (!isPackage(sourceUri)) {
88+
return;
89+
}
90+
91+
if (!samePackage(importUri, sourceUri)) {
92+
rule.reportLint(node.uri);
93+
}
94+
}
95+
}

test/rule_test.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:linter/src/io.dart';
1414
import 'package:linter/src/linter.dart';
1515
import 'package:linter/src/rules.dart';
1616
import 'package:linter/src/rules/camel_case_types.dart';
17+
import 'package:linter/src/rules/implementation_imports.dart';
1718
import 'package:linter/src/rules/package_prefixed_library_names.dart';
1819
import 'package:linter/src/util.dart';
1920
import 'package:path/path.dart' as p;
@@ -55,6 +56,66 @@ defineRuleTests() {
5556
}
5657

5758
defineRuleUnitTests() {
59+
group('uris', () {
60+
group('isPackage', () {
61+
var uris = [
62+
Uri.parse('package:foo/src/bar.dart'),
63+
Uri.parse('package:foo/src/baz/bar.dart')
64+
];
65+
uris.forEach((uri) {
66+
test(uri.toString(), () {
67+
expect(isPackage(uri), isTrue);
68+
});
69+
});
70+
var uris2 = [
71+
Uri.parse('foo/bar.dart'),
72+
Uri.parse('src/bar.dart'),
73+
Uri.parse('dart:async')
74+
];
75+
uris2.forEach((uri) {
76+
test(uri.toString(), () {
77+
expect(isPackage(uri), isFalse);
78+
});
79+
});
80+
});
81+
82+
group('samePackage', () {
83+
test('identity', () {
84+
expect(
85+
samePackage(Uri.parse('package:foo/src/bar.dart'),
86+
Uri.parse('package:foo/src/bar.dart')),
87+
isTrue);
88+
});
89+
test('foo/bar.dart', () {
90+
expect(
91+
samePackage(Uri.parse('package:foo/src/bar.dart'),
92+
Uri.parse('package:foo/bar.dart')),
93+
isTrue);
94+
});
95+
});
96+
97+
group('implementation', () {
98+
var uris = [
99+
Uri.parse('package:foo/src/bar.dart'),
100+
Uri.parse('package:foo/src/baz/bar.dart')
101+
];
102+
uris.forEach((uri) {
103+
test(uri.toString(), () {
104+
expect(isImplementation(uri), isTrue);
105+
});
106+
});
107+
var uris2 = [
108+
Uri.parse('package:foo/bar.dart'),
109+
Uri.parse('src/bar.dart')
110+
];
111+
uris2.forEach((uri) {
112+
test(uri.toString(), () {
113+
expect(isImplementation(uri), isFalse);
114+
});
115+
});
116+
});
117+
});
118+
58119
group('names', () {
59120
group('keywords', () {
60121
var good = ['class', 'if', 'assert', 'catch', 'import'];

0 commit comments

Comments
 (0)