Skip to content

Commit

Permalink
Add Support for 'package:' configs (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniil Marchenko authored Jan 9, 2024
1 parent 27bc03e commit 6120a44
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 25 deletions.
9 changes: 7 additions & 2 deletions packages/custom_lint_builder/lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import 'package:custom_lint_core/src/resolver.dart';
import 'package:glob/glob.dart';
import 'package:hotreloader/hotreloader.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart' show PackageConfig;
import 'package:path/path.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:rxdart/subjects.dart';
Expand Down Expand Up @@ -235,11 +236,14 @@ class _CustomLintAnalysisConfigs {

factory _CustomLintAnalysisConfigs.from(
Pubspec pubspecForContext,
PackageConfig packageConfig,
AnalysisContext analysisContext,
CustomLintPluginClient client,
) {
final configs =
CustomLintConfigs.parse(analysisContext.contextRoot.optionsFile);
final configs = CustomLintConfigs.parse(
analysisContext.contextRoot.optionsFile,
packageConfig,
);

final activePluginsForContext = Map.fromEntries(
client._channel.registeredPlugins.entries.where(
Expand Down Expand Up @@ -386,6 +390,7 @@ class _ClientAnalyzerPlugin extends analyzer_plugin.ServerPlugin {
for (final pubspecEntry in pubspecs.entries)
pubspecEntry.key: _CustomLintAnalysisConfigs.from(
await pubspecEntry.value,
await parsePackageConfig(io.Directory.current),
pubspecEntry.key,
_client,
),
Expand Down
1 change: 1 addition & 0 deletions packages/custom_lint_builder/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies:
glob: ^2.1.1
hotreloader: ">=3.0.5 <5.0.0"
meta: ^1.7.0
package_config: ^2.1.0
path: ^1.8.0
pubspec_parse: ^1.2.0
rxdart: ^0.27.7
Expand Down
34 changes: 26 additions & 8 deletions packages/custom_lint_core/lib/src/configs.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:analyzer/file_system/file_system.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart';
import 'package:yaml/yaml.dart';

Expand All @@ -18,7 +19,10 @@ class CustomLintConfigs {

/// Decode a [CustomLintConfigs] from a file.
@internal
factory CustomLintConfigs.parse(File? analysisOptionsFile) {
factory CustomLintConfigs.parse(
File? analysisOptionsFile,
PackageConfig packageConfig,
) {
if (analysisOptionsFile == null || !analysisOptionsFile.exists) {
return CustomLintConfigs.empty;
}
Expand All @@ -35,13 +39,27 @@ class CustomLintConfigs {
final include = yaml['include'] as Object?;
var includedOptions = CustomLintConfigs.empty;
if (include is String) {
final includeAbsolutePath = absolute(
analysisOptionsFile.parent.path,
include,
);
includedOptions = CustomLintConfigs.parse(
analysisOptionsFile.provider.getFile(includeAbsolutePath),
);
final includeUri = Uri.parse(include);
String? includeAbsolutePath;

if (includeUri.scheme == 'package') {
final packageUri = packageConfig.resolve(includeUri);
includeAbsolutePath = packageUri?.toFilePath();
} else {
includeAbsolutePath = normalize(
absolute(
analysisOptionsFile.parent.path,
includeUri.toFilePath(),
),
);
}

if (includeAbsolutePath != null) {
includedOptions = CustomLintConfigs.parse(
analysisOptionsFile.provider.getFile(includeAbsolutePath),
packageConfig,
);
}
}

final customLint = yaml['custom_lint'] as Object?;
Expand Down
1 change: 1 addition & 0 deletions packages/custom_lint_core/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies:
custom_lint: 0.5.7
matcher: ^0.12.0
meta: ^1.7.0
package_config: ^2.1.0
path: ^1.8.0
pubspec_parse: ^1.2.2
source_span: ^1.8.0
Expand Down
132 changes: 120 additions & 12 deletions packages/custom_lint_core/test/configs_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import 'dart:io' as io;

import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
// ignore: implementation_imports
import 'package:custom_lint/src/package_utils.dart' show parsePackageConfig;
import 'package:custom_lint_core/custom_lint_core.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart';
import 'package:test/test.dart';

Expand All @@ -19,9 +22,54 @@ File createAnalysisOptions(String content) {
return PhysicalResourceProvider.INSTANCE.getFile(ioFile.path);
}

void main() {
Future<String> createTempProject(String projectName, String tempDirPath) async {
final projectPath = join(tempDirPath, projectName);

final dir = io.Directory(projectPath);
await dir.create();

final libPath = join(dir.path, 'lib');
await io.Directory(libPath).create();

final analysisOptionsPath = join(libPath, 'analysis_options.yaml');
await io.File(analysisOptionsPath).writeAsString('''
custom_lint:
rules:
- from_package
''');

return dir.path;
}

PackageConfig patchPackageConfig(
PackageConfig currentPackageConfig,
String packageName,
String packagePath,
) {
final patchedPackages = currentPackageConfig.packages.toList()
..add(
Package(
packageName,
Uri.file('$packagePath/'),
packageUriRoot: Uri.parse('lib/'),
),
);

final patchedPackageConfig = PackageConfig(
patchedPackages,
extraData: currentPackageConfig.extraData,
);

return patchedPackageConfig;
}

void main() async {
late File includeFile;
late CustomLintConfigs includeConfig;

final packageConfig = await parsePackageConfig(io.Directory.current);

const testPackageName = 'test_package_with_config';
setUp(() {
includeFile = createAnalysisOptions(
'''
Expand All @@ -35,7 +83,7 @@ custom_lint:
''',
);

includeConfig = CustomLintConfigs.parse(includeFile);
includeConfig = CustomLintConfigs.parse(includeFile, packageConfig);
});

test('Empty config', () {
Expand All @@ -45,13 +93,14 @@ custom_lint:

group('parse', () {
test('if file is null, defaults to empty', () {
final configs = CustomLintConfigs.parse(null);
final configs = CustomLintConfigs.parse(null, packageConfig);
expect(configs, same(CustomLintConfigs.empty));
});

test('if file does not exist, defaults to empty ', () {
final configs = CustomLintConfigs.parse(
PhysicalResourceProvider.INSTANCE.getFile('/this-does-no-exist'),
packageConfig,
);
expect(configs, same(CustomLintConfigs.empty));
});
Expand All @@ -63,7 +112,7 @@ linter:
rules:
public_member_api_docs: false
''');
final configs = CustomLintConfigs.parse(analysisOptions);
final configs = CustomLintConfigs.parse(analysisOptions, packageConfig);

expect(configs, includeConfig);
});
Expand All @@ -75,7 +124,7 @@ linter:
rules:
public_member_api_docs: false
''');
final configs = CustomLintConfigs.parse(analysisOptions);
final configs = CustomLintConfigs.parse(analysisOptions, packageConfig);

expect(configs, includeConfig);
});
Expand All @@ -89,7 +138,7 @@ linter:
custom_lint:
''');
final configs = CustomLintConfigs.parse(analysisOptions);
final configs = CustomLintConfigs.parse(analysisOptions, packageConfig);

expect(configs, includeConfig);
});
Expand All @@ -100,7 +149,7 @@ custom_lint:
rules:
- a
''');
final configs = CustomLintConfigs.parse(analysisOptions);
final configs = CustomLintConfigs.parse(analysisOptions, packageConfig);

expect(
configs.rules,
Expand All @@ -125,7 +174,7 @@ linter:
custom_lint:
enable_all_lint_rules: false
''');
final configs = CustomLintConfigs.parse(analysisOptions);
final configs = CustomLintConfigs.parse(analysisOptions, packageConfig);

expect(configs.enableAllLintRules, false);
expect(configs.rules, includeConfig.rules);
Expand All @@ -144,14 +193,70 @@ custom_lint:
rules:
- a
''');
final configs = CustomLintConfigs.parse(analysisOptions);
final configs = CustomLintConfigs.parse(analysisOptions, packageConfig);

expect(configs.enableAllLintRules, false);
expect(configs.rules, {
'a': const LintOptions.empty(enabled: true),
});
});

test('include config using package: uri', () async {
final dir = createDir();
final file = createAnalysisOptions('''
include: package:$testPackageName/analysis_options.yaml
''');

final tempProjectDir = await createTempProject(dir.path, testPackageName);

final patchedPackageConfig = patchPackageConfig(
packageConfig,
testPackageName,
tempProjectDir,
);
final configs = CustomLintConfigs.parse(file, patchedPackageConfig);

expect(configs.rules.containsKey('from_package'), true);
});

test('if package: uri is not resolved default to empty', () async {
const notExistingFileName = 'this-does-not-exist';

final file = createAnalysisOptions('''
include: package:$testPackageName/$notExistingFileName
''');
final dir = createDir();

final tempProjectDir = await createTempProject(dir.path, testPackageName);
final patchedPackageConfig = patchPackageConfig(
packageConfig,
testPackageName,
tempProjectDir,
);
final configs = CustomLintConfigs.parse(file, patchedPackageConfig);

expect(configs, same(CustomLintConfigs.empty));
});

test('if package: uri is not valid default to empty', () async {
const notExistingPackage = 'this-package-does-not-exists';

final file = createAnalysisOptions('''
include: package:$notExistingPackage/analysis_options.yaml
''');
final dir = createDir();
final tempProjectDir = await createTempProject(dir.path, testPackageName);

final patchedPackageConfig = patchPackageConfig(
packageConfig,
testPackageName,
tempProjectDir,
);
final configs = CustomLintConfigs.parse(file, patchedPackageConfig);

expect(configs, same(CustomLintConfigs.empty));
});

test('if custom_lint.rules is present, merges with "include"', () {
final analysisOptions = createAnalysisOptions('''
include: ${includeFile.path}
Expand All @@ -168,7 +273,7 @@ custom_lint:
foo: 21
- d
''');
final configs = CustomLintConfigs.parse(analysisOptions);
final configs = CustomLintConfigs.parse(analysisOptions, packageConfig);

expect(configs.enableAllLintRules, true);
expect(configs.rules, {
Expand All @@ -185,11 +290,14 @@ custom_lint:
group('Handles errors', () {
test('Defaults to empty if yaml fails to parse', () {
final configs = CustomLintConfigs.parse(
createAnalysisOptions('''
createAnalysisOptions(
'''
foo:
bar:
baz:
'''),
''',
),
packageConfig,
);
expect(configs, CustomLintConfigs.empty);
});
Expand Down
14 changes: 11 additions & 3 deletions packages/custom_lint_core/test/lint_rule_test.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import 'dart:io';

import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/error/listener.dart';
// ignore: implementation_imports
import 'package:custom_lint/src/package_utils.dart' show parsePackageConfig;
import 'package:custom_lint_core/custom_lint_core.dart';

import 'package:test/test.dart';

import 'assist_test.dart';
Expand Down Expand Up @@ -51,9 +56,10 @@ class MyLintRule extends DartLintRule {
}
}

void main() {
void main() async {
const onByDefault = TestLintRule(enabledByDefault: true);
const offByDefault = TestLintRule(enabledByDefault: false);
final packageConfig = await parsePackageConfig(Directory.current);

test('LintRule.testRun', () async {
const assist = MyLintRule();
Expand Down Expand Up @@ -107,7 +113,8 @@ custom_lint:
rules:
- test_lint
''');
final configs = CustomLintConfigs.parse(analysisOptionFile);
final configs =
CustomLintConfigs.parse(analysisOptionFile, packageConfig);

expect(onByDefault.isEnabled(configs), true);
expect(offByDefault.isEnabled(configs), true);
Expand All @@ -119,7 +126,8 @@ custom_lint:
rules:
- test_lint: false
''');
final configs = CustomLintConfigs.parse(analysisOptionFile);
final configs =
CustomLintConfigs.parse(analysisOptionFile, packageConfig);

expect(onByDefault.isEnabled(configs), false);
expect(offByDefault.isEnabled(configs), false);
Expand Down

1 comment on commit 6120a44

@vercel
Copy link

@vercel vercel bot commented on 6120a44 Jan 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.