Skip to content

Commit 1334fd4

Browse files
committed
Pub lint steel thread.
1 parent 262f4ea commit 1334fd4

File tree

9 files changed

+184
-34
lines changed

9 files changed

+184
-34
lines changed

lib/src/ast.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ library dart_lint.src.ast;
77

88
import 'package:analyzer/src/generated/ast.dart';
99
import 'package:analyzer/src/generated/element.dart';
10+
import 'package:analyzer/src/generated/scanner.dart';
11+
12+
final _identifier = new RegExp(r'^([_a-zA-Z]+([_a-zA-Z0-9])*)$');
13+
14+
/// Returns `true` if the given [id] is a Dart keyword.
15+
bool isKeyWord(String id) => Keyword.keywords.keys.contains(id);
1016

1117
/// Returns `true` if the given [ClassMember] is a method.
1218
bool isMethod(ClassMember m) => m is MethodDeclaration;
@@ -75,6 +81,10 @@ bool isSimpleSetter(MethodDeclaration setter) {
7581
return false;
7682
}
7783

84+
/// Returns `true` if the given [id] is a valid Dart identifier.
85+
bool isValidDartIdentifier(String id) =>
86+
!isKeyWord(id) && _identifier.hasMatch(id);
87+
7888
bool _checkForSimpleGetter(MethodDeclaration getter, Expression expression) {
7989
if (expression is SimpleIdentifier) {
8090
var staticElement = expression.staticElement;

lib/src/io.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ library io;
77
import 'dart:io';
88

99
import 'package:linter/src/linter.dart';
10+
import 'package:linter/src/util.dart';
11+
import 'package:path/path.dart' as p;
1012

13+
bool isDartFile(FileSystemEntity entry) => isDartFileName(entry.path);
14+
15+
bool isPubspecFile(FileSystemEntity entry) =>
16+
isPubspecFileName(p.basename(entry.path));
1117

1218
/// Runs the linter on [file], skipping links and files not ending in the
1319
/// '.dart' extension.
@@ -35,8 +41,8 @@ bool lintFile(FileSystemEntity file, {String dartSdkPath, String packageRoot}) {
3541
}
3642

3743
try {
38-
linter.lintFile(file);
39-
return true;
44+
linter.lintFile(file);
45+
return true;
4046
} catch (err, stack) {
4147
stderr.writeln('''An error occurred while linting $path
4248
Please report it at: github.com/dart-lang/dart_lint/issues
@@ -45,5 +51,3 @@ $stack''');
4551
}
4652
return false;
4753
}
48-
49-
bool isDartFile(FileSystemEntity entry) => entry.path.endsWith('.dart');

lib/src/linter.dart

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:analyzer/src/generated/source.dart';
1313
import 'package:analyzer/src/services/lint.dart';
1414
import 'package:analyzer/src/string_source.dart';
1515
import 'package:linter/src/analysis.dart';
16+
import 'package:linter/src/io.dart';
1617
import 'package:linter/src/pub.dart';
1718
import 'package:linter/src/rules.dart';
1819

@@ -69,12 +70,15 @@ abstract class DartLinter {
6970
Iterable<AnalysisErrorInfo> lintLibrarySource(
7071
{String libraryName, String libraryContents});
7172

72-
Iterable<AnalysisErrorInfo> lintPubSpecSource({String contents});
73+
Iterable<AnalysisErrorInfo> lintPubspecSource({String contents});
7374
}
7475

7576
class Group {
7677

7778
/// Defined rule groups.
79+
static const Group PUB = const Group._('Pub',
80+
link: const Hyperlink('See the <strong>Pubspec Format</strong>',
81+
'https://www.dartlang.org/tools/pub/pubspec.html'));
7882
static const Group STYLE_GUIDE = const Group._('Style Guide',
7983
link: const Hyperlink('See the <strong>Style Guide</strong>',
8084
'https://www.dartlang.org/articles/style-guide/'));
@@ -89,6 +93,8 @@ class Group {
8993
case 'Styleguide':
9094
case 'Style Guide':
9195
return STYLE_GUIDE;
96+
case 'Pub':
97+
return PUB;
9298
default:
9399
return new Group._(name,
94100
custom: true, description: description, link: link);
@@ -211,6 +217,11 @@ abstract class LintRule extends Linter implements Comparable<LintRule> {
211217
/// Lint name.
212218
final CamelCaseString name;
213219

220+
/// Until pubspec analysis is pushed into the analyzer proper, we need to
221+
/// do some extra book-keeping to keep track of details that will help us
222+
/// constitute AnalysisErrorInfos.
223+
final List<AnalysisErrorInfo> _locationInfo = <AnalysisErrorInfo>[];
224+
214225
LintRule({String name, this.group, this.kind, this.description, this.details,
215226
this.maturity: Maturity.STABLE})
216227
: name = new CamelCaseString(name);
@@ -227,16 +238,25 @@ abstract class LintRule extends Linter implements Comparable<LintRule> {
227238
/// Return a visitor to be passed to pubspecs to perform lint
228239
/// analysis.
229240
/// Lint errors are reported via this [Linter]'s error [reporter].
230-
PubSpecVisitor getPubSpecVisitor() => null;
241+
PubSpecVisitor getPubspecVisitor() => null;
242+
243+
@override
244+
AstVisitor getVisitor() => null;
231245

232246
void reportLint(AstNode node) {
233247
reporter.reportErrorForNode(
234248
new LintCode(name.value, description), node, []);
235249
}
236250

237251
void reportPubLint(PSNode node) {
238-
reporter.reportErrorForOffset(new LintCode(name.value, description),
239-
node.span.start.offset, node.span.length);
252+
// Cache error and location info for creating AnalysisErrorInfos
253+
var error = new AnalysisError.con2(reporter.source, node.span.start.offset,
254+
node.span.length, new LintCode(name.value, description));
255+
256+
_locationInfo.add(new AnalysisErrorInfoImpl([error], new _LineInfo(node)));
257+
258+
// Then do the reporting
259+
reporter.reportError(error);
240260
}
241261
}
242262

@@ -317,7 +337,9 @@ class SourceLinter implements DartLinter, AnalysisErrorListener {
317337

318338
@override
319339
Iterable<AnalysisErrorInfo> lintFile(File sourceFile) =>
320-
_registerAndRun(() => new AnalysisDriver.forFile(sourceFile, options));
340+
isPubspecFile(sourceFile)
341+
? _lintPubspecFile(sourceFile)
342+
: _lintDartFile(sourceFile);
321343

322344
@override
323345
Iterable<AnalysisErrorInfo> lintLibrarySource(
@@ -326,31 +348,39 @@ class SourceLinter implements DartLinter, AnalysisErrorListener {
326348
new _StringSource(libraryContents, libraryName), options));
327349

328350
@override
329-
Iterable<AnalysisErrorInfo> lintPubSpecSource({String contents}) {
351+
Iterable<AnalysisErrorInfo> lintPubspecSource({String contents}) {
352+
var results = <AnalysisErrorInfo>[];
330353

331354
//TODO: error handling
332355
var spec = new PubSpec.parse(contents);
333356

334357
for (Linter lint in options.enabledLints) {
335358
if (lint is LintRule) {
336359
LintRule rule = lint;
337-
var visitor = rule.getPubSpecVisitor();
360+
var visitor = rule.getPubspecVisitor();
338361
if (visitor != null) {
339362
try {
340363
spec.accept(visitor);
341364
} on Exception catch (e) {
342365
reporter.exception(new LinterException(e.toString()));
343366
}
367+
results.addAll(rule._locationInfo);
344368
}
345369
}
346370
}
347-
//TODO: collect and return errors
348-
return null;
371+
372+
return results;
349373
}
350374

351375
@override
352376
onError(AnalysisError error) => errors.add(error);
353377

378+
Iterable<AnalysisErrorInfo> _lintDartFile(File sourceFile) =>
379+
_registerAndRun(() => new AnalysisDriver.forFile(sourceFile, options));
380+
381+
Iterable<AnalysisErrorInfo> _lintPubspecFile(File sourceFile) =>
382+
lintPubspecSource(contents: sourceFile.readAsStringSync());
383+
354384
Iterable<AnalysisErrorInfo> _registerAndRun(_DriverFactory createDriver) {
355385
_registerLinters(options.enabledLints);
356386
return createDriver().getErrors();
@@ -360,6 +390,15 @@ class SourceLinter implements DartLinter, AnalysisErrorListener {
360390
new LinterOptions(() => ruleMap.values);
361391
}
362392

393+
class _LineInfo implements LineInfo {
394+
PSNode node;
395+
_LineInfo(this.node);
396+
397+
@override
398+
LineInfo_Location getLocation(int offset) =>
399+
new LineInfo_Location(node.span.start.line + 1, node.span.start.column);
400+
}
401+
363402
class _StringSource extends StringSource {
364403
_StringSource(String contents, String fullName) : super(contents, fullName);
365404

lib/src/rules.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:linter/src/rules/empty_constructor_bodies.dart';
1010
import 'package:linter/src/rules/library_names.dart';
1111
import 'package:linter/src/rules/library_prefixes.dart';
1212
import 'package:linter/src/rules/one_member_abstracts.dart';
13+
import 'package:linter/src/rules/pub/pub_package_names.dart';
1314
import 'package:linter/src/rules/super_goes_last.dart';
1415
import 'package:linter/src/rules/type_init_formals.dart';
1516
import 'package:linter/src/rules/unnecessary_brace_in_string_interp.dart';
@@ -20,9 +21,10 @@ import 'package:linter/src/rules/unnecessary_getters_setters.dart';
2021
final Map<String, LintRule> ruleMap = {
2122
'camel_case_types': new CamelCaseTypes(),
2223
'empty_constructor_bodies': new EmptyConstructorBodies(),
23-
'library_names' : new LibraryNames(),
24-
'library_prefixes' : new LibraryPrefixes(),
24+
'library_names': new LibraryNames(),
25+
'library_prefixes': new LibraryPrefixes(),
2526
'one_member_abstracts': new OneMemberAbstracts(),
27+
'pub_package_names': new PubPackageNames(),
2628
'super_goes_last': new SuperGoesLast(),
2729
'type_init_formals': new TypeInitFormals(),
2830
'unnecessary_brace_in_string_interp': new UnnecessaryBraceInStringInterp(),
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.rules.pub.package_names;
6+
7+
import 'package:linter/src/ast.dart';
8+
import 'package:linter/src/linter.dart';
9+
import 'package:linter/src/pub.dart';
10+
import 'package:linter/src/util.dart';
11+
12+
const desc = 'DO use lowercase_with_underscores for package names';
13+
14+
const details = '''
15+
**DO** use `lowercase_with_underscores` for package names.
16+
17+
From the [Pubspec format description]
18+
(https://www.dartlang.org/tools/pub/pubspec.html):
19+
20+
Package names should be all lowercase, with underscores to separate words,
21+
`just_like_this`. Use only basic Latin letters and Arabic digits: [a-z0-9_].
22+
Also, make sure the name is a valid Dart identifier -- that it doesn't start
23+
with digits and isn't a reserved word.
24+
''';
25+
26+
bool isValidPackageName(String id) =>
27+
isLowerCaseUnderScore(id) && isValidDartIdentifier(id);
28+
29+
class PubPackageNames extends LintRule {
30+
PubPackageNames() : super(
31+
name: 'PubPackageNames',
32+
description: desc,
33+
details: details,
34+
group: Group.PUB,
35+
kind: Kind.DO);
36+
37+
@override
38+
PubSpecVisitor getPubspecVisitor() => new Visitor(this);
39+
}
40+
41+
class Visitor extends PubSpecVisitor {
42+
LintRule rule;
43+
Visitor(this.rule);
44+
45+
visitPackageName(PSEntry name) {
46+
if (!isValidPackageName(name.value.text)) {
47+
rule.reportPubLint(name.value);
48+
}
49+
}
50+
}

lib/src/util.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ final _lowerCaseUnderScore = new RegExp(r'^([a-z]+([_]?[a-z]+))+$');
99
final _lowerCaseUnderScoreWithDots =
1010
new RegExp(r'^([a-z]+([_]?[a-z]+))+(.([a-z]+([_]?[a-z]+)))*$');
1111

12+
final _pubspec = new RegExp(r'^[_]?pubspec.yaml$');
13+
14+
bool isDartFileName(String fileName) => fileName.endsWith('.dart');
15+
1216
bool isLowerCaseUnderScore(String id) => _lowerCaseUnderScore.hasMatch(id);
1317

1418
bool isLowerCaseUnderScoreWithDots(String id) =>
1519
_lowerCaseUnderScoreWithDots.hasMatch(id);
20+
21+
bool isPubspecFileName(String fileName) => _pubspec.hasMatch(fileName);

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ dependencies:
1111
dev_dependencies:
1212
markdown: '>=0.7.1+2 <0.8.0'
1313
mock: '>=0.11.0+2 <0.12.0'
14+
mockito: '>=0.8.2 <0.9.0'
1415
unittest: '>=0.11.0 <0.12.0'

0 commit comments

Comments
 (0)