blob: b4a2990d2578e6e835a30b5dd0b85c9b6722fcee [file] [log] [blame]
// Copyright (c) 2017, 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'Adhere to Effective Dart Guide directives sorting conventions.';
const _details = r'''
**DO** follow the conventions in the
[Effective Dart Guide](https://dart.dev/guides/language/effective-dart/style#ordering)
**DO** place “dart:” imports before other imports.
**BAD:**
```dart
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
import 'dart:async'; // LINT
import 'dart:html'; // LINT
```
**BAD:**
```dart
import 'dart:html'; // OK
import 'package:bar/bar.dart';
import 'dart:async'; // LINT
import 'package:foo/foo.dart';
```
**GOOD:**
```dart
import 'dart:async'; // OK
import 'dart:html'; // OK
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
```
**DO** place “package:” imports before relative imports.
**BAD:**
```dart
import 'a.dart';
import 'b.dart';
import 'package:bar/bar.dart'; // LINT
import 'package:foo/foo.dart'; // LINT
```
**BAD:**
```dart
import 'package:bar/bar.dart'; // OK
import 'a.dart';
import 'package:foo/foo.dart'; // LINT
import 'b.dart';
```
**GOOD:**
```dart
import 'package:bar/bar.dart'; // OK
import 'package:foo/foo.dart'; // OK
import 'a.dart';
import 'b.dart';
```
**DO** specify exports in a separate section after all imports.
**BAD:**
```dart
import 'src/error.dart';
export 'src/error.dart'; // LINT
import 'src/string_source.dart';
```
**GOOD:**
```dart
import 'src/error.dart';
import 'src/string_source.dart';
export 'src/error.dart'; // OK
```
**DO** sort sections alphabetically.
**BAD:**
```dart
import 'package:foo/bar.dart'; // OK
import 'package:bar/bar.dart'; // LINT
import 'a/b.dart'; // OK
import 'a.dart'; // LINT
```
**GOOD:**
```dart
import 'package:bar/bar.dart'; // OK
import 'package:foo/bar.dart'; // OK
import 'a.dart'; // OK
import 'a/b.dart'; // OK
''';
const _directiveSectionOrderedAlphabetically =
'Sort directive sections alphabetically.';
const _exportDirectiveAfterImportDirectives =
'Specify exports in a separate section after all imports.';
const _exportKeyword = 'export';
const _importKeyword = 'import';
String _dartDirectiveGoFirst(String type) =>
"Place 'dart:' ${type}s before other ${type}s.";
bool _isAbsoluteDirective(NamespaceDirective node) {
var uriContent = node.uriContent;
return uriContent != null && uriContent.contains(':');
}
bool _isDartDirective(NamespaceDirective node) {
var uriContent = node.uriContent;
return uriContent != null && uriContent.startsWith('dart:');
}
bool _isExportDirective(Directive node) => node is ExportDirective;
bool _isNotDartDirective(NamespaceDirective node) => !_isDartDirective(node);
bool _isPackageDirective(NamespaceDirective node) {
var uriContent = node.uriContent;
return uriContent != null && uriContent.startsWith('package:');
}
bool _isPartDirective(Directive node) => node is PartDirective;
bool _isRelativeDirective(NamespaceDirective node) =>
!_isAbsoluteDirective(node);
String _packageDirectiveBeforeRelative(String type) =>
"Place 'package:' ${type}s before relative ${type}s.";
class DirectivesOrdering extends LintRule
implements ProjectVisitor, NodeLintRule {
DartProject? project;
DirectivesOrdering()
: super(
name: 'directives_ordering',
description: _desc,
details: _details,
group: Group.style);
@override
ProjectVisitor getProjectVisitor() => this;
@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this);
registry.addCompilationUnit(this, visitor);
}
@override
void visit(DartProject project) {
this.project = project;
}
void _reportLintWithDartDirectiveGoFirstMessage(AstNode node, String type) {
_reportLintWithDescription(node, _dartDirectiveGoFirst(type));
}
void _reportLintWithDescription(AstNode node, String description) {
reporter.reportErrorForNode(LintCode(name, description), node, []);
}
void _reportLintWithDirectiveSectionOrderedAlphabeticallyMessage(
AstNode node) {
_reportLintWithDescription(node, _directiveSectionOrderedAlphabetically);
}
void _reportLintWithExportDirectiveAfterImportDirectiveMessage(AstNode node) {
_reportLintWithDescription(node, _exportDirectiveAfterImportDirectives);
}
void _reportLintWithPackageDirectiveBeforeRelativeMessage(
AstNode node, String type) {
_reportLintWithDescription(node, _packageDirectiveBeforeRelative(type));
}
}
class _PackageBox {
final String _packageName;
_PackageBox(this._packageName);
bool _isNotOwnPackageDirective(NamespaceDirective node) =>
_isPackageDirective(node) && !_isOwnPackageDirective(node);
bool _isOwnPackageDirective(NamespaceDirective node) {
var uriContent = node.uriContent;
return uriContent != null &&
uriContent.startsWith('package:$_packageName/');
}
}
class _Visitor extends SimpleAstVisitor<void> {
final DirectivesOrdering rule;
_Visitor(this.rule);
DartProject? get project => rule.project;
@override
void visitCompilationUnit(CompilationUnit node) {
var lintedNodes = <AstNode>{};
_checkDartDirectiveGoFirst(lintedNodes, node);
_checkPackageDirectiveBeforeRelative(lintedNodes, node);
_checkExportDirectiveAfterImportDirective(lintedNodes, node);
_checkDirectiveSectionOrderedAlphabetically(lintedNodes, node);
}
void _checkDartDirectiveGoFirst(
Set<AstNode> lintedNodes, CompilationUnit node) {
void reportImport(NamespaceDirective directive) {
if (lintedNodes.add(directive)) {
rule._reportLintWithDartDirectiveGoFirstMessage(
directive, _importKeyword);
}
}
void reportExport(NamespaceDirective directive) {
if (lintedNodes.add(directive)) {
rule._reportLintWithDartDirectiveGoFirstMessage(
directive, _exportKeyword);
}
}
Iterable<NamespaceDirective> getNodesToLint(
Iterable<NamespaceDirective> directives) =>
directives.skipWhile(_isDartDirective).where(_isDartDirective);
getNodesToLint(_getImportDirectives(node)).forEach(reportImport);
getNodesToLint(_getExportDirectives(node)).forEach(reportExport);
}
void _checkDirectiveSectionOrderedAlphabetically(
Set<AstNode> lintedNodes, CompilationUnit node) {
var importDirectives = _getImportDirectives(node);
var exportDirectives = _getExportDirectives(node);
var dartImports = importDirectives.where(_isDartDirective);
var dartExports = exportDirectives.where(_isDartDirective);
var relativeImports = importDirectives.where(_isRelativeDirective);
var relativeExports = exportDirectives.where(_isRelativeDirective);
_checkSectionInOrder(lintedNodes, dartImports);
_checkSectionInOrder(lintedNodes, dartExports);
_checkSectionInOrder(lintedNodes, relativeImports);
_checkSectionInOrder(lintedNodes, relativeExports);
var projectName = project?.name;
if (projectName == null) {
// Not a pub package. Package directives should be sorted in one block.
var packageImports = importDirectives.where(_isPackageDirective);
var packageExports = exportDirectives.where(_isPackageDirective);
_checkSectionInOrder(lintedNodes, packageImports);
_checkSectionInOrder(lintedNodes, packageExports);
} else {
var packageBox = _PackageBox(projectName);
var thirdPartyPackageImports =
importDirectives.where(packageBox._isNotOwnPackageDirective);
var thirdPartyPackageExports =
exportDirectives.where(packageBox._isNotOwnPackageDirective);
var ownPackageImports =
importDirectives.where(packageBox._isOwnPackageDirective);
var ownPackageExports =
exportDirectives.where(packageBox._isOwnPackageDirective);
_checkSectionInOrder(lintedNodes, thirdPartyPackageImports);
_checkSectionInOrder(lintedNodes, thirdPartyPackageExports);
_checkSectionInOrder(lintedNodes, ownPackageImports);
_checkSectionInOrder(lintedNodes, ownPackageExports);
}
}
void _checkExportDirectiveAfterImportDirective(
Set<AstNode> lintedNodes, CompilationUnit node) {
void reportDirective(Directive directive) {
if (lintedNodes.add(directive)) {
rule._reportLintWithExportDirectiveAfterImportDirectiveMessage(
directive);
}
}
node.directives.reversed
.skipWhile(_isPartDirective)
.skipWhile(_isExportDirective)
.where(_isExportDirective)
.forEach(reportDirective);
}
void _checkPackageDirectiveBeforeRelative(
Set<AstNode> lintedNodes, CompilationUnit node) {
void reportImport(NamespaceDirective directive) {
if (lintedNodes.add(directive)) {
rule._reportLintWithPackageDirectiveBeforeRelativeMessage(
directive, _importKeyword);
}
}
void reportExport(NamespaceDirective directive) {
if (lintedNodes.add(directive)) {
rule._reportLintWithPackageDirectiveBeforeRelativeMessage(
directive, _exportKeyword);
}
}
Iterable<NamespaceDirective> getNodesToLint(
Iterable<NamespaceDirective> directives) =>
directives
.where(_isNotDartDirective)
.skipWhile(_isAbsoluteDirective)
.where(_isPackageDirective);
getNodesToLint(_getImportDirectives(node)).forEach(reportImport);
getNodesToLint(_getExportDirectives(node)).forEach(reportExport);
}
void _checkSectionInOrder(
Set<AstNode> lintedNodes, Iterable<NamespaceDirective> nodes) {
void reportDirective(NamespaceDirective directive) {
if (lintedNodes.add(directive)) {
rule._reportLintWithDirectiveSectionOrderedAlphabeticallyMessage(
directive);
}
}
NamespaceDirective? previousDirective;
for (var directive in nodes) {
if (previousDirective != null) {
var previousUri = previousDirective.uriContent;
var directiveUri = directive.uriContent;
if (previousUri != null &&
directiveUri != null &&
previousUri.compareTo(directiveUri) > 0) {
reportDirective(directive);
}
}
previousDirective = directive;
}
}
Iterable<ExportDirective> _getExportDirectives(CompilationUnit node) =>
node.directives.whereType<ExportDirective>();
Iterable<ImportDirective> _getImportDirectives(CompilationUnit node) =>
node.directives.whereType<ImportDirective>();
}