From ecfc0cb22157fcac7caaa814f770303f4d9a8eb3 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Mon, 11 Nov 2024 10:33:34 -0800 Subject: [PATCH] Support a list of include paths in analysis options (#1594) Support a list of include paths in analysis options The implementation is fairly simple. If the include value in an analysis options file is a List, then iteratively merge included options files. Fixes #1591. --- .../analysis_options_file.dart | 40 +++++++++++++++---- .../analysis_options_file_test.dart | 16 +++++++- test/utils.dart | 14 +++++-- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/lib/src/analysis_options/analysis_options_file.dart b/lib/src/analysis_options/analysis_options_file.dart index 7b7475c7..bb774af1 100644 --- a/lib/src/analysis_options/analysis_options_file.dart +++ b/lib/src/analysis_options/analysis_options_file.dart @@ -68,11 +68,7 @@ Future readAnalysisOptions( // Lower the YAML to a regular map. var options = {...yaml}; - // If there is an `include:` key, then load that and merge it with these - // options. - if (options['include'] case String include) { - options.remove('include'); - + Future> optionsFromInclude(String include) async { // If the include path is "package:", resolve it to a file path first. var includeUri = Uri.tryParse(include); if (includeUri != null && includeUri.scheme == 'package') { @@ -95,9 +91,39 @@ Future readAnalysisOptions( // options file. var includePath = await fileSystem.join( (await fileSystem.parentDirectory(optionsPath))!, include); - var includeFile = await readAnalysisOptions(fileSystem, includePath, + return await readAnalysisOptions(fileSystem, includePath, resolvePackageUri: resolvePackageUri); - options = merge(includeFile, options) as AnalysisOptions; + } + + // If there is an `include:` key with a String value, then load that and merge + // it with these options. If there is an `include:` key with a List value, + // then load each value, merging successive included options, overriding + // previous results with each set of included options, finally merging with + // these options. + switch (options['include']) { + case String include: + options.remove('include'); + var includeOptions = await optionsFromInclude(include); + options = merge(includeOptions, options) as AnalysisOptions; + case List includeList: + options.remove('include'); + var mergedIncludeOptions = AnalysisOptions(); + for (var include in includeList) { + if (include is! String) { + throw PackageResolutionException( + 'Unsupported "include" value in analysis options include list: ' + '"$include".'); + } + var includeOptions = await optionsFromInclude(include); + mergedIncludeOptions = + merge(mergedIncludeOptions, includeOptions) as AnalysisOptions; + } + options = merge(mergedIncludeOptions, options) as AnalysisOptions; + case null: + break; + case Object include: + throw PackageResolutionException( + 'Unsupported "include" value in analysis options: "$include".'); } return options; diff --git a/test/analysis_options/analysis_options_file_test.dart b/test/analysis_options/analysis_options_file_test.dart index dde5ce05..cda0a81b 100644 --- a/test/analysis_options/analysis_options_file_test.dart +++ b/test/analysis_options/analysis_options_file_test.dart @@ -77,18 +77,29 @@ void main() { 'ab': 'from a', 'ac': 'from a', 'abc': 'from a', + 'ad': 'from a', }), - 'dir|b.yaml': analysisOptions(include: 'c.yaml', other: { + 'dir|b.yaml': analysisOptions(include: [ + 'c.yaml', + 'd.yaml' + ], other: { 'ab': 'from b', 'abc': 'from b', 'b': 'from b', 'bc': 'from b', + 'bd': 'from b', }), 'dir|c.yaml': analysisOptions(other: { 'ac': 'from c', 'abc': 'from c', 'bc': 'from c', 'c': 'from c', + 'cd': 'from c', + }), + 'dir|d.yaml': analysisOptions(other: { + 'ad': 'from d', + 'bd': 'from d', + 'cd': 'from d', }), }); @@ -97,10 +108,13 @@ void main() { expect(options['a'], 'from a'); expect(options['ab'], 'from a'); expect(options['ac'], 'from a'); + expect(options['ad'], 'from a'); expect(options['abc'], 'from a'); expect(options['b'], 'from b'); expect(options['bc'], 'from b'); + expect(options['bd'], 'from b'); expect(options['c'], 'from c'); + expect(options['cd'], 'from d'); }); test('removes the include key after merging', () async { diff --git a/test/utils.dart b/test/utils.dart index 9a4f3782..828f899d 100644 --- a/test/utils.dart +++ b/test/utils.dart @@ -200,11 +200,19 @@ d.DirectoryDescriptor packageConfig(String rootPackageName, /// to include another analysis options file. If [other] is given, then those /// are added as other top-level keys in the YAML. String analysisOptions( - {int? pageWidth, String? include, Map? other}) { + {int? pageWidth, + Object? /* String | List */ include, + Map? other}) { var yaml = StringBuffer(); - if (include != null) { - yaml.writeln('include: $include'); + switch (include) { + case String _: + yaml.writeln('include: $include'); + case List _: + yaml.writeln('include:'); + for (var path in include) { + yaml.writeln(' - $path'); + } } if (pageWidth != null) {