Skip to content

Search for pubspecs surrounding analysis contexts #152

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ To create a custom lint, you will need two things:
# pubspec.yaml
name: my_custom_lint_package
environment:
sdk: ">=2.16.0 <3.0.0"
sdk: ">=3.0.0 <4.0.0"

dependencies:
# we will use analyzer for inspecting Dart files
Expand Down Expand Up @@ -163,7 +163,7 @@ For users to run custom_lint packages, there are a few steps:
# The pubspec.yaml of an application using our lints
name: example_app
environment:
sdk: ">=2.16.0 <3.0.0"
sdk: ">=3.0.0 <4.0.0"

dev_dependencies:
custom_lint:
Expand Down
2 changes: 1 addition & 1 deletion packages/custom_lint/example/example_lint/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 0.0.4
publish_to: none

environment:
sdk: '>=2.16.0 <3.0.0'
sdk: '>=3.0.0 <4.0.0'

dependencies:
analyzer: ^5.0.0
Expand Down
2 changes: 1 addition & 1 deletion packages/custom_lint/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 0.0.1
publish_to: none

environment:
sdk: '>=2.16.0 <3.0.0'
sdk: '>=3.0.0 <4.0.0'

dependencies:
riverpod: ^2.0.0
Expand Down
75 changes: 75 additions & 0 deletions packages/custom_lint/lib/src/package_utils.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:io';

