Skip to content

Commit 8a5f09d

Browse files
committed
Lint for library names. Fix #19.
1 parent 51da20a commit 8a5f09d

File tree

4 files changed

+145
-39
lines changed

4 files changed

+145
-39
lines changed

lib/src/rules.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ library rules;
77
import 'package:dart_lint/src/linter.dart';
88
import 'package:dart_lint/src/rules/camel_case_types.dart';
99
import 'package:dart_lint/src/rules/empty_constructor_bodies.dart';
10+
import 'package:dart_lint/src/rules/library_names.dart';
1011
import 'package:dart_lint/src/rules/one_member_abstracts.dart';
1112
import 'package:dart_lint/src/rules/super_goes_last.dart';
1213
import 'package:dart_lint/src/rules/type_init_formals.dart';
@@ -17,6 +18,7 @@ import 'package:dart_lint/src/rules/unnecessary_getters.dart';
1718
final Map<String, LintRule> ruleMap = {
1819
'camel_case_types': new CamelCaseTypes(),
1920
'empty_constructor_bodies': new EmptyConstructorBodies(),
21+
'library_names' : new LibraryNames(),
2022
'one_member_abstracts': new OneMemberAbstracts(),
2123
'super_goes_last': new SuperGoesLast(),
2224
'type_init_formals': new TypeInitFormals(),

lib/src/rules/library_names.dart

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 library_names;
6+
7+
import 'package:analyzer/src/generated/ast.dart';
8+
import 'package:dart_lint/src/linter.dart';
9+
10+
const desc =
11+
'DO name libraries and source files using lowercase_with_underscores.';
12+
13+
const details = r'''
14+
**DO** name libraries and source files using lowercase_with_underscores.
15+
16+
Some file systems are not case-sensitive, so many projects require filenames
17+
to be all lowercase. Using a separate character allows names to still be
18+
readable in that form. Using underscores as the separator ensures that the name
19+
is still a valid Dart identifier, which may be helpful if the language later
20+
supports symbolic imports.
21+
22+
**GOOD:**
23+
24+
* `slider_menu.dart`
25+
* `file_system.dart`
26+
* `library peg_parser;`
27+
28+
**BAD:**
29+
30+
* `SliderMenu.dart`
31+
* `filesystem.dart`
32+
* `library peg-parser;`
33+
```
34+
''';
35+
36+
final _lowerCaseUnderScore = new RegExp(r'^([a-z]+([_]?[a-z]+))+$');
37+
38+
bool isLowerCaseUnderScore(String id) =>
39+
_lowerCaseUnderScore.hasMatch(id);
40+
41+
class LibraryNames extends LintRule {
42+
LibraryNames()
43+
: super(
44+
name: 'LibraryNames',
45+
description: desc,
46+
details: details,
47+
group: Group.STYLE_GUIDE,
48+
kind: Kind.DO);
49+
50+
@override
51+
AstVisitor getVisitor() => new Visitor(this);
52+
}
53+
54+
class Visitor extends SimpleAstVisitor {
55+
LintRule rule;
56+
Visitor(this.rule);
57+
58+
@override
59+
visitLibraryDirective(LibraryDirective node) {
60+
if (!isLowerCaseUnderScore(node.name.toString())) {
61+
rule.reportLint(node);
62+
}
63+
}
64+
}

test/lint_test.dart

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:unittest/unittest.dart';
1818

1919
import '../bin/lint.dart' as dartlint;
2020
import 'package:dart_lint/src/rules/camel_case_types.dart';
21+
import 'package:dart_lint/src/rules/library_names.dart';
2122

2223
const String ruleDir = 'test/rules';
2324

@@ -33,13 +34,19 @@ void defineLinterEngineTests() {
3334
expect(msg, expected);
3435
});
3536
}
36-
_test('exception', 'EXCEPTION: LinterException: foo',
37+
_test(
38+
'exception',
39+
'EXCEPTION: LinterException: foo',
3740
(r) => r.exception(new LinterException('foo')));
3841
_test('logError', 'ERROR: foo', (r) => r.logError('foo'));
39-
_test('logError2', 'ERROR: foo',
42+
_test(
43+
'logError2',
44+
'ERROR: foo',
4045
(r) => r.logError2('foo', new Exception()));
4146
_test('logInformation', 'INFO: foo', (r) => r.logInformation('foo'));
42-
_test('logInformation2', 'INFO: foo',
47+
_test(
48+
'logInformation2',
49+
'INFO: foo',
4350
(r) => r.logInformation2('foo', new Exception()));
4451
_test('warn', 'WARN: foo', (r) => r.warn('foo'));
4552
});
@@ -50,7 +57,8 @@ void defineLinterEngineTests() {
5057
});
5158
test('toString', () {
5259
expect(const LinterException().toString(), equals('LinterException'));
53-
expect(const LinterException('foo').toString(),
60+
expect(
61+
const LinterException('foo').toString(),
5462
equals('LinterException: foo'));
5563
});
5664
});
@@ -61,7 +69,8 @@ void defineLinterEngineTests() {
6169
expect(new CamelCaseString('Foo').humanized, equals('Foo'));
6270
});
6371
test('validation', () {
64-
expect(() => new CamelCaseString('foo'),
72+
expect(
73+
() => new CamelCaseString('foo'),
6574
throwsA(new isInstanceOf<ArgumentError>()));
6675
});
6776
test('toString', () {
@@ -90,9 +99,10 @@ void defineLinterEngineTests() {
9099
bool visited;
91100
var options =
92101
new LinterOptions(() => [new MockLinter((n) => visited = true)]);
93-
new SourceLinter(options).lintLibrarySource(
94-
libraryName: 'testLibrary',
95-
libraryContents: 'library testLibrary;');
102+
new SourceLinter(
103+
options).lintLibrarySource(
104+
libraryName: 'testLibrary',
105+
libraryContents: 'library testLibrary;');
96106
expect(visited, isTrue);
97107
});
98108
});
@@ -129,11 +139,11 @@ void defineLinterEngineTests() {
129139
group('io', () {
130140
test('link', () {
131141
Link l = new Link('bogus');
132-
expect(processFile(l), isFalse);
142+
expect(lintFile(l), isFalse);
133143
});
134144
test('bad extension', () {
135145
File f = new File('bogus.txt');
136-
expect(processFile(f), isFalse);
146+
expect(lintFile(f), isFalse);
137147
});
138148
});
139149

@@ -146,23 +156,26 @@ void defineLinterEngineTests() {
146156
test('html - strong', () {
147157
Hyperlink link =
148158
new Hyperlink('dart', 'http://dartlang.org', bold: true);
149-
expect(link.html, equals(
150-
'<a href="http://dartlang.org"><strong>dart</strong></a>'));
159+
expect(
160+
link.html,
161+
equals('<a href="http://dartlang.org"><strong>dart</strong></a>'));
151162
});
152163
});
153164
group('kind', () {
154165
test('priority', () {
155-
expect(Kind.supported, orderedEquals(
156-
[Kind.DO, Kind.DONT, Kind.PREFER, Kind.AVOID, Kind.CONSIDER]));
166+
expect(
167+
Kind.supported,
168+
orderedEquals([Kind.DO, Kind.DONT, Kind.PREFER, Kind.AVOID, Kind.CONSIDER]));
157169
});
158170
test('comparing', () {
159171
expect(Kind.DO.compareTo(Kind.DONT), equals(-1));
160172
});
161173
});
162174
group('rule', () {
163175
test('sorting', () {
164-
expect(Kind.supported, orderedEquals(
165-
[Kind.DO, Kind.DONT, Kind.PREFER, Kind.AVOID, Kind.CONSIDER]));
176+
expect(
177+
Kind.supported,
178+
orderedEquals([Kind.DO, Kind.DONT, Kind.PREFER, Kind.AVOID, Kind.CONSIDER]));
166179
});
167180
test('comparing', () {
168181
LintRule r1 = new MockLintRule('Bar', Kind.DO);
@@ -202,15 +215,14 @@ defineRuleUnitTests() {
202215
group('camel case', () {
203216
group('upper', () {
204217
var good = [
205-
'_FooBar',
206-
'FooBar',
207-
'_Foo',
208-
'Foo',
209-
'F',
210-
'FB',
211-
'F1',
212-
'FooBar1'
213-
];
218+
'_FooBar',
219+
'FooBar',
220+
'_Foo',
221+
'Foo',
222+
'F',
223+
'FB',
224+
'F1',
225+
'FooBar1'];
214226
good.forEach(
215227
(s) => test('"$s"', () => expect(isUpperCamelCase(s), isTrue)));
216228

@@ -219,6 +231,15 @@ defineRuleUnitTests() {
219231
(s) => test('"$s"', () => expect(isUpperCamelCase(s), isFalse)));
220232
});
221233
});
234+
group('lower_case_underscores', () {
235+
var good = ['foo_bar', 'foo', 'foo_bar_baz'];
236+
good.forEach(
237+
(s) => test('"$s"', () => expect(isLowerCaseUnderScore(s), isTrue)));
238+
239+
var bad = ['Foo', 'fooBar', 'foo_Bar', 'foo_', '_f', 'F_B'];
240+
bad.forEach(
241+
(s) => test('"$s"', () => expect(isLowerCaseUnderScore(s), isFalse)));
242+
});
222243
});
223244
}
224245

@@ -231,7 +252,8 @@ void defineSanityTests() {
231252
expect(extractAnnotation('int x; //LINT'), isNotNull);
232253
expect(extractAnnotation('int x; // OK'), isNull);
233254
expect(extractAnnotation('int x;'), isNull);
234-
expect(extractAnnotation('dynamic x; // LINT dynamic is bad').message,
255+
expect(
256+
extractAnnotation('dynamic x; // LINT dynamic is bad').message,
235257
equals('dynamic is bad'));
236258
expect(extractAnnotation('dynamic x; //LINT').message, isNull);
237259
expect(extractAnnotation('dynamic x; //LINT ').message, isNull);
@@ -241,18 +263,28 @@ void defineSanityTests() {
241263
expect(
242264
new Annotation('Actual message (to be ignored)', ErrorType.LINT, 1),
243265
matchesAnnotation(null, ErrorType.LINT, 1));
244-
expect(new Annotation('Message', ErrorType.LINT, 1),
266+
expect(
267+
new Annotation('Message', ErrorType.LINT, 1),
245268
matchesAnnotation('Message', ErrorType.LINT, 1));
246269
});
247270
test('inequality', () {
248-
expect(() => expect(new Annotation('Message', ErrorType.LINT, 1),
249-
matchesAnnotation('Message', ErrorType.HINT, 1)),
271+
expect(
272+
() =>
273+
expect(
274+
new Annotation('Message', ErrorType.LINT, 1),
275+
matchesAnnotation('Message', ErrorType.HINT, 1)),
250276
throwsA(new isInstanceOf<TestFailure>()));
251-
expect(() => expect(new Annotation('Message', ErrorType.LINT, 1),
252-
matchesAnnotation('Message2', ErrorType.LINT, 1)),
277+
expect(
278+
() =>
279+
expect(
280+
new Annotation('Message', ErrorType.LINT, 1),
281+
matchesAnnotation('Message2', ErrorType.LINT, 1)),
253282
throwsA(new isInstanceOf<TestFailure>()));
254-
expect(() => expect(new Annotation('Message', ErrorType.LINT, 1),
255-
matchesAnnotation('Message', ErrorType.LINT, 2)),
283+
expect(
284+
() =>
285+
expect(
286+
new Annotation('Message', ErrorType.LINT, 1),
287+
matchesAnnotation('Message', ErrorType.LINT, 2)),
256288
throwsA(new isInstanceOf<TestFailure>()));
257289
});
258290
});
@@ -283,9 +315,9 @@ main() {
283315
defineRuleUnitTests();
284316
}
285317

286-
AnnotationMatcher matchesAnnotation(
287-
String message, ErrorType type, int lineNumber) =>
288-
new AnnotationMatcher(new Annotation(message, type, lineNumber));
318+
AnnotationMatcher matchesAnnotation(String message, ErrorType type,
319+
int lineNumber) =>
320+
new AnnotationMatcher(new Annotation(message, type, lineNumber));
289321

290322
void testRule(String ruleName, File file) {
291323
test('$ruleName', () {
@@ -329,8 +361,10 @@ class Annotation {
329361

330362
Annotation(this.message, this.type, this.lineNumber);
331363

332-
Annotation.forError(AnalysisError error, LineInfo lineInfo) : this(
333-
error.message, error.errorCode.type,
364+
Annotation.forError(AnalysisError error, LineInfo lineInfo)
365+
: this(
366+
error.message,
367+
error.errorCode.type,
334368
lineInfo.getLocation(error.offset).lineNumber);
335369

336370
Annotation.forLint([String message]) : this(message, ErrorType.LINT, null);
@@ -371,7 +405,8 @@ class AnnotationMatcher extends Matcher {
371405
class MockLinter extends LintRule {
372406
VisitorCallback visitorCallback;
373407

374-
MockLinter([nodeVisitor v]) : super(
408+
MockLinter([nodeVisitor v])
409+
: super(
375410
name: 'MockLint',
376411
group: Group.STYLE_GUIDE,
377412
kind: Kind.AVOID,

test/rules/library_names.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
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 Foo; //LINT

0 commit comments

Comments
 (0)