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

Add a lint for missing files in conditional imports #3080

Merged
merged 5 commits into from
Dec 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.16.0

- new lint: `conditional_uri_does_not_exist`

# 1.15.0

- new lint: `use_decorated_box`
Expand Down
1 change: 1 addition & 0 deletions example/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ linter:
- cast_nullable_to_non_nullable
- close_sinks
- comment_references
- conditional_uri_does_not_exist
- constant_identifier_names
- control_flow_in_finally
- curly_braces_in_flow_control_structures
Expand Down
2 changes: 2 additions & 0 deletions lib/src/rules.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import 'rules/cascade_invocations.dart';
import 'rules/cast_nullable_to_non_nullable.dart';
import 'rules/close_sinks.dart';
import 'rules/comment_references.dart';
import 'rules/conditional_uri_does_not_exist.dart';
import 'rules/constant_identifier_names.dart';
import 'rules/control_flow_in_finally.dart';
import 'rules/curly_braces_in_flow_control_structures.dart';
Expand Down Expand Up @@ -264,6 +265,7 @@ void registerLintRules({bool inTestMode = false}) {
..register(CastNullableToNonNullable())
..register(CloseSinks())
..register(CommentReferences())
..register(ConditionalUriDoesNotExist())
..register(ConstantIdentifierNames())
..register(ControlFlowInFinally())
..register(CurlyBracesInFlowControlStructures())
Expand Down
73 changes: 73 additions & 0 deletions lib/src/rules/conditional_uri_does_not_exist.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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'Missing conditional import.';

const _details = r'''

**DON'T** reference files that do not exist in conditional imports.

Code may fail at runtime if the condition evaluates such that the missing file
needs to be imported.

**BAD:**
```dart
import 'file_that_does_exist.dart'
if (condition) 'file_that_does_not_exist.dart';
```

**GOOD:**
```dart
import 'file_that_does_exist.dart'
if (condition) 'file_that_also_does_exist.dart';
```

''';

class ConditionalUriDoesNotExist extends LintRule {
ConditionalUriDoesNotExist()
: super(
name: 'conditional_uri_does_not_exist',
description: _desc,
details: _details,
group: Group.style);

@override
void registerNodeProcessors(
NodeLintRegistry registry, LinterContext context) {
var visitor = _Visitor(this);
registry.addConfiguration(this, visitor);
}
}

class _Visitor extends SimpleAstVisitor<void> {
static const LintCode code = LintCode('conditional_uri_does_not_exist',
"The target of the conditional URI '{0}' doesn't exist.",
correctionMessage: 'Try creating the file referenced by the URI, or '
'try using a URI for a file that does exist.');

final LintRule rule;

_Visitor(this.rule);

@override
void visitConfiguration(Configuration configuration) {
String? uriContent = configuration.uri.stringValue;
if (uriContent != null) {
Source? source = configuration.uriSource;
// Checking source with .exists() will not detect the presence of overlays
// in the analysis server (although running the script when the files
// don't exist on disk would also fail to find it).
if (!(source?.exists() ?? false)) {
rule.reportLint(configuration.uri,
arguments: [uriContent], errorCode: code);
}
}
}
}
2 changes: 1 addition & 1 deletion test/rule_test_support.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class ExpectedDiagnostic {
if (!diagnosticMatcher(error)) return false;
if (error.offset != offset) return false;
if (error.length != length) return false;
if (messageContains != null && error.message.contains(messageContains!)) {
if (messageContains != null && !error.message.contains(messageContains!)) {
return false;
}

Expand Down
2 changes: 2 additions & 0 deletions test/rules/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'avoid_function_literals_in_foreach_calls.dart'
import 'avoid_init_to_null.dart' as avoid_init_to_null;
import 'avoid_shadowing_type_parameters.dart'
as avoid_shadowing_type_parameters;
import 'conditional_uri_does_not_exist.dart' as conditional_uri_does_not_exist;
import 'file_names.dart' as file_names;
import 'literal_only_boolean_expressions.dart'
as literal_only_boolean_expressions;
Expand Down Expand Up @@ -39,6 +40,7 @@ void main() {
avoid_function_literals_in_foreach_calls.main();
avoid_init_to_null.main();
avoid_shadowing_type_parameters.main();
conditional_uri_does_not_exist.main();
file_names.main();
literal_only_boolean_expressions.main();
missing_whitespace_between_adjacent_strings.main();
Expand Down
78 changes: 78 additions & 0 deletions test/rules/conditional_uri_does_not_exist.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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:test_reflective_loader/test_reflective_loader.dart';

import '../rule_test_support.dart';

main() {
defineReflectiveSuite(() {
defineReflectiveTests(ConditionalUriDoesNotExistTest);
});
}

@reflectiveTest
class ConditionalUriDoesNotExistTest extends LintRuleTest {
@override
bool get addMetaPackageDep => true;

@override
String get lintRule => 'conditional_uri_does_not_exist';

test_missingDartLibraries() async {
await assertDiagnostics(
r'''
import ''
if (dart.library.io) 'dart:missing_1'
if (dart.library.html) 'dart:async'
if (dart.library.async) 'dart:missing_2';
''',
[
error(HintCode.UNUSED_IMPORT, 7, 2),
lint('conditional_uri_does_not_exist', 35, 16,
messageContains: 'dart:missing_1'),
lint('conditional_uri_does_not_exist', 120, 16,
messageContains: 'dart:missing_2'),
],
);
}

test_missingFiles() async {
newFile('$testPackageRootPath/lib/exists.dart');

await assertDiagnostics(
r'''
import ''
if (dart.library.io) 'missing_1.dart'
if (dart.library.html) 'exists.dart'
if (dart.library.async) 'missing_2.dart';
''',
[
error(HintCode.UNUSED_IMPORT, 7, 2),
lint('conditional_uri_does_not_exist', 35, 16,
messageContains: 'missing_1.dart'),
lint('conditional_uri_does_not_exist', 121, 16,
messageContains: 'missing_2.dart'),
],
);
}

test_missingPackages() async {
await assertDiagnostics(
r'''
import ''
if (dart.library.io) 'package:meta/missing_1.dart'
if (dart.library.html) 'package:meta/meta.dart'
if (dart.library.io) 'package:foo/missing_2.dart';
''',
[
error(HintCode.UNUSED_IMPORT, 7, 2),
lint('conditional_uri_does_not_exist', 35, 29,
messageContains: 'missing_1.dart'),
lint('conditional_uri_does_not_exist', 142, 28,
messageContains: 'missing_2.dart'),
],
);
}
}
1 change: 1 addition & 0 deletions tool/since/linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ cascade_invocations: 0.1.29
cast_nullable_to_non_nullable : 0.1.120
close_sinks: 0.1.19
comment_references: 0.1.17
conditional_uri_does_not_exist: 1.16.0
control_flow_in_finally: 0.1.16
constant_identifier_names: 0.1.1
curly_braces_in_flow_control_structures: 0.1.57
Expand Down