import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart';
import 'package:pubspec_parse/pubspec_parse.dart';
Expand Down Expand Up @@ -121,8 +122,82 @@ Future<PackageConfig?> tryParsePackageConfig(Directory directory) async {
/// does not exists.
Future<PackageConfig> parsePackageConfig(Directory directory) async {
final packageConfigFile = directory.packageConfig;

return PackageConfig.parseBytes(
await packageConfigFile.readAsBytes(),
packageConfigFile.uri,
);
}

/// Finds the project directory associated with an analysis context root, or null if it is not found
///
/// This is a folder that contains both a `pubspec.yaml` and a `.dart_tool/package_config.json` file.
/// It is either alongside the analysis_options.yaml file, or in a parent directory.
Directory? tryFindProjectDirectory(
Directory directory, {
Directory? original,
bool missingPackageConfigOkay = false,
}) {
try {
return findProjectDirectory(
directory,
original: original,
missingPackageConfigOkay: missingPackageConfigOkay,
);
} on FileSystemException {
return null;
}
}

/// Finds the project directory associated with an analysis context root
///
/// This is a folder that contains both a `pubspec.yaml` and a `.dart_tool/package_config.json` file.
/// It is either alongside the analysis_options.yaml file, or in a parent directory.
Directory findProjectDirectory(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This handles looking for a project directory which contains at minimum a pubspec next to a .dart_tool/package_config.json (and in the case of hosted dependencies in the pubcache, the package config file is not necessary).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we care about searching for the existence of a package_config.json here? Shouldn't searching only for a pubspec be enough?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure why this was resolved. I still don't get why we are looking for package_config.jsons

To begin with, there's a high chance that if there's a missing package_config, then #168 should take care of it.

Directory directory, {
Directory? original,
bool missingPackageConfigOkay = false,
}) {
final packageConfigFile = directory.packageConfig.existsSync();
final pubspecFile = directory.pubspec.existsSync();
if (packageConfigFile && pubspecFile) {
return directory;
}
if (pubspecFile) {
if (missingPackageConfigOkay) {
return directory;
}
throw PackageConfigNotFoundError._(directory.path);
}
if (directory.parent.uri == directory.uri) {
throw FindProjectError._(original?.path ?? directory.path);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd rather make the return value nullable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a tryFindProjectDirectory

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's not quite what I meant. I don't think we need a variant that throws. If we do want to throw, I'd move the exception to the place which calls findProductDirectory instead.

}
return findProjectDirectory(directory.parent, original: directory);
}

/// No pubspec.yaml file was found for a plugin.
@internal
class FindProjectError extends FileSystemException {
/// An error that represents the folder [path] where the search for the pubspec started.
FindProjectError._(String path)
: super('Failed to find dart project at $path:\n', path);

@override
String toString() => message;
}

/// No .dart_tool/package_config.json file was found for a plugin.
@internal
class PackageConfigNotFoundError extends FileSystemException {
/// The [path] where the pubspec.yaml file was found, but missing a package_config.json
PackageConfigNotFoundError._(String path)
: super(
'Failed to find .dart_tool/package_config.json at $path.\n'
'Make sure to run `pub get` first.\n'
'If "$path" is in your PUB_CACHE dir, run `dart pub cache repair`',
path,
);

@override
String toString() => message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,6 @@ class CustomLintRequestFailure implements Exception {

@override
String toString() {
return 'A request throw the exception:$message\n$stackTrace';
return 'A request threw the exception:$message\n$stackTrace';
}
}
27 changes: 16 additions & 11 deletions packages/custom_lint/lib/src/workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,13 @@ class CustomLintWorkspace {

final contextRootsWithCustomLint = await Future.wait(
allContextRoots.map((contextRoot) async {
final pubspecFile = Directory(contextRoot.root.path).pubspec;
if (!pubspecFile.existsSync()) {
return null;
}
final projectDir = tryFindProjectDirectory(
Directory(contextRoot.root.path),
);
if (projectDir == null) return null;

final pubspec = await tryParsePubspec(projectDir);
if (pubspec == null) return null;

final optionFile = contextRoot.optionsFile;
if (optionFile == null) {
Expand Down Expand Up @@ -384,7 +387,8 @@ class CustomLintPluginCheckerCache {
if (cached != null) return cached;

return _cache[directory] = Future(() async {
final pubspec = await parsePubspec(directory);
final pubspec = await tryParsePubspec(directory);
if (pubspec == null) return false;

// TODO test that dependency_overrides & dev_dependencies aren't checked.
return pubspec.dependencies.containsKey('custom_lint_builder');
Expand Down Expand Up @@ -502,19 +506,20 @@ class CustomLintProject {
CustomLintPluginCheckerCache cache,
) async {
final directory = Directory(contextRoot.root);

final projectPubspec = await parsePubspec(directory)
final projectDirectory = findProjectDirectory(directory);
final projectPubspec = await parsePubspec(projectDirectory).catchError(
// ignore: avoid_types_on_closure_parameters
.catchError((Object err, StackTrace stack) {
(Object err, StackTrace stack) {
throw PubspecParseError._(
directory.path,
error: err,
errorStackTrace: stack,
);
});
final projectPackageConfig = await parsePackageConfig(directory)
// ignore: avoid_types_on_closure_parameters
.catchError((Object err, StackTrace stack) {
final projectPackageConfig =
await parsePackageConfig(projectDirectory).catchError(
// ignore: avoid_types_on_closure_parameters
(Object err, StackTrace stack) {
throw PackageConfigParseError._(
directory.path,
error: err,
Expand Down
2 changes: 1 addition & 1 deletion packages/custom_lint/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repository: https://github.com/invertase/dart_custom_lint
issue_tracker: https://github.com/invertase/dart_custom_lint/issues

environment:
sdk: ">=2.19.0 <3.0.0"
sdk: ">=3.0.0 <4.0.0"

dependencies:
analyzer: ^5.7.0
Expand Down
9 changes: 6 additions & 3 deletions packages/custom_lint/test/cli_process_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,11 @@ void main() {
expect(
trimDependencyOverridesWarning(process.stderr),
startsWith(
'Failed to decode .dart_tool/package_config.json at $missingPackageConfig. '
'The request analysis.setContextRoots failed with the following error:\n'
'RequestErrorCode.PLUGIN_ERROR\n'
'A request threw the exception:Failed to find .dart_tool/package_config.json at $missingPackageConfig.\n'
'Make sure to run `pub get` first.\n'
'PathNotFoundException: Cannot open file, path =',
'If "$missingPackageConfig" is in your PUB_CACHE dir, run `dart pub cache repair`\n',
),
);
expect(process.stdout, isEmpty);
Expand All @@ -212,7 +214,8 @@ void main() {
'dependency conflict',
() async {
// Create two packages with the same name but different paths
final workspace = await createSimpleWorkspace(['dep', 'dep']);
final workspace =
await createSimpleWorkspace(['dep', 'dep'], local: true);

final plugin = createPlugin(
parent: workspace,
Expand Down
4 changes: 2 additions & 2 deletions packages/custom_lint/test/create_project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ version: 0.0.1
publish_to: none

environment:
sdk: ">=2.17.0 <4.0.0"
sdk: ">=3.0.0 <4.0.0"

dependencies:
analyzer: any
Expand Down Expand Up @@ -216,7 +216,7 @@ version: 0.0.1
publish_to: none

environment:
sdk: ">=2.17.0 <4.0.0"
sdk: ">=3.0.0 <4.0.0"

dependencies:
analyzer: any
Expand Down
86 changes: 71 additions & 15 deletions packages/custom_lint/test/src/workspace_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ void writeSimplePackageConfig(
Future<Directory> createSimpleWorkspace(
List<Object> projectEntry, {
bool withPackageConfig = true,
bool local = false,
}) async {
/// The number of time we've created a package with a given name.
final packageCount = <String, int>{};
Expand All @@ -182,7 +183,7 @@ Future<Directory> createSimpleWorkspace(
return folderName;
}

return createWorkspace(withPackageConfig: withPackageConfig, {
return createWorkspace(local: local, withPackageConfig: withPackageConfig, {
for (final projectEntry in projectEntry)
if (projectEntry is Pubspec)
getFolderName(projectEntry.name): projectEntry
Expand All @@ -208,8 +209,10 @@ Future<Directory> createSimpleWorkspace(
Future<Directory> createWorkspace(
Map<String, Pubspec> pubspecs, {
bool withPackageConfig = true,
bool withNestedAnalysisOptions = false,
bool local = false,
}) async {
final dir = createTemporaryDirectory();
final dir = createTemporaryDirectory(local: local);

String packagePathOf(Dependency dependency, String name) {
if (dependency is HostedDependency) {
Expand Down Expand Up @@ -262,10 +265,16 @@ Future<Directory> createWorkspace(
return dir;
}

Directory createTemporaryDirectory() {
final dir = Directory.current //
.dir('.dart_tool')
.createTempSync('custom_lint_test');
Directory createTemporaryDirectory({bool local = false}) {
final Directory dir;
if (local) {
// The cli_process_test needs it to be local in order for the relative paths to match
dir =
Directory.current.dir('.dart_tool').createTempSync('custom_lint_test');
} else {
// Others need global directory in order to not pick up this project's package_config.json
dir = Directory.systemTemp.createTempSync('custom_lint_test');
}
addTearDown(() => dir.deleteSync(recursive: true));

// Watches process kill to delete the temporary directory.
Expand Down Expand Up @@ -724,25 +733,55 @@ void main() {
);
}

test('throws MissingPubspecError if package does not contain a pubspec',
test(
'finds pubspecs above analysis options file if there exists one',
() async {
final workspace = await createSimpleWorkspace(['package']);

final analysisFile = File(
p.join(workspace.path, 'package', 'analysis_options.yaml'),
);
analysisFile.createSync();
analysisFile.writeAsStringSync(analysisOptionsWithCustomLintEnabled);
final nestedAnalysisFile = File(
p.join(workspace.path, 'package', 'test', 'analysis_options.yaml'),
);
nestedAnalysisFile.createSync(recursive: true);
nestedAnalysisFile
.writeAsStringSync(analysisOptionsWithCustomLintEnabled);

final customLintWorkspace = await CustomLintWorkspace.fromPaths(
[p.join(workspace.path, 'package')],
workingDirectory: workspace,
);
// Expect one context root for the workspace and one for the test folder
expect(customLintWorkspace.contextRoots.length, equals(2));
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you remove those trailing commas and format again? Tests typically don't use them

);

test(
'throws PackageConfigNotFoundError if package has a pubspec but no .dart_tool/package_config.json',
() async {
final workspace = await createSimpleWorkspace([]);
workspace.dir('package').createSync(recursive: true);
final workspace = await createSimpleWorkspace(['package']);
workspace.dir('package', '.dart_tool').deleteSync(recursive: true);

expect(
() => fromContextRootsFromPaths(
[p.join(workspace.path, 'package')],
workingDirectory: workspace,
),
throwsA(isA<PubspecParseError>()),
throwsA(isA<PackageConfigNotFoundError>()),
);
});

test(
'throws MissingPackageConfigError if package has a pubspec but no .dart_tool/package_config.json',
'throws PackageConfigParseError if package has a malformed .dart_tool/package_config.json',
() async {
final workspace = await createSimpleWorkspace(['package']);
workspace.dir('package', '.dart_tool').deleteSync(recursive: true);
workspace
.dir('package', '.dart_tool')
.file('package_config.json')
.writeAsStringSync('malformed');

expect(
() => fromContextRootsFromPaths(
Expand All @@ -753,6 +792,23 @@ void main() {
);
});

test('throws PubspecParseError if package has a malformed pubspec.yaml',
() async {
final workspace = await createSimpleWorkspace(['package']);
workspace
.dir('package')
.file('pubspec.yaml')
.writeAsStringSync('malformed');

expect(
() => fromContextRootsFromPaths(
[p.join(workspace.path, 'package')],
workingDirectory: workspace,
),
throwsA(isA<PubspecParseError>()),
);
});

test('Supports empty workspace', () async {
final customLintWorkspace = await fromContextRootsFromPaths(
[],
Expand Down Expand Up @@ -2019,8 +2075,8 @@ dart pub upgrade dep
'app2',
devDependencies: {
'dep': HostedDependency(
version: VersionConstraint.parse('>=2.0.0 <3.0.0'),
),
version: VersionConstraint.parse('>=3.0.0 <4.0.0'),
)
},
),
]);
Expand Down Expand Up @@ -2062,7 +2118,7 @@ Plugin dep:
- Hosted with version constraint: ^1.0.0
Resolved with ${app.plugins.single.package.root.path}
Used by project "app" at "app"
- Hosted with version constraint: >=2.0.0 <3.0.0
- Hosted with version constraint: >=3.0.0 <4.0.0
Resolved with ${app2.plugins.single.package.root.path}
Used by project "app2" at "app2"

Expand Down
Loading