Skip to content
This repository has been archived by the owner on Jul 16, 2023. It is now read-only.

fix: resolve package with imported analysis options #887

Merged
merged 2 commits into from Jun 13, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Expand Up @@ -30,6 +30,7 @@
"mocktail",
"negatable",
"nullsafety",
"posix",
"pubignore",
"pubspec",
"semver",
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
* test: added test case in [`prefer-const-border-radius`](https://dartcodemetrics.dev/docs/rules/flutter/prefer-const-border-radius) rule.
* chore: restrict `analyzer` version to `>=2.4.0 <4.2.0`.
* fix: improve context root included files calculation.
* fix: resolve package with imported analysis options.

## 4.15.2

Expand Down
1 change: 1 addition & 0 deletions dart_dependency_validator.yaml
@@ -1,3 +1,4 @@
ignore:
- analyzer
- intl
- test_lints
4 changes: 2 additions & 2 deletions lib/src/analyzers/lint_analyzer/lint_analyzer.dart
Expand Up @@ -81,8 +81,8 @@ class LintAnalyzer {
final analyzerResult = <LintFileReport>[];

for (final context in collection.contexts) {
final analysisOptions = await analysisOptionsFromContext(context) ??
await analysisOptionsFromFilePath(rootFolder);
final analysisOptions = analysisOptionsFromContext(context) ??
analysisOptionsFromFilePath(rootFolder, context);

final excludesRootFolder = analysisOptions.folderPath ?? rootFolder;

Expand Down
10 changes: 5 additions & 5 deletions lib/src/analyzers/unused_code_analyzer/unused_code_analyzer.dart
Expand Up @@ -55,7 +55,7 @@ class UnusedCodeAnalyzer {

for (final context in collection.contexts) {
final unusedCodeAnalysisConfig =
await _getAnalysisConfig(context, rootFolder, config);
_getAnalysisConfig(context, rootFolder, config);

final filePaths = getFilePaths(
folders,
Expand Down Expand Up @@ -98,13 +98,13 @@ class UnusedCodeAnalyzer {
return _getReports(codeUsages, publicCode, rootFolder);
}

Future<UnusedCodeAnalysisConfig> _getAnalysisConfig(
UnusedCodeAnalysisConfig _getAnalysisConfig(
AnalysisContext context,
String rootFolder,
UnusedCodeConfig config,
) async {
final analysisOptions = await analysisOptionsFromContext(context) ??
await analysisOptionsFromFilePath(rootFolder);
) {
final analysisOptions = analysisOptionsFromContext(context) ??
analysisOptionsFromFilePath(rootFolder, context);

final contextConfig =
ConfigBuilder.getUnusedCodeConfigFromOption(analysisOptions)
Expand Down
Expand Up @@ -47,7 +47,7 @@ class UnusedFilesAnalyzer {

for (final context in collection.contexts) {
final unusedFilesAnalysisConfig =
await _getAnalysisConfig(context, rootFolder, config);
_getAnalysisConfig(context, rootFolder, config);

final filePaths = getFilePaths(
folders,
Expand Down Expand Up @@ -92,13 +92,13 @@ class UnusedFilesAnalyzer {
}
}

Future<UnusedFilesAnalysisConfig> _getAnalysisConfig(
UnusedFilesAnalysisConfig _getAnalysisConfig(
AnalysisContext context,
String rootFolder,
UnusedFilesConfig config,
) async {
final analysisOptions = await analysisOptionsFromContext(context) ??
await analysisOptionsFromFilePath(rootFolder);
) {
final analysisOptions = analysisOptionsFromContext(context) ??
analysisOptionsFromFilePath(rootFolder, context);

final contextConfig =
ConfigBuilder.getUnusedFilesConfigFromOption(analysisOptions)
Expand Down
Expand Up @@ -51,8 +51,8 @@ class UnusedL10nAnalyzer {
final localizationUsages = <ClassElement, Set<String>>{};

for (final context in collection.contexts) {
final analysisOptions = await analysisOptionsFromContext(context) ??
await analysisOptionsFromFilePath(rootFolder);
final analysisOptions = analysisOptionsFromContext(context) ??
analysisOptionsFromFilePath(rootFolder, context);

final contextConfig =
ConfigBuilder.getUnusedL10nConfigFromOption(analysisOptions)
Expand Down
44 changes: 31 additions & 13 deletions lib/src/config_builder/models/analysis_options.dart
@@ -1,7 +1,7 @@
import 'dart:io';
import 'dart:isolate';

import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/uri_converter.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';

Expand Down Expand Up @@ -109,28 +109,43 @@ class AnalysisOptions {
}
}

Future<AnalysisOptions?> analysisOptionsFromContext(
AnalysisOptions? analysisOptionsFromContext(
AnalysisContext context,
) async {
) {
final optionsFilePath = context.contextRoot.optionsFile?.path;

return optionsFilePath == null
? null
: analysisOptionsFromFile(File(optionsFilePath));
: analysisOptionsFromFile(File(optionsFilePath), context);
}

Future<AnalysisOptions> analysisOptionsFromFilePath(String path) {
AnalysisOptions analysisOptionsFromFilePath(
String path,
AnalysisContext context,
) {
final analysisOptionsFile = File(p.absolute(path, _analysisOptionsFileName));

return analysisOptionsFromFile(analysisOptionsFile);
return analysisOptionsFromFile(analysisOptionsFile, context);
}

Future<AnalysisOptions> analysisOptionsFromFile(File? options) async =>
AnalysisOptions analysisOptionsFromFile(
File? options,
AnalysisContext context,
) =>
options != null && options.existsSync()
? AnalysisOptions(options.path, await _loadConfigFromYamlFile(options))
? AnalysisOptions(
options.path,
_loadConfigFromYamlFile(
options,
context.currentSession.uriConverter,
),
)
: const AnalysisOptions(null, {});

Future<Map<String, Object>> _loadConfigFromYamlFile(File options) async {
Map<String, Object> _loadConfigFromYamlFile(
File options,
UriConverter converter,
) {
try {
final node = options.existsSync()
? loadYamlNode(options.readAsStringSync())
Expand All @@ -141,12 +156,15 @@ Future<Map<String, Object>> _loadConfigFromYamlFile(File options) async {

final includeNode = optionsNode['include'];
if (includeNode is String) {
final resolvedUri = includeNode.startsWith('package:')
? await Isolate.resolvePackageUri(Uri.parse(includeNode))
: Uri.file(p.absolute(p.dirname(options.path), includeNode));
final packageImport = includeNode.startsWith('package:');

final resolvedUri = packageImport
? converter.uriToPath(Uri.parse(includeNode))
: p.absolute(p.dirname(options.path), includeNode);

if (resolvedUri != null) {
final resolvedYamlMap =
await _loadConfigFromYamlFile(File.fromUri(resolvedUri));
_loadConfigFromYamlFile(File(resolvedUri), converter);
optionsNode =
mergeMaps(defaults: resolvedYamlMap, overrides: optionsNode);
}
Expand Down
4 changes: 4 additions & 0 deletions pubspec.yaml
Expand Up @@ -32,5 +32,9 @@ dev_dependencies:
mocktail: ^0.3.0
test: ^1.16.8

# internal package, used only in tests data
test_lints:
path: ./test/resources/test_lints

executables:
metrics:
1 change: 1 addition & 0 deletions test/resources/analysis_options_with_import.yaml
@@ -0,0 +1 @@
include: package:test_lints/analysis_options.yaml
3 changes: 3 additions & 0 deletions test/resources/test_lints/lib/analysis_options.1.0.0.yaml
@@ -0,0 +1,3 @@
analyzer:
plugins:
- dart_code_metrics
1 change: 1 addition & 0 deletions test/resources/test_lints/lib/analysis_options.yaml
@@ -0,0 +1 @@
include: package:test_lints/analysis_options.1.0.0.yaml
6 changes: 6 additions & 0 deletions test/resources/test_lints/pubspec.yaml
@@ -0,0 +1,6 @@
name: test_lints
description: Lints for Dart and Flutter based on software industry standards and best practices.
version: 1.0.0

environment:
sdk: ">=2.14.0 <3.0.0"
40 changes: 34 additions & 6 deletions test/src/config_builder/models/analysis_options_test.dart
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:dart_code_metrics/src/config_builder/models/analysis_options.dart';
import 'package:dart_code_metrics/src/utils/analyzer_utils.dart';
import 'package:test/test.dart';

const _options = {
Expand Down Expand Up @@ -28,23 +29,35 @@ const _options = {
};

void main() {
final collection = createAnalysisContextCollection(
['test'],
Directory.current.path,
null,
);

group('analysisOptionsFromFile constructs AnalysisOptions from', () {
test('null', () async {
final options = await analysisOptionsFromFile(null);
test('null', () {
final options = analysisOptionsFromFile(null, collection.contexts.first);

expect(options.options, isEmpty);
});

test('invalid file', () async {
final options = await analysisOptionsFromFile(File('unavailable.yaml'));
test('invalid file', () {
final options = analysisOptionsFromFile(
File('unavailable.yaml'),
collection.contexts.first,
);

expect(options.options, isEmpty);
});

test('yaml file', () async {
test('yaml file', () {
const yamlFilePath = './test/resources/analysis_options_pkg.yaml';

final options = await analysisOptionsFromFile(File(yamlFilePath));
final options = analysisOptionsFromFile(
File(yamlFilePath),
collection.contexts.first,
);

expect(options.options, contains('linter'));
expect(options.options['linter'], contains('rules'));
Expand Down Expand Up @@ -97,6 +110,21 @@ void main() {
),
);
});

test('valid file with single import', () {
const yamlFilePath = './test/resources/analysis_options_with_import.yaml';

final options = analysisOptionsFromFile(
File(yamlFilePath),
collection.contexts.first,
);

expect(options.options['analyzer'], isNotEmpty);
expect(
(options.options['analyzer'] as Map<String, Object>)['plugins'],
equals(['dart_code_metrics']),
);
});
});

group('AnalysisOptions', () {
Expand Down