blob: 57506b21d241303e482fa9b124ad3ae7c2538f3c [file] [log] [blame]
// Copyright (c) 2020, 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 'dart:typed_data';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/analysis_options/analysis_options_provider.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/context/packages.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/context_root.dart';
import 'package:analyzer/src/dart/analysis/driver.dart' show ErrorEncoding;
import 'package:analyzer/src/dart/analysis/feature_set_provider.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/results.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer/src/dart/micro/analysis_context.dart';
import 'package:analyzer/src/dart/micro/library_analyzer.dart';
import 'package:analyzer/src/dart/micro/library_graph.dart';
import 'package:analyzer/src/generated/engine.dart'
show AnalysisEngine, AnalysisOptionsImpl;
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary/api_signature.dart';
import 'package:analyzer/src/summary/format.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary2/link.dart' as link2;
import 'package:analyzer/src/summary2/linked_bundle_context.dart';
import 'package:analyzer/src/summary2/linked_element_factory.dart';
import 'package:analyzer/src/summary2/reference.dart';
import 'package:analyzer/src/task/options.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:collection/collection.dart';
import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';
class FileResolver {
final PerformanceLog logger;
final ResourceProvider resourceProvider;
final MemoryByteStore byteStore;
final SourceFactory sourceFactory;
/*
* A function that returns the digest for a file as a String. The function
* returns a non null value, can return an empty string if file does
* not exist/has no contents.
*/
final String Function(String path) getFileDigest;
/**
* A function that fetches the given list of files. This function can be used
* to batch file reads in systems where file fetches are expensive.
*/
final void Function(List<String> paths) prefetchFiles;
final Workspace workspace;
/// This field gets value only during testing.
FileResolverTestView testView;
MicroAnalysisContextImpl analysisContext;
FileSystemState fsState;
_LibraryContext libraryContext;
FileResolver(this.logger, this.resourceProvider, this.byteStore,
this.sourceFactory, this.getFileDigest, this.prefetchFiles,
{@required Workspace workspace})
: this.workspace = workspace;
FeatureSet get defaultFeatureSet => FeatureSet.fromEnableFlags([]);
ErrorsResult getErrors(String path) {
_throwIfNotAbsoluteNormalizedPath(path);
return logger.run('Get errors for $path', () {
var fileContext = logger.run('Get file $path', () {
return _createFileContext(path);
});
var file = fileContext.file;
var errorsSignatureBuilder = ApiSignature();
errorsSignatureBuilder.addBytes(file.libraryCycle.signature);
errorsSignatureBuilder.addBytes(file.digest);
var errorsSignature = errorsSignatureBuilder.toByteList();
var errorsKey = file.path + '.errors';
var bytes = byteStore.get(errorsKey);
List<AnalysisError> errors;
if (bytes != null) {
var data = CiderUnitErrors.fromBuffer(bytes);
if (const ListEquality().equals(data.signature, errorsSignature)) {
errors = data.errors.map((error) {
return ErrorEncoding.decode(file.source, error);
}).toList();
}
}
if (errors == null) {
var unitResult = resolve(path);
errors = unitResult.errors;
bytes = CiderUnitErrorsBuilder(
signature: errorsSignature,
errors: errors.map((ErrorEncoding.encode)).toList(),
).toBuffer();
byteStore.put(errorsKey, bytes);
}
return ErrorsResultImpl(
libraryContext.analysisSession,
path,
file.uri,
file.lineInfo,
false, // isPart
errors,
);
});
}
String getLibraryLinkedSignature(String path) {
_throwIfNotAbsoluteNormalizedPath(path);
var fileContext = _createFileContext(path);
var file = fileContext.file;
return file.libraryCycle.signatureStr;
}
ResolvedUnitResult resolve(String path) {
_throwIfNotAbsoluteNormalizedPath(path);
return logger.run('Resolve $path', () {
var fileContext = logger.run('Get file $path', () {
return _createFileContext(path);
});
var file = fileContext.file;
libraryContext.load2(file);
testView?.addResolvedFile(path);
var errorListener = RecordingErrorListener();
var content = resourceProvider.getFile(path).readAsStringSync();
var unit = file.parse(errorListener, content);
Map<FileState, UnitAnalysisResult> results;
logger.run('Compute analysis results', () {
var libraryAnalyzer = LibraryAnalyzer(
fileContext.analysisOptions,
analysisContext.declaredVariables,
sourceFactory,
(_) => true,
// _isLibraryUri
libraryContext.analysisContext,
libraryContext.elementFactory,
libraryContext.inheritanceManager,
file,
resourceProvider,
(String path) => resourceProvider.getFile(path).readAsStringSync(),
);
results = libraryAnalyzer.analyzeSync();
});
UnitAnalysisResult fileResult = results[file];
return ResolvedUnitResultImpl(
analysisContext.currentSession,
path,
file.uri,
file.exists,
content,
unit.lineInfo,
false, // isPart
fileResult.unit,
fileResult.errors,
);
});
}
/// Make sure that [analysisContext], [fsState] and [libraryContext] are
/// compatible with the given [fileAnalysisOptions].
///
/// Specifically we check that `implicit-casts` and `strict-inference`
/// flags are the same, so the type systems would be the same.
void _createContext(AnalysisOptionsImpl fileAnalysisOptions) {
if (analysisContext != null) {
var analysisOptions = analysisContext.analysisOptions;
var analysisOptionsImpl = analysisOptions as AnalysisOptionsImpl;
if (analysisOptionsImpl.implicitCasts !=
fileAnalysisOptions.implicitCasts ||
analysisOptionsImpl.strictInference !=
fileAnalysisOptions.strictInference) {
logger.writeln(
'Reset the context, different type system affecting options.',
);
fsState = null; // TODO(scheglov) don't do this
analysisContext = null;
libraryContext = null;
}
}
var analysisOptions = AnalysisOptionsImpl()
..implicitCasts = fileAnalysisOptions.implicitCasts
..strictInference = fileAnalysisOptions.strictInference;
if (fsState == null) {
var featureSetProvider = FeatureSetProvider.build(
resourceProvider: resourceProvider,
packages: Packages.empty,
packageDefaultFeatureSet: analysisOptions.contextFeatures,
nonPackageDefaultFeatureSet: analysisOptions.nonPackageFeatureSet,
);
fsState = FileSystemState(
logger,
resourceProvider,
byteStore,
sourceFactory,
analysisOptions,
Uint32List(0), // linkedSalt
featureSetProvider,
getFileDigest,
prefetchFiles,
);
}
if (analysisContext == null) {
logger.run('Create AnalysisContext', () {
var root = ContextRootImpl(
resourceProvider,
resourceProvider.getFolder(workspace.root),
);
analysisContext = MicroAnalysisContextImpl(
this,
root,
analysisOptions,
DeclaredVariables(),
sourceFactory,
resourceProvider,
workspace: workspace,
);
});
}
if (libraryContext == null) {
libraryContext = _LibraryContext(
logger,
resourceProvider,
byteStore,
analysisContext.currentSession,
analysisContext.analysisOptions,
sourceFactory,
analysisContext.declaredVariables,
);
}
}
_FileContext _createFileContext(String path) {
var analysisOptions = _getAnalysisOptions(path);
_createContext(analysisOptions);
var file = fsState.getFileForPath(path);
return _FileContext(analysisOptions, file);
}
File _findOptionsFile(Folder folder) {
while (folder != null) {
File packagesFile =
_getFile(folder, AnalysisEngine.ANALYSIS_OPTIONS_YAML_FILE);
if (packagesFile != null) {
return packagesFile;
}
folder = folder.parent;
}
return null;
}
/// Return the analysis options.
///
/// If the [optionsFile] is not `null`, read it.
///
/// If the [workspace] is a [WorkspaceWithDefaultAnalysisOptions], get the
/// default options, if the file exists.
///
/// Otherwise, return the default options.
AnalysisOptionsImpl _getAnalysisOptions(String path) {
YamlMap optionMap;
var folder = resourceProvider.getFile(path).parent;
var optionsFile = _findOptionsFile(folder);
if (optionsFile != null) {
try {
var optionsProvider = AnalysisOptionsProvider(sourceFactory);
optionMap = optionsProvider.getOptionsFromFile(optionsFile);
} catch (e) {
// ignored
}
} else {
Source source;
if (workspace is WorkspaceWithDefaultAnalysisOptions) {
source = sourceFactory.forUri(WorkspaceWithDefaultAnalysisOptions.uri);
}
if (source != null && source.exists()) {
try {
var optionsProvider = AnalysisOptionsProvider(sourceFactory);
optionMap = optionsProvider.getOptionsFromSource(source);
} catch (e) {
// ignored
}
}
}
var options = AnalysisOptionsImpl();
if (optionMap != null) {
applyToAnalysisOptions(options, optionMap);
}
return options;
}
void _throwIfNotAbsoluteNormalizedPath(String path) {
var pathContext = resourceProvider.pathContext;
if (pathContext.normalize(path) != path) {
throw ArgumentError(
'Only normalized paths are supported: $path',
);
}
}
static File _getFile(Folder directory, String name) {
Resource resource = directory.getChild(name);
if (resource is File && resource.exists) {
return resource;
}
return null;
}
}
class FileResolverTestView {
/// The paths of files which were resolved.
///
/// The file path is added every time when it is resolved.
final List<String> resolvedFiles = [];
void addResolvedFile(String path) {
resolvedFiles.add(path);
}
}
class _FileContext {
final AnalysisOptionsImpl analysisOptions;
final FileState file;
_FileContext(this.analysisOptions, this.file);
}
class _LibraryContext {
final PerformanceLog logger;
final ResourceProvider resourceProvider;
final MemoryByteStore byteStore;
final AnalysisSession analysisSession;
AnalysisContextImpl analysisContext;
LinkedElementFactory elementFactory;
InheritanceManager3 inheritanceManager;
Set<LibraryCycle> loadedBundles = Set.identity();
_LibraryContext(
this.logger,
this.resourceProvider,
this.byteStore,
this.analysisSession,
AnalysisOptionsImpl analysisOptions,
SourceFactory sourceFactory,
DeclaredVariables declaredVariables,
) {
var synchronousSession =
SynchronousSession(analysisOptions, declaredVariables);
analysisContext = AnalysisContextImpl(
synchronousSession,
sourceFactory,
);
_createElementFactory();
}
/// Load data required to access elements of the given [targetLibrary].
void load2(FileState targetLibrary) {
var inputBundles = <LinkedNodeBundle>[];
var numCycles = 0;
var librariesTotal = 0;
var librariesLoaded = 0;
var librariesLinked = 0;
var librariesLinkedTimer = Stopwatch();
var inputsTimer = Stopwatch();
var bytesGet = 0;
var bytesPut = 0;
void loadBundle(LibraryCycle cycle) {
if (!loadedBundles.add(cycle)) return;
numCycles++;
librariesTotal += cycle.libraries.length;
cycle.directDependencies.forEach(loadBundle);
var key = cycle.cyclePathsHash;
var bytes = byteStore.get(key);
// check to see if any of the sources have changed
if (bytes != null) {
var hash = CiderLinkedLibraryCycle.fromBuffer(bytes).signature;
if (!const ListEquality().equals(hash, cycle.signature)) {
bytes = null;
}
}
if (bytes == null) {
librariesLinkedTimer.start();
inputsTimer.start();
var inputLibraries = <link2.LinkInputLibrary>[];
for (var libraryFile in cycle.libraries) {
var librarySource = libraryFile.source;
if (librarySource == null) continue;
var inputUnits = <link2.LinkInputUnit>[];
var partIndex = -1;
for (var file in libraryFile.libraryFiles) {
var isSynthetic = !file.exists;
var content = '';
try {
content = resourceProvider.getFile(file.path).readAsStringSync();
} catch (_) {}
var unit = file.parse(
AnalysisErrorListener.NULL_LISTENER,
content,
);
String partUriStr;
if (partIndex >= 0) {
partUriStr = libraryFile.unlinked2.parts[partIndex];
}
partIndex++;
inputUnits.add(
link2.LinkInputUnit(
partUriStr,
file.source,
isSynthetic,
unit,
),
);
}
inputLibraries.add(
link2.LinkInputLibrary(librarySource, inputUnits),
);
}
inputsTimer.stop();
var linkResult = link2.link(elementFactory, inputLibraries);
librariesLinked += cycle.libraries.length;
bytes = serializeBundle(cycle.signature, linkResult).toBuffer();
byteStore.put(key, bytes);
bytesPut += bytes.length;
librariesLinkedTimer.stop();
} else {
// TODO(scheglov) Take / clear parsed units in files.
bytesGet += bytes.length;
librariesLoaded += cycle.libraries.length;
}
// We are about to load dart:core, but if we have just linked it, the
// linker might have set the type provider. So, clear it, and recreate
// the element factory - it is empty anyway.
if (!elementFactory.hasDartCore) {
analysisContext.clearTypeProvider();
_createElementFactory();
}
var cBundle = CiderLinkedLibraryCycle.fromBuffer(bytes);
inputBundles.add(cBundle.bundle);
elementFactory.addBundle(
LinkedBundleContext(elementFactory, cBundle.bundle),
);
// Set informative data.
for (var libraryFile in cycle.libraries) {
for (var unitFile in libraryFile.libraryFiles) {
elementFactory.setInformativeData(
libraryFile.uriStr,
unitFile.uriStr,
unitFile.unlinked2.informativeData,
);
}
}
}
logger.run('Prepare linked bundles', () {
var libraryCycle = targetLibrary.libraryCycle;
loadBundle(libraryCycle);
logger.writeln(
'[numCycles: $numCycles]'
'[librariesTotal: $librariesTotal]'
'[librariesLoaded: $librariesLoaded]'
'[inputsTimer: ${inputsTimer.elapsedMilliseconds} ms]'
'[librariesLinked: $librariesLinked]'
'[librariesLinkedTimer: ${librariesLinkedTimer.elapsedMilliseconds} ms]'
'[bytesGet: $bytesGet][bytesPut: $bytesPut]',
);
});
// There might be a rare (and wrong) situation, when the external summaries
// already include the [targetLibrary]. When this happens, [loadBundle]
// exists without doing any work. But the type provider must be created.
_createElementFactoryTypeProvider();
inheritanceManager = InheritanceManager3();
}
void _createElementFactory() {
elementFactory = LinkedElementFactory(
analysisContext,
analysisSession,
Reference.root(),
);
}
/// Ensure that type provider is created.
void _createElementFactoryTypeProvider() {
if (analysisContext.typeProviderNonNullableByDefault == null) {
var dartCore = elementFactory.libraryOfUri('dart:core');
var dartAsync = elementFactory.libraryOfUri('dart:async');
elementFactory.createTypeProviders(dartCore, dartAsync);
}
}
static CiderLinkedLibraryCycleBuilder serializeBundle(
List<int> signature, link2.LinkResult linkResult) {
return CiderLinkedLibraryCycleBuilder(
signature: signature,
bundle: linkResult.bundle,
);
}
}