blob: 5ca64c58351a4d49777b3468944c9bbc6d34f50c [file] [log] [blame]
// Copyright (c) 2016, 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:convert';
import 'dart:typed_data';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/source/source_resource.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/summary/summarize_ast.dart';
import 'package:analyzer/src/util/fast_uri.dart';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
/**
* [FileContentOverlay] is used to temporary override content of files.
*/
class FileContentOverlay {
final _map = <String, String>{};
/**
* Return the content of the file with the given [path], or `null` the
* overlay does not override the content of the file.
*
* The [path] must be absolute and normalized.
*/
String operator [](String path) => _map[path];
/**
* Return the new [content] of the file with the given [path].
*
* The [path] must be absolute and normalized.
*/
void operator []=(String path, String content) {
if (content == null) {
_map.remove(path);
} else {
_map[path] = content;
}
}
}
/**
* Information about a file being analyzed, explicitly or implicitly.
*
* It provides a consistent view on its properties.
*
* The properties are not guaranteed to represent the most recent state
* of the file system. To update the file to the most recent state, [refresh]
* should be called.
*/
class FileState {
final FileSystemState _fsState;
/**
* The absolute path of the file.
*/
final String path;
/**
* The absolute URI of the file.
*/
final Uri uri;
Source _source;
String _content;
String _contentHash;
LineInfo _lineInfo;
UnlinkedUnit _unlinked;
List<int> _apiSignature;
List<FileState> _importedFiles;
List<FileState> _exportedFiles;
List<FileState> _partedFiles;
List<FileState> _dependencies;
FileState(this._fsState, this.path, this.uri) {
_source = new FileSource(_fsState._resourceProvider.getFile(path), uri);
}
/**
* The unlinked API signature of the file.
*/
List<int> get apiSignature => _apiSignature;
/**
* The content of the file.
*/
String get content => _content;
/**
* The MD5 hash of the [content].
*/
String get contentHash => _contentHash;
/**
* Return information about line in the file.
*/
LineInfo get lineInfo => _lineInfo;
/**
* Return the list of all direct dependencies.
*/
List<FileState> get dependencies => _dependencies;
/**
* The list of files this file exports.
*/
List<FileState> get exportedFiles => _exportedFiles;
/**
* The list of files this file imports.
*/
List<FileState> get importedFiles => _importedFiles;
/**
* The list of files this library file references as parts.
*/
List<FileState> get partedFiles => _partedFiles;
/**
* The [Source] of the file in the [SourceFactory].
*/
Source get source => _source;
/**
* The [UnlinkedUnit] of the file.
*/
UnlinkedUnit get unlinked => _unlinked;
/**
* Read the file content and ensure that all of the file properties are
* consistent with the read content, including API signature.
*
* Return `true` if the API signature changed since the last refresh.
*/
bool refresh() {
// Read the content.
try {
_content = _fsState._contentOverlay[path];
_content ??= _fsState._resourceProvider.getFile(path).readAsStringSync();
} catch (_) {
_content = '';
// TODO(scheglov) We fail to report URI_DOES_NOT_EXIST.
// On one hand we need to provide an unlinked bundle to prevent
// analysis context from reading the file (we want it to work
// hermetically and handle one one file at a time). OTOH,
// ResynthesizerResultProvider happily reports that any source in the
// SummaryDataStore has MODIFICATION_TIME `0`. We need to return `-1`
// for missing files. Maybe add this feature to SummaryDataStore?
}
// Compute the content hash.
List<int> contentBytes = UTF8.encode(_content);
{
List<int> hashBytes = md5.convert(contentBytes).bytes;
_contentHash = hex.encode(hashBytes);
}
// Prepare the unlinked bundle key.
String unlinkedKey;
{
ApiSignature signature = new ApiSignature();
signature.addUint32List(_fsState._salt);
signature.addBytes(contentBytes);
unlinkedKey = '${signature.toHex()}.unlinked';
}
// Prepare bytes of the unlinked bundle - existing or new.
List<int> bytes;
{
bytes = _fsState._byteStore.get(unlinkedKey);
if (bytes == null) {
CompilationUnit unit =
_parse(_source, _content, _fsState._analysisOptions);
_fsState._logger.run('Create unlinked for $path', () {
UnlinkedUnitBuilder unlinkedUnit = serializeAstUnlinked(unit);
bytes = unlinkedUnit.toBuffer();
_fsState._byteStore.put(unlinkedKey, bytes);
});
}
}
// Read the unlinked bundle.
_unlinked = new UnlinkedUnit.fromBuffer(bytes);
_lineInfo = new LineInfo(_unlinked.lineStarts);
List<int> newApiSignature = _unlinked.apiSignature;
bool apiSignatureChanged = _apiSignature != null &&
!_equalByteLists(_apiSignature, newApiSignature);
_apiSignature = newApiSignature;
// Build the graph.
_importedFiles = <FileState>[];
_exportedFiles = <FileState>[];
_partedFiles = <FileState>[];
for (UnlinkedImport import in _unlinked.imports) {
if (!import.isImplicit) {
String uri = import.uri;
if (!_isDartUri(uri)) {
FileState file = _fileForRelativeUri(uri);
_importedFiles.add(file);
}
}
}
for (UnlinkedExportPublic export in _unlinked.publicNamespace.exports) {
String uri = export.uri;
if (!_isDartUri(uri)) {
FileState file = _fileForRelativeUri(uri);
_exportedFiles.add(file);
}
}
for (String uri in _unlinked.publicNamespace.parts) {
if (!_isDartUri(uri)) {
FileState file = _fileForRelativeUri(uri);
_partedFiles.add(file);
}
}
// Compute direct dependencies.
_dependencies = (new Set<FileState>()
..addAll(_importedFiles)
..addAll(_exportedFiles)
..addAll(_partedFiles))
.toList();
// Return whether the API signature changed.
return apiSignatureChanged;
}
@override
String toString() => path;
/**
* Return the [FileState] for the given [relativeUri].
*/
FileState _fileForRelativeUri(String relativeUri) {
Uri absoluteUri = resolveRelativeUri(uri, FastUri.parse(relativeUri));
String absolutePath = _fsState._sourceFactory
.resolveUri(null, absoluteUri.toString())
.fullName;
return _fsState.getFile(absolutePath, absoluteUri);
}
/**
* Return `true` if the given byte lists are equal.
*/
static bool _equalByteLists(List<int> a, List<int> b) {
if (a == null) {
return b == null;
} else if (b == null) {
return false;
}
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
static bool _isDartUri(String uri) {
return uri.startsWith('dart:');
}
/**
* Return the parsed unresolved [CompilationUnit] for the given [content].
*/
static CompilationUnit _parse(
Source source, String content, AnalysisOptions analysisOptions) {
AnalysisErrorListener errorListener = AnalysisErrorListener.NULL_LISTENER;
CharSequenceReader reader = new CharSequenceReader(content);
Scanner scanner = new Scanner(source, reader, errorListener);
scanner.scanGenericMethodComments = analysisOptions.strongMode;
Token token = scanner.tokenize();
LineInfo lineInfo = new LineInfo(scanner.lineStarts);
Parser parser = new Parser(source, errorListener);
parser.parseGenericMethodComments = analysisOptions.strongMode;
CompilationUnit unit = parser.parseCompilationUnit(token);
unit.lineInfo = lineInfo;
return unit;
}
}
/**
* Information about known file system state.
*/
class FileSystemState {
final PerformanceLog _logger;
final ResourceProvider _resourceProvider;
final ByteStore _byteStore;
final FileContentOverlay _contentOverlay;
final SourceFactory _sourceFactory;
final AnalysisOptions _analysisOptions;
final Uint32List _salt;
final Map<String, FileState> _pathToFile = <String, FileState>{};
FileSystemState(
this._logger,
this._byteStore,
this._contentOverlay,
this._resourceProvider,
this._sourceFactory,
this._analysisOptions,
this._salt);
/**
* Return the [FileState] for the give [path]. The returned file has the
* last known state since if was last refreshed.
*/
FileState getFile(String path, [Uri uri]) {
FileState file = _pathToFile[path];
if (file == null) {
uri ??= _uriForPath(path);
file = new FileState(this, path, uri);
_pathToFile[path] = file;
file.refresh();
}
return file;
}
/**
* Return the default [Uri] for the given path in [_sourceFactory].
*/
Uri _uriForPath(String path) {
Source fileSource = _resourceProvider.getFile(path).createSource();
return _sourceFactory.restoreUri(fileSource);
}
}