blob: f32ba627d9a316bf1ea9fd015dc1f427ae5c7ad5 [file] [log] [blame]
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/utilities/extensions/file_system.dart';
import 'package:analyzer/src/workspace/basic.dart';
import 'package:analyzer/src/workspace/pub.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:linter/src/rules.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../../util/tree_string_sink.dart';
import '../resolution/context_collection_resolution.dart';
import '../resolution/node_text_expectations.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(AnalysisContextCollectionLowTest);
defineReflectiveTests(AnalysisContextCollectionTest);
defineReflectiveTests(UpdateNodeTextExpectations);
});
}
@reflectiveTest
class AnalysisContextCollectionLowTest with ResourceProviderMixin {
Folder get sdkRoot => newFolder('/sdk');
void setUp() {
createMockSdk(
resourceProvider: resourceProvider,
root: sdkRoot,
);
registerLintRules();
}
test_contextFor_noContext() {
var collection = _newCollection(includedPaths: [convertPath('/root')]);
expect(
() => collection.contextFor(convertPath('/other/test.dart')),
throwsStateError,
);
}
test_contextFor_notAbsolute() {
var collection = _newCollection(includedPaths: [convertPath('/root')]);
expect(
() => collection.contextFor(convertPath('test.dart')),
throwsArgumentError,
);
}
test_contextFor_notNormalized() {
var collection = _newCollection(includedPaths: [convertPath('/root')]);
expect(
() => collection.contextFor(convertPath('/test/lib/../lib/test.dart')),
throwsArgumentError,
);
}
test_new_analysisOptions_includes() {
var rootFolder = newFolder('/home/test');
var fooFolder = newFolder('/home/packages/foo');
newFile('${fooFolder.path}/lib/included.yaml', r'''
linter:
rules:
- empty_statements
''');
var packageConfigFileBuilder = PackageConfigFileBuilder()
..add(name: 'foo', rootPath: fooFolder.path);
newPackageConfigJsonFile(
rootFolder.path,
packageConfigFileBuilder.toContent(toUriStr: toUriStr),
);
var optionsFile = newAnalysisOptionsYamlFile(rootFolder.path, r'''
include: package:foo/included.yaml
linter:
rules:
- unnecessary_parenthesis
''');
var collection = _newCollection(includedPaths: [rootFolder.path]);
var analysisContext = collection.contextFor(rootFolder.path);
var analysisOptions =
analysisContext.getAnalysisOptionsForFile(optionsFile);
expect(
analysisOptions.lintRules.map((e) => e.name),
unorderedEquals(['empty_statements', 'unnecessary_parenthesis']),
);
}
test_new_analysisOptions_lintRules() {
var rootFolder = newFolder('/home/test');
var optionsFile = newAnalysisOptionsYamlFile(rootFolder.path, r'''
linter:
rules:
- non_existent_lint_rule
- unnecessary_parenthesis
''');
var collection = _newCollection(includedPaths: [rootFolder.path]);
var analysisContext = collection.contextFor(rootFolder.path);
var analysisOptions =
analysisContext.getAnalysisOptionsForFile(optionsFile);
expect(
analysisOptions.lintRules.map((e) => e.name),
unorderedEquals(['unnecessary_parenthesis']),
);
}
test_new_includedPaths_notAbsolute() {
expect(
() => AnalysisContextCollectionImpl(includedPaths: ['root']),
throwsArgumentError,
);
}
test_new_includedPaths_notNormalized() {
expect(
() => AnalysisContextCollectionImpl(
includedPaths: [convertPath('/root/lib/../lib')]),
throwsArgumentError,
);
}
test_new_outer_inner() {
var outerFolder = newFolder('/test/outer');
newFile('/test/outer/lib/outer.dart', '');
var innerFolder = newFolder('/test/outer/inner');
newAnalysisOptionsYamlFile('/test/outer/inner', '');
newFile('/test/outer/inner/inner.dart', '');
var collection = _newCollection(includedPaths: [outerFolder.path]);
expect(collection.contexts, hasLength(2));
var outerContext = collection.contexts
.singleWhere((c) => c.contextRoot.root == outerFolder);
var innerContext = collection.contexts
.singleWhere((c) => c.contextRoot.root == innerFolder);
expect(innerContext, isNot(same(outerContext)));
// Outer and inner contexts own corresponding files.
expect(collection.contextFor(convertPath('/test/outer/lib/outer.dart')),
same(outerContext));
expect(collection.contextFor(convertPath('/test/outer/inner/inner.dart')),
same(innerContext));
// The file does not have to exist, during creation, or at all.
expect(collection.contextFor(convertPath('/test/outer/lib/outer2.dart')),
same(outerContext));
expect(collection.contextFor(convertPath('/test/outer/inner/inner2.dart')),
same(innerContext));
}
test_new_sdkPath_notAbsolute() {
expect(
() => AnalysisContextCollectionImpl(
includedPaths: ['/root'], sdkPath: 'sdk'),
throwsArgumentError,
);
}
test_new_sdkPath_notNormalized() {
expect(
() => AnalysisContextCollectionImpl(
includedPaths: [convertPath('/root')], sdkPath: '/home/sdk/../sdk'),
throwsArgumentError,
);
}
AnalysisContextCollectionImpl _newCollection(
{required List<String> includedPaths}) {
return AnalysisContextCollectionImpl(
resourceProvider: resourceProvider,
includedPaths: includedPaths,
sdkPath: sdkRoot.path,
);
}
}
@reflectiveTest
class AnalysisContextCollectionTest with ResourceProviderMixin {
final _AnalysisContextCollectionPrinterConfiguration configuration =
_AnalysisContextCollectionPrinterConfiguration();
Folder get sdkRoot => newFolder('/sdk');
File newPackageConfigJsonFileFromBuilder(
String directoryPath,
PackageConfigFileBuilder builder,
) {
final content = builder.toContent(toUriStr: toUriStr);
return newPackageConfigJsonFile(directoryPath, content);
}
void newSinglePackageConfigJsonFile({
required String packagePath,
required String name,
}) {
final builder = PackageConfigFileBuilder()
..add(name: name, rootPath: packagePath);
newPackageConfigJsonFileFromBuilder(packagePath, builder);
}
void setUp() {
createMockSdk(
resourceProvider: resourceProvider,
root: sdkRoot,
);
registerLintRules();
}
test_basicWorkspace() async {
final workspaceRootPath = '/home';
final testPackageRootPath = '$workspaceRootPath/test';
newSinglePackageConfigJsonFile(
packagePath: testPackageRootPath,
name: 'test',
);
newFile('$testPackageRootPath/lib/a.dart', '');
_assertWorkspaceCollectionText(workspaceRootPath, r'''
contexts
/home/test
packagesFile: /home/test/.dart_tool/package_config.json
workspace: workspace_0
analyzedFiles
/home/test/lib/a.dart
uri: package:test/a.dart
workspacePackage_0_0
workspaces
workspace_0: BasicWorkspace
root: /home/test
workspacePackage_0_0
''');
}
test_pubWorkspace_multipleAnalysisOptions() async {
final workspaceRootPath = '/home';
final testPackageRootPath = '$workspaceRootPath/test';
final testPackageLibPath = '$testPackageRootPath/lib';
newPubspecYamlFile(testPackageRootPath, r'''
name: test
''');
newSinglePackageConfigJsonFile(
packagePath: testPackageRootPath,
name: 'test',
);
newAnalysisOptionsYamlFile(testPackageRootPath, '');
newFile('$testPackageLibPath/a.dart', '');
final nestedPath = '$testPackageLibPath/nested';
newAnalysisOptionsYamlFile(nestedPath, '');
newFile('$nestedPath/b.dart', '');
_assertWorkspaceCollectionText(workspaceRootPath, r'''
contexts
/home/test
optionsFile: /home/test/analysis_options.yaml
packagesFile: /home/test/.dart_tool/package_config.json
workspace: workspace_0
analyzedFiles
/home/test/lib/a.dart
uri: package:test/a.dart
workspacePackage_0_0
/home/test/lib/nested
optionsFile: /home/test/lib/nested/analysis_options.yaml
packagesFile: /home/test/.dart_tool/package_config.json
workspace: workspace_1
analyzedFiles
/home/test/lib/nested/b.dart
uri: package:test/nested/b.dart
workspacePackage_1_0
workspaces
workspace_0: PubWorkspace
root: /home/test
pubPackages
workspacePackage_0_0: PubWorkspacePackage
root: /home/test
workspace_1: PubWorkspace
root: /home/test
pubPackages
workspacePackage_1_0: PubWorkspacePackage
root: /home/test
''');
}
test_pubWorkspace_multiplePackageConfigs() async {
final workspaceRootPath = '/home';
final testPackageRootPath = '$workspaceRootPath/test';
final testPackageLibPath = '$testPackageRootPath/lib';
newPubspecYamlFile(testPackageRootPath, r'''
name: test
''');
newSinglePackageConfigJsonFile(
packagePath: testPackageRootPath,
name: 'test',
);
newFile('$testPackageLibPath/a.dart', '');
final nestedPackageRootPath = '$testPackageRootPath/nested';
newPubspecYamlFile(nestedPackageRootPath, r'''
name: nested
''');
newSinglePackageConfigJsonFile(
packagePath: nestedPackageRootPath,
name: 'nested',
);
newFile('$nestedPackageRootPath/lib/b.dart', '');
_assertWorkspaceCollectionText(workspaceRootPath, r'''
contexts
/home/test
packagesFile: /home/test/.dart_tool/package_config.json
workspace: workspace_0
analyzedFiles
/home/test/lib/a.dart
uri: package:test/a.dart
workspacePackage_0_0
/home/test/nested
packagesFile: /home/test/nested/.dart_tool/package_config.json
workspace: workspace_1
analyzedFiles
/home/test/nested/lib/b.dart
uri: package:nested/b.dart
workspacePackage_1_0
workspaces
workspace_0: PubWorkspace
root: /home/test
pubPackages
workspacePackage_0_0: PubWorkspacePackage
root: /home/test
workspace_1: PubWorkspace
root: /home/test/nested
pubPackages
workspacePackage_1_0: PubWorkspacePackage
root: /home/test/nested
''');
}
test_pubWorkspace_sdkVersionConstraint() async {
final workspaceRootPath = '/home';
final testPackageRootPath = '$workspaceRootPath/test';
newPubspecYamlFile(testPackageRootPath, r'''
environment:
sdk: ^3.0.0
''');
newSinglePackageConfigJsonFile(
packagePath: testPackageRootPath,
name: 'test',
);
newFile('$testPackageRootPath/lib/a.dart', '');
_assertWorkspaceCollectionText(workspaceRootPath, r'''
contexts
/home/test
packagesFile: /home/test/.dart_tool/package_config.json
workspace: workspace_0
analyzedFiles
/home/test/lib/a.dart
uri: package:test/a.dart
workspacePackage_0_0
workspaces
workspace_0: PubWorkspace
root: /home/test
pubPackages
workspacePackage_0_0: PubWorkspacePackage
root: /home/test
sdkVersionConstraint: ^3.0.0
''');
}
test_pubWorkspace_singleAnalysisOptions() async {
final workspaceRootPath = '/home';
final testPackageRootPath = '$workspaceRootPath/test';
final testPackageLibPath = '$testPackageRootPath/lib';
newPubspecYamlFile(testPackageRootPath, r'''
name: test
''');
newSinglePackageConfigJsonFile(
packagePath: testPackageRootPath,
name: 'test',
);
newAnalysisOptionsYamlFile(testPackageRootPath, '');
newFile('$testPackageLibPath/a.dart', '');
_assertWorkspaceCollectionText(workspaceRootPath, r'''
contexts
/home/test
optionsFile: /home/test/analysis_options.yaml
packagesFile: /home/test/.dart_tool/package_config.json
workspace: workspace_0
analyzedFiles
/home/test/lib/a.dart
uri: package:test/a.dart
workspacePackage_0_0
workspaces
workspace_0: PubWorkspace
root: /home/test
pubPackages
workspacePackage_0_0: PubWorkspacePackage
root: /home/test
''');
}
void _assertCollectionText(
AnalysisContextCollectionImpl collection,
String expected,
) {
final actual = _getContextCollectionText(collection);
if (actual != expected) {
print('-------- Actual --------');
print('$actual------------------------');
NodeTextExpectationsCollector.add(actual);
}
expect(actual, expected);
}
/// Asserts the text of a context collection created for a single included
/// workspace path, without any excludes.
void _assertWorkspaceCollectionText(
String workspaceRootPath,
String expected,
) {
final collection = AnalysisContextCollectionImpl(
resourceProvider: resourceProvider,
sdkPath: sdkRoot.path,
includedPaths: [
getFolder(workspaceRootPath).path,
],
);
_assertCollectionText(collection, expected);
}
String _getContextCollectionText(
AnalysisContextCollectionImpl contextCollection,
) {
final buffer = StringBuffer();
_AnalysisContextCollectionPrinter(
configuration: configuration,
resourceProvider: resourceProvider,
sink: TreeStringSink(sink: buffer, indent: ''),
).write(contextCollection);
return buffer.toString();
}
}
class _AnalysisContextCollectionPrinter {
final _AnalysisContextCollectionPrinterConfiguration configuration;
final ResourceProvider resourceProvider;
final TreeStringSink sink;
final Map<Workspace, (int, String)> _workspaces = Map.identity();
final Map<Workspace, Map<WorkspacePackage, String>> _workspacePackages =
Map.identity();
_AnalysisContextCollectionPrinter({
required this.configuration,
required this.resourceProvider,
required this.sink,
});
void write(AnalysisContextCollectionImpl contextCollection) {
sink.writeElements(
'contexts',
contextCollection.contexts,
_writeAnalysisContext,
);
sink.writeElements(
'workspaces',
_workspaces.keys.toList(),
_writeWorkspace,
);
}
String _idOfWorkspace(Workspace workspace) {
return _indexIdOfWorkspace(workspace).$2;
}
String _idOfWorkspacePackage(WorkspacePackage package) {
final workspace = package.workspace;
final packages = _workspacePackages[workspace] ??= Map.identity();
if (packages[package] case final id?) {
return id;
} else {
final workspaceIndex = _indexIdOfWorkspace(workspace).$1;
final id = 'workspacePackage_${workspaceIndex}_${packages.length}';
return packages[package] ??= id;
}
}
(int, String) _indexIdOfWorkspace(Workspace workspace) {
if (_workspaces[workspace] case final existing?) {
return existing;
}
final index = _workspaces.length;
final id = 'workspace_$index';
return _workspaces[workspace] = (index, id);
}
bool _isDartFile(File file) {
return file_paths.isDart(resourceProvider.pathContext, file.path);
}
void _writeAnalysisContext(DriverBasedAnalysisContext analysisContext) {
final contextRoot = analysisContext.contextRoot;
final fsState = analysisContext.driver.fsState;
final analyzedFiles = contextRoot.analyzedFiles().toList();
if (!configuration.withEmptyContextRoots && analyzedFiles.isEmpty) {
return;
}
sink.writelnWithIndent(contextRoot.root.posixPath);
sink.withIndent(() {
_writeNamedFile('optionsFile', contextRoot.optionsFile);
_writeNamedFile('packagesFile', contextRoot.packagesFile);
sink.writelnWithIndent(
'workspace: ${_idOfWorkspace(contextRoot.workspace)}',
);
sink.writeElements('analyzedFiles', analyzedFiles, (path) {
final file = resourceProvider.getFile(path);
if (_isDartFile(file)) {
_writeDartFile(fsState, file);
}
});
});
}
void _writeDartFile(FileSystemState fsState, File file) {
sink.writelnWithIndent(file.posixPath);
sink.withIndent(() {
final fileState = fsState.getFileForPath(file.path);
sink.writelnWithIndent('uri: ${fileState.uri}');
final workspacePackage = fileState.workspacePackage;
if (workspacePackage != null) {
final id = _idOfWorkspacePackage(workspacePackage);
sink.writelnWithIndent(id);
}
});
}
void _writeNamedFile(String name, File? file) {
if (file != null) {
sink.writelnWithIndent('$name: ${file.posixPath}');
}
}
void _writeWorkspace(Workspace workspace) {
final id = _idOfWorkspace(workspace);
switch (workspace) {
case BasicWorkspace():
sink.writelnWithIndent('$id: BasicWorkspace');
sink.withIndent(() {
final root = resourceProvider.getFolder(workspace.root);
sink.writelnWithIndent('root: ${root.posixPath}');
sink.writelnWithIndent(
_idOfWorkspacePackage(workspace.theOnlyPackage),
);
});
case PubWorkspace():
sink.writelnWithIndent('$id: PubWorkspace');
sink.withIndent(() {
final root = resourceProvider.getFolder(workspace.root);
sink.writelnWithIndent('root: ${root.posixPath}');
sink.writeElements(
'pubPackages',
workspace.pubPackages.toList(),
_writeWorkspacePackage,
);
});
default:
throw UnimplementedError('${workspace.runtimeType}');
}
}
void _writeWorkspacePackage(WorkspacePackage package) {
final id = _idOfWorkspacePackage(package);
switch (package) {
case BasicWorkspacePackage():
sink.writelnWithIndent('$id: BasicWorkspacePackage');
sink.withIndent(() {
final root = resourceProvider.getFolder(package.root);
sink.writelnWithIndent('root: ${root.posixPath}');
});
case PubWorkspacePackage():
sink.writelnWithIndent('$id: PubWorkspacePackage');
sink.withIndent(() {
final root = resourceProvider.getFolder(package.root);
sink.writelnWithIndent('root: ${root.posixPath}');
final sdkVersionConstraint = package.sdkVersionConstraint;
if (sdkVersionConstraint != null) {
sink.writelnWithIndent(
'sdkVersionConstraint: $sdkVersionConstraint');
}
});
default:
throw UnimplementedError('${package.runtimeType}');
}
}
}
class _AnalysisContextCollectionPrinterConfiguration {
bool withEmptyContextRoots = false;
}