blob: 830e013424c1e9f4af41eaa5f177c96147e026f6 [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/dart/analysis/context_root.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:glob/glob.dart';
import 'package:path/path.dart';
/// An implementation of a context root.
class ContextRootImpl implements ContextRoot {
@override
final ResourceProvider resourceProvider;
@override
final Folder root;
@override
final Workspace workspace;
@override
final List<Resource> included = [];
@override
final List<Resource> excluded = [];
/// A list of the globs for excluded files that were read from the analysis
/// options file.
List<Glob> excludedGlobs = [];
@override
File? optionsFile;
@override
File? packagesFile;
/// Initialize a newly created context root.
ContextRootImpl(this.resourceProvider, this.root, this.workspace);
@override
Iterable<String> get excludedPaths =>
excluded.map((Resource folder) => folder.path);
@override
int get hashCode => root.path.hashCode;
@override
Iterable<String> get includedPaths =>
included.map((Resource folder) => folder.path);
@override
bool operator ==(Object other) {
return other is ContextRoot && root.path == other.root.path;
}
@override
Iterable<String> analyzedFiles() sync* {
var visitedCanonicalPaths = <String>{};
for (String path in includedPaths) {
if (!_isExcluded(path)) {
Resource resource = resourceProvider.getResource(path);
if (resource is File) {
yield path;
} else if (resource is Folder) {
yield* _includedFilesInFolder(visitedCanonicalPaths, resource);
} else {
Type type = resource.runtimeType;
throw StateError('Unknown resource at path "$path" ($type)');
}
}
}
}
@override
bool isAnalyzed(String path) {
return _isIncluded(path) && !_isExcluded(path);
}
/// Return the absolute paths of all of the files that are included in the
/// given [folder].
Iterable<String> _includedFilesInFolder(
Set<String> visited,
Folder folder,
) sync* {
for (Resource resource in folder.getChildren()) {
String path = resource.path;
if (!_isExcluded(path)) {
if (resource is File) {
yield path;
} else if (resource is Folder) {
var canonicalPath = resource.resolveSymbolicLinksSync().path;
if (visited.add(canonicalPath)) {
yield* _includedFilesInFolder(visited, resource);
visited.remove(canonicalPath);
}
} else {
Type type = resource.runtimeType;
throw StateError('Unknown resource at path "$path" ($type)');
}
}
}
}
/// Return `true` if the given [path] is either the same as or inside of one
/// of the [excludedPaths].
bool _isExcluded(String path) {
Context context = resourceProvider.pathContext;
String name = context.basename(path);
if (name.startsWith('.')) {
return true;
}
for (String excludedPath in excludedPaths) {
if (context.isAbsolute(excludedPath)) {
if (path == excludedPath || context.isWithin(excludedPath, path)) {
return true;
}
} else {
// The documentation claims that [excludedPaths] only contains absolute
// paths, so we shouldn't be able to reach this point.
for (String includedPath in includedPaths) {
if (context.isWithin(
context.join(includedPath, excludedPath), path)) {
return true;
}
}
}
}
for (Glob pattern in excludedGlobs) {
if (pattern.matches(path)) {
return true;
}
}
return false;
}
/// Return `true` if the given [path] is either the same as or inside of one
/// of the [includedPaths].
bool _isIncluded(String path) {
Context context = resourceProvider.pathContext;
for (String includedPath in includedPaths) {
if (path == includedPath || context.isWithin(includedPath, path)) {
return true;
}
}
return false;
}
}