diff --git a/example/all.yaml b/example/all.yaml index 8ae0a3a47..f8741cd7f 100644 --- a/example/all.yaml +++ b/example/all.yaml @@ -67,6 +67,7 @@ linter: - empty_catches - empty_constructor_bodies - empty_statements + - eol_at_end_of_file - exhaustive_cases - file_names - flutter_style_todos diff --git a/lib/src/formatter.dart b/lib/src/formatter.dart index e2520230c..0caef27fe 100644 --- a/lib/src/formatter.dart +++ b/lib/src/formatter.dart @@ -246,7 +246,10 @@ class SimpleFormatter implements ReportFormatter { var tableWidth = max(_summaryLength, longest + largestCountGuess); var pad = tableWidth - longest; var line = ''.padLeft(tableWidth, '-'); - out..writeln(line)..writeln('Counts')..writeln(line); + out + ..writeln(line) + ..writeln('Counts') + ..writeln(line); for (var code in codes) { out ..write(code.padRight(longest)) diff --git a/lib/src/rules.dart b/lib/src/rules.dart index 6111ad78f..af930d83e 100644 --- a/lib/src/rules.dart +++ b/lib/src/rules.dart @@ -69,6 +69,7 @@ import 'rules/do_not_use_environment.dart'; import 'rules/empty_catches.dart'; import 'rules/empty_constructor_bodies.dart'; import 'rules/empty_statements.dart'; +import 'rules/eol_at_end_of_file.dart'; import 'rules/exhaustive_cases.dart'; import 'rules/file_names.dart'; import 'rules/flutter_style_todos.dart'; @@ -266,6 +267,7 @@ void registerLintRules({bool inTestMode = false}) { ..register(EmptyCatches()) ..register(EmptyConstructorBodies()) ..register(EmptyStatements()) + ..register(EolAtEndOfFile()) ..register(ExhaustiveCases()) ..register(FileNames()) ..register(FlutterStyleTodos()) diff --git a/lib/src/rules/eol_at_end_of_file.dart b/lib/src/rules/eol_at_end_of_file.dart new file mode 100644 index 000000000..ca2425036 --- /dev/null +++ b/lib/src/rules/eol_at_end_of_file.dart @@ -0,0 +1,66 @@ +// Copyright (c) 2021, 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/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +import '../analyzer.dart'; + +const _desc = r'Put a single newline at end of file.'; + +const _details = r''' + +**DO** put a single newline at the end of non-empty files. + +**BAD:** +```dart +a { +} +``` + +**GOOD:** +```dart +b { +} + <-- newline +'''; + +class EolAtEndOfFile extends LintRule implements NodeLintRule { + EolAtEndOfFile() + : super( + name: 'eol_at_end_of_file', + description: _desc, + details: _details, + group: Group.style); + + @override + void registerNodeProcessors( + NodeLintRegistry registry, LinterContext context) { + var visitor = _Visitor(this, context); + registry.addCompilationUnit(this, visitor); + } +} + +class _Visitor extends SimpleAstVisitor { + final LintRule rule; + final LinterContext context; + + _Visitor(this.rule, this.context); + + @override + void visitCompilationUnit(CompilationUnit node) { + var content = context.currentUnit.content; + if (content.isNotEmpty && + (!content.endsWithNewline || content.endsWithMultipleNewlines)) { + rule.reportLintForOffset(content.trimRight().length, 1); + } + } +} + +extension on String { + bool get endsWithNewline => newline.any(endsWith); + static const newline = ['\n', '\r']; + bool get endsWithMultipleNewlines => multipleNewlines.any(endsWith); + static const multipleNewlines = ['\n\n', '\r\r', '\r\n\r\n']; +} diff --git a/lib/src/rules/unnecessary_getters_setters.dart b/lib/src/rules/unnecessary_getters_setters.dart index 688691e49..2639f0068 100644 --- a/lib/src/rules/unnecessary_getters_setters.dart +++ b/lib/src/rules/unnecessary_getters_setters.dart @@ -103,7 +103,9 @@ class _Visitor extends SimpleAstVisitor { isSimpleGetter(getter) && getterElement.metadata.isEmpty && setterElement.metadata.isEmpty) { - rule..reportLint(getter.name)..reportLint(setter.name); + rule + ..reportLint(getter.name) + ..reportLint(setter.name); } } } diff --git a/test/integration/eol_at_end_of_file.dart b/test/integration/eol_at_end_of_file.dart new file mode 100644 index 000000000..7026f7edc --- /dev/null +++ b/test/integration/eol_at_end_of_file.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2021, 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/src/lint/io.dart'; +import 'package:analyzer/src/lint/linter.dart'; +import 'package:linter/src/analyzer.dart'; +import 'package:linter/src/cli.dart' as cli; +import 'package:test/test.dart'; + +import '../mocks.dart'; +import '../test_constants.dart'; + +void main() { + group('eol_at_end_of_file', () { + var currentOut = outSink; + var collectingOut = CollectingSink(); + setUp(() => outSink = collectingOut); + tearDown(() { + collectingOut.buffer.clear(); + outSink = currentOut; + }); + test('eol at end of file', () async { + await cli.runLinter([ + '$integrationTestDir/eol_at_end_of_file', + '--rules=eol_at_end_of_file', + ], LinterOptions()); + expect( + collectingOut.trim(), contains('5 files analyzed, 3 issues found')); + expect(collectingOut.trim(), + contains('Put a single newline at end of file')); + }); + }); +} diff --git a/test_data/integration/eol_at_end_of_file/lintconfig.yaml b/test_data/integration/eol_at_end_of_file/lintconfig.yaml new file mode 100644 index 000000000..10eb318ca --- /dev/null +++ b/test_data/integration/eol_at_end_of_file/lintconfig.yaml @@ -0,0 +1,2 @@ +rules: + - eol_at_end_of_file diff --git a/test_data/integration/eol_at_end_of_file/src/empty.dart b/test_data/integration/eol_at_end_of_file/src/empty.dart new file mode 100644 index 000000000..e69de29bb diff --git a/test_data/integration/eol_at_end_of_file/src/multipleComments.dart b/test_data/integration/eol_at_end_of_file/src/multipleComments.dart new file mode 100644 index 000000000..78defead1 --- /dev/null +++ b/test_data/integration/eol_at_end_of_file/src/multipleComments.dart @@ -0,0 +1,4 @@ +void multipleComments() { +} +// This is just a placeholder. +// This file should be replaced with useful code. \ No newline at end of file diff --git a/test_data/integration/eol_at_end_of_file/src/multipleNewLines.dart b/test_data/integration/eol_at_end_of_file/src/multipleNewLines.dart new file mode 100644 index 000000000..43f9365f3 --- /dev/null +++ b/test_data/integration/eol_at_end_of_file/src/multipleNewLines.dart @@ -0,0 +1,3 @@ +void multipleNewlines() { +} + diff --git a/test_data/integration/eol_at_end_of_file/src/newline.dart b/test_data/integration/eol_at_end_of_file/src/newline.dart new file mode 100644 index 000000000..964025131 --- /dev/null +++ b/test_data/integration/eol_at_end_of_file/src/newline.dart @@ -0,0 +1,2 @@ +void newline() { +} diff --git a/test_data/integration/eol_at_end_of_file/src/noNewline.dart b/test_data/integration/eol_at_end_of_file/src/noNewline.dart new file mode 100644 index 000000000..d298bfecc --- /dev/null +++ b/test_data/integration/eol_at_end_of_file/src/noNewline.dart @@ -0,0 +1,2 @@ +void noNewline() { +} \ No newline at end of file diff --git a/test_data/rules/eol_at_end_of_file.dart b/test_data/rules/eol_at_end_of_file.dart new file mode 100644 index 000000000..0f56b6dbf --- /dev/null +++ b/test_data/rules/eol_at_end_of_file.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2021, 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. + +// test w/ `dart test -N eol_at_end_of_file` + +bar() { +} //LINT \ No newline at end of file diff --git a/tool/doc.dart b/tool/doc.dart index a966b48d1..43abdf8e8 100644 --- a/tool/doc.dart +++ b/tool/doc.dart @@ -487,7 +487,7 @@ linter: .where((r) => r.maturity != Maturity.deprecated) .map((r) => r.name) .toList() - ..sort(); + ..sort(); for (var rule in sortedRules) { sb.write(' - $rule\n'); }