analyzer: Rewrite all plugin paths to be absolute paths
Fixes https://github.com/dart-lang/sdk/issues/61477
Change-Id: I9f241d91b7a5d61e6772f18da7a399d19d344e6f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/449065
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart b/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart
index 81cc5ff..78dfcd5 100644
--- a/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart
+++ b/pkg/analyzer/lib/src/analysis_options/analysis_options_provider.dart
@@ -9,6 +9,7 @@
import 'package:analyzer/src/generated/source.dart' show SourceFactory;
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/util/yaml.dart';
+import 'package:path/path.dart' as path;
import 'package:source_span/source_span.dart';
import 'package:yaml/yaml.dart';
@@ -59,7 +60,7 @@
/// Returns an empty options map if the file does not exist or cannot be
/// parsed.
YamlMap getOptionsFromFile(File file) {
- return getOptionsFromSource(FileSource(file));
+ return getOptionsFromSource(FileSource(file), file.provider.pathContext);
}
/// Provides the options found in [source].
@@ -67,7 +68,11 @@
/// Recursively merges options referenced by any `include` directives and
/// removes any `include` directives from the resulting options map. Returns
/// an empty options map if the file does not exist or cannot be parsed.
- YamlMap getOptionsFromSource(Source source, {Set<Source>? handled}) {
+ YamlMap getOptionsFromSource(
+ Source source,
+ path.Context pathContext, {
+ Set<Source>? handled,
+ }) {
handled ??= {};
try {
var options = getOptionsFromString(_readAnalysisOptions(source));
@@ -85,16 +90,23 @@
.toList(),
_ => <String>[],
};
- var includeOptions = includes.fold(YamlMap(), (options, path) {
- var includeUri = _sourceFactory.resolveUri(source, path);
- if (includeUri == null || !handled!.add(includeUri)) {
+ var includeOptions = includes.fold(YamlMap(), (currentOptions, path) {
+ var includeSource = _sourceFactory.resolveUri(source, path);
+ if (includeSource == null || !handled!.add(includeSource)) {
// Return the existing options, unchanged.
- return options;
+ return currentOptions;
}
- return merge(
- options,
- getOptionsFromSource(includeUri, handled: handled),
+ var includedOptions = getOptionsFromSource(
+ includeSource,
+ pathContext,
+ handled: handled,
);
+ includedOptions = _rewriteRelativePaths(
+ includedOptions,
+ pathContext.dirname(includeSource.fullName),
+ pathContext,
+ );
+ return merge(currentOptions, includedOptions);
});
options = merge(includeOptions, options);
return options;
@@ -147,6 +159,39 @@
return null;
}
}
+
+ /// Walks [options] with semantic knowledge about where paths may appear in an
+ /// analysis options file, rewriting relative paths (relative to [directory])
+ /// as absolute paths.
+ ///
+ /// Namely: paths to plugins which are specified by path.
+ // TODO(srawlins): I think 'exclude' paths should be made absolute too; I
+ // believe there is an existing bug about 'include'd 'exclude' paths.
+ YamlMap _rewriteRelativePaths(
+ YamlMap options,
+ String directory,
+ path.Context pathContext,
+ ) {
+ var pluginsSection = options.valueAt('plugins');
+ if (pluginsSection is! YamlMap) return options;
+ var plugins = <String, Object>{};
+ pluginsSection.nodes.forEach((key, value) {
+ if (key is YamlScalar && value is YamlMap) {
+ var pathValue = value.valueAt('path')?.value;
+ if (pathValue is String) {
+ if (pathContext.isRelative(pathValue)) {
+ // We need to store the absolute path, before this value is used in
+ // a synthetic pub package.
+ pathValue = pathContext.join(directory, pathValue);
+ pathValue = pathContext.normalize(pathValue);
+ }
+
+ plugins[key.value as String] = {'path': pathValue};
+ }
+ }
+ });
+ return merge(options, YamlMap.wrap({'plugins': plugins}));
+ }
}
/// Thrown on options format exceptions.
diff --git a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
index 6dff455..5d41daa 100644
--- a/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
+++ b/pkg/analyzer/lib/src/dart/micro/resolve_file.dart
@@ -828,7 +828,10 @@
performance.run('getOptionsFromFile', (_) {
try {
var optionsProvider = AnalysisOptionsProvider(sourceFactory);
- optionMap = optionsProvider.getOptionsFromSource(source);
+ optionMap = optionsProvider.getOptionsFromSource(
+ source,
+ resourceProvider.pathContext,
+ );
} catch (_) {}
});
}
diff --git a/pkg/analyzer/test/src/options/options_provider_test.dart b/pkg/analyzer/test/src/options/options_provider_test.dart
index e9ad9f7..bf0410e 100644
--- a/pkg/analyzer/test/src/options/options_provider_test.dart
+++ b/pkg/analyzer/test/src/options/options_provider_test.dart
@@ -361,9 +361,37 @@
expect(options.lintRules, isNot(contains(topLevelLint)));
}
+ test_include_plugins() {
+ newFile('/project/analysis_options.yaml', '''
+plugins:
+ plugin_one:
+ path: foo/bar
+''');
+ newFile('/project/foo/analysis_options.yaml', r'''
+include: ../analysis_options.yaml
+''');
+
+ var options = _getOptionsObject('/project/foo') as AnalysisOptionsImpl;
+
+ expect(options.pluginsOptions.configurations, hasLength(1));
+ var pluginConfiguration = options.pluginsOptions.configurations.first;
+ expect(
+ pluginConfiguration.source,
+ isA<PathPluginSource>().having(
+ (e) => e.toYaml(name: 'plugin_one'),
+ 'toYaml',
+ '''
+ plugin_one:
+ path: ${convertPath('/project/foo/bar')}
+''',
+ ),
+ );
+ }
+
AnalysisOptions _getOptionsObject(String filePath) =>
AnalysisOptionsImpl.fromYaml(
optionsMap: provider.getOptions(getFolder(filePath)),
+ file: getFile(filePath),
);
}