// 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/context/builder.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer_cli/src/options.dart'; import 'package:path/path.dart' as path; /// Cache of [AnalysisOptionsImpl] objects that correspond to directories /// with analyzed files, used to reduce searching for `analysis_options.yaml` /// files. class ContextCache { final ResourceProvider resourceProvider; final CommandLineOptions clOptions; final void Function(String) verbosePrint; /// A mapping from normalized paths (currently, directories) to cache entries /// which include builder, analysis_options paths, and [AnalysisOptionImpl]s. final Map _byDirectory = {}; ContextCache(this.resourceProvider, this.clOptions, this.verbosePrint); /// Look up info about a context from the cache. You can pass in any [path], /// and it will try to provide an existing [ContextCacheEntry] that is /// suitable for that [path] if one exists. ContextCacheEntry forSource(String path) { path = _normalizeSourcePath(path); return _byDirectory.putIfAbsent(path, () { final builder = new ContextBuilder(resourceProvider, null, null, options: clOptions.contextBuilderOptions); return new ContextCacheEntry(builder, path, clOptions, verbosePrint); }); } /// Cheaply normalize source paths so we can increase cache performance. /// Getting the location of an analysis_options.yaml file for a given source /// can be expensive, so we want to reduce extra lookups where possible. We /// know that two files in the same directory share an analysis options file, /// so that's the normalization we perform currently. However, this could be /// any form of performance-increasing cache key normalization. String _normalizeSourcePath(String sourcePath) { if (!resourceProvider.pathContext.isAbsolute(sourcePath)) { // TODO(mfairhurst) Use resourceProvider.pathContext.absolute(). For the // moment, we get an issues where pathContext.current is `.`, which causes // pathContext.absolute() to produce `./foo` instead of `/absolute/foo`. sourcePath = path.absolute(sourcePath); } sourcePath = resourceProvider.pathContext.normalize(sourcePath); // Prepare the directory which is, or contains, the context root. if (resourceProvider.getFolder(sourcePath).exists) { return sourcePath; } return resourceProvider.pathContext.dirname(sourcePath); } } /// Each entry of the [ContextCache] caches three things: the [ContextBuilder], /// the analysis_options.yaml path of the context, and the [AnalysisOptionsImpl] /// of the context. class ContextCacheEntry { final CommandLineOptions clOptions; final ContextBuilder builder; final String requestedSourceDirectory; final void Function(String) verbosePrint; AnalysisOptionsImpl _analysisOptions; String _analysisRoot; ContextCacheEntry(this.builder, this.requestedSourceDirectory, this.clOptions, this.verbosePrint); /// Get the fully parsed [AnalysisOptionsImpl] for this entry. AnalysisOptionsImpl get analysisOptions => _analysisOptions ??= _getAnalysisOptions(); /// Find the root path from which excludes should be considered due to where /// the analysis_options.yaml was defined. String get analysisRoot => _analysisRoot ??= _getAnalysisRoot(); /// The actual calculation to get the [AnalysisOptionsImpl], with no caching. /// This should not be used except behind the getter which caches this result /// automatically. AnalysisOptionsImpl _getAnalysisOptions() { AnalysisOptionsImpl contextOptions = builder.getAnalysisOptions( requestedSourceDirectory, verbosePrint: clOptions.verbose ? verbosePrint : null) as AnalysisOptionsImpl; contextOptions.trackCacheDependencies = false; contextOptions.disableCacheFlushing = clOptions.disableCacheFlushing; contextOptions.enabledExperiments = clOptions.enabledExperiments; contextOptions.hint = !clOptions.disableHints; contextOptions.generateImplicitErrors = clOptions.showPackageWarnings; contextOptions.generateSdkErrors = clOptions.showSdkWarnings; contextOptions.useFastaParser = clOptions.useFastaParser; return contextOptions; } /// The actual calculation to get the analysis root, with no caching. This /// should not be used except behind the getter which caches this result /// automatically. String _getAnalysisRoot() { // The analysis yaml defines the root, if it exists. var analysisOptionsPath = builder .getOptionsFile(requestedSourceDirectory, forceSearch: true) ?.path; if (analysisOptionsPath == null) { return requestedSourceDirectory; } return path.dirname(analysisOptionsPath); } }