blob: 06eda4118707ec03dc570753726c47c9c9008d4a [file]
// Copyright (c) 2017, 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:async';
import 'dart:collection';
import 'dart:math' as math;
import 'package:analysis_server/src/status/diagnostics.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analysis_server/src/status/utilities/library_cycle_extensions.dart';
import 'package:analysis_server/src/status/utilities/string_extensions.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/source.dart';
import 'package:analyzer/src/dart/analysis/driver.dart' as analysis;
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/library_graph.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/source/package_map_resolver.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer/src/workspace/pub.dart';
import 'package:analyzer/src/workspace/workspace.dart';
import 'package:path/path.dart' as path;
class ContextsPage extends DiagnosticPageWithNav {
ContextsPage(DiagnosticsSite site)
: super(
site,
'contexts',
'Contexts',
description:
'An analysis context defines a set of sources for which URIs are '
'all resolved in the same way.',
);
@override
String get navDetail => '${server.driverMap.length}';
@override
Future<void> generateContent(Map<String, String> params) async {
var driverMap = SplayTreeMap.of(
server.driverMap,
(a, b) => a.path.compareTo(b.path),
);
if (driverMap.isEmpty) {
blankslate('No contexts.');
return;
}
var (folder: folder, driver: driver) = _currentContext(params, driverMap);
var contextPath = folder.path;
buf.writeln('<div class="tabnav">');
buf.writeln('<nav class="tabnav-tabs">');
var driverFolders = driverMap.keys;
for (var f in driverFolders) {
var selectedClass = f == folder ? 'selected' : '';
var href = '${this.path}?context=${Uri.encodeQueryComponent(f.path)}';
buf.writeln(
'<a href="${escape(href)}" class="tabnav-tab $selectedClass" title="${escape(f.path)}">${escape(f.shortName)}</a>',
);
}
buf.writeln('</nav>');
buf.writeln('</div>');
buf.writeln(formatOption('Context location', escape(contextPath)));
buf.writeln(
formatOption('SDK root', escape(driver.analysisContext?.sdkRoot?.path)),
);
h3('Analysis options');
// Display analysis options entries inside this context root.
var separator = folder.provider.pathContext.separator;
var innerContextFolders = driverFolders
.where((e) => e.path.startsWith('$contextPath$separator'))
.toList();
var foldersInContextRoot = driver.analysisOptionsMap.folders.where((e) {
if (contextPath == e.path) return true;
// Exclude analysis options in inner folders.
if (innerContextFolders.any(
(f) => e.path == f.path || e.path.startsWith('${f.path}$separator'),
)) {
return false;
}
return e.path.startsWith('$contextPath$separator');
});
ul(foldersInContextRoot, (folder) {
buf.write(escape(folder.path));
buf.write('$separator<wbr>');
var optionsPath = path.join(folder.path, file_paths.analysisOptionsYaml);
buf.writeln(
formatContentsLink(optionsPath, file_paths.analysisOptionsYaml),
);
}, classes: 'scroll-table');
h3('Workspace');
var workspace = driver.analysisContext!.contextRoot.workspace;
buf.writeln('<p>');
buf.writeln(formatOption('Workspace root', escape(workspace.root)));
var workspaceFolder = folder.provider.getFolder(workspace.root);
void writePackage(WorkspacePackageImpl package) {
buf.writeln(formatOption('Package root', escape(package.root.path)));
if (package is PubPackage) {
buf.write('pubspec file: ');
buf.write(escape(workspaceFolder.path));
buf.write('$separator<wbr>');
var pubspecPath = workspaceFolder
.getChildAssumingFile(file_paths.pubspecYaml)
.path;
buf.writeln(formatContentsLink(pubspecPath, file_paths.pubspecYaml));
}
}
var packageConfig = workspaceFolder
.getChildAssumingFolder(file_paths.dotDartTool)
.getChildAssumingFile(file_paths.packageConfigJson);
buf.writeln(
formatOption('Has package_config.json file', packageConfig.exists),
);
String lenCounter(int length) {
return '<span class="counter" style="float: right;">$length</span>';
}
if (workspace is PackageConfigWorkspace) {
var packages = workspace.allPackages;
h4('Packages ${lenCounter(packages.length)}', raw: true);
ul(packages, writePackage, classes: 'scroll-table');
}
buf.writeln('</p>');
buf.writeln('</div>');
h3('Plugins');
var optionsData = collectOptionsData(driver);
p(optionsData.plugins.toList().join(', '));
var priorityFiles = driver.priorityFiles;
var addedFiles = driver.addedFiles.toList();
var knownFiles = driver.knownFiles.map((f) => f.path).toSet();
var implicitFiles = knownFiles.difference(driver.addedFiles).toList();
addedFiles.sort();
implicitFiles.sort();
h3('Context files');
void writeFile(String file) {
var astPath = '/ast?file=${Uri.encodeQueryComponent(file)}';
var elementPath = '/element-model?file=${Uri.encodeQueryComponent(file)}';
var contentsPath = '/contents?file=${Uri.encodeQueryComponent(file)}';
var hasOverlay = server.resourceProvider.hasOverlay(file);
buf.write(file.wordBreakOnSlashes);
buf.writeln(' <a href="$astPath">ast</a>');
buf.writeln(' <a href="$elementPath">element</a>');
buf.writeln(
' <a href="$contentsPath">contents${hasOverlay ? '*' : ''}</a>',
);
}
h4('Priority files ${lenCounter(priorityFiles.length)}', raw: true);
ul(priorityFiles, writeFile, classes: 'scroll-table');
h4('Added files ${lenCounter(addedFiles.length)}', raw: true);
ul(addedFiles, writeFile, classes: 'scroll-table');
h4('Implicit files ${lenCounter(implicitFiles.length)}', raw: true);
ul(implicitFiles, writeFile, classes: 'scroll-table');
var sourceFactory = driver.sourceFactory;
if (sourceFactory is SourceFactoryImpl) {
h3('Resolvers');
for (var resolver in sourceFactory.resolvers) {
h4(resolver.runtimeType.toString());
buf.write('<p class="scroll-table">');
if (resolver is DartUriResolver) {
var sdk = resolver.dartSdk;
buf.write(' (sdk = ');
buf.write(sdk.runtimeType);
if (sdk is FolderBasedDartSdk) {
buf.write(' (path = ');
buf.write(sdk.directory.path);
buf.write(')');
} else if (sdk is EmbedderSdk) {
buf.write(' (map = ');
writeMap(sdk.urlMappings);
buf.write(')');
}
buf.write(')');
} else if (resolver is PackageMapUriResolver) {
writeMap(resolver.packageMap);
} else if (resolver is PackageConfigPackageUriResolver) {
writeMap(resolver.packageMap);
}
buf.write('</p>');
}
}
h3('Dartdoc template info');
var info = driver.dartdocDirectiveInfo;
buf.write('<p class="scroll-table">');
writeMap(info.templateMap);
buf.write('</p>');
h3('Largest library cycles');
Set<LibraryCycle> cycles = {};
var contextRoot = driver.analysisContext!.contextRoot;
var pathContext = contextRoot.resourceProvider.pathContext;
for (var filePath in contextRoot.analyzedFiles()) {
if (!file_paths.isDart(pathContext, filePath)) continue;
var fileState = driver.fsState.getFileForPath(filePath);
var kind = fileState.kind;
if (kind is LibraryFileKind) {
cycles.add(kind.libraryCycle);
}
}
var sortedMultiLibraryCycles =
cycles.where((cycle) => cycle.size > 1).toList()
..sort((first, second) => second.size - first.size);
var cyclesToDisplay = math.min(sortedMultiLibraryCycles.length, 10);
var initialPathLength = contextRoot.root.path.length + 1;
buf.write(
'A library cycle is a '
'<a href="https://en.wikipedia.org/wiki/Cycle_(graph_theory)">cycle</a> '
'in the import/export graph. If library <em>a</em> imports library '
'<em>b</em> and library <em>b</em> imports library <em>a</em>, they are '
'in the same cycle. If library <em>a</em> imports library <em>b</em> and '
'exports library <em>c</em>, and library <em>b</em> imports library '
'<em>a</em>, then all three are in the same cycle. In some cases, '
'library cycles are statically analyzed as a unit; reducing the size of '
'the largest library cycles may result in faster incremental analysis.',
);
buf.write('<p>There are ${cycles.length} library cycles. ');
if (cyclesToDisplay < 10) {
buf.write('$cyclesToDisplay of these have more than one library.');
if (cyclesToDisplay > 0) {
buf.write(' They contain');
}
buf.write('</p>');
} else {
buf.write('The $cyclesToDisplay largest contain</p>');
}
buf.write('<ul>');
for (var i = 0; i < cyclesToDisplay; i++) {
var cycle = sortedMultiLibraryCycles[i];
var libraries = cycle.libraries;
var cycleSize = cycle.size;
var libraryCount = math.min(cycleSize, 8);
buf.write('<li>$cycleSize libraries, including');
buf.write('<ul>');
for (var j = 0; j < libraryCount; j++) {
var library = libraries[j];
buf.write('<li>');
buf.write(library.file.path.substring(initialPathLength));
buf.write('</li>');
}
if (cycleSize > libraryCount) {
buf.write('<li>${cycleSize - libraryCount} more...</li>');
}
buf.write('</ul>');
buf.write('</li>');
}
buf.write('</ul>');
}
void writeList<E>(List<E> list) {
buf.writeln('[${list.join(', ')}]');
}
void writeMap<V>(Map<String, V> map) {
var keys = map.keys.toList();
keys.sort();
var length = keys.length;
buf.write('{');
for (var i = 0; i < length; i++) {
buf.write('<br>');
var key = keys[i];
var value = map[key];
buf.write(key);
buf.write(' = ');
if (value is List) {
writeList(value);
} else {
buf.write(value);
}
buf.write(',');
}
buf.write('<br>}');
}
/// Information regarding the context currently being displayed.
({Folder folder, analysis.AnalysisDriver driver}) _currentContext(
Map<String, String> params,
Map<Folder, analysis.AnalysisDriver> driverMap,
) {
var contextPath = params['context'];
if (contextPath == null) {
return (
folder: driverMap.entries.first.key,
driver: driverMap.entries.first.value,
);
} else {
var entry = driverMap.entries.firstWhere(
(e) => e.key.path == contextPath,
orElse: () => driverMap.entries.first,
);
return (folder: entry.key, driver: entry.value);
}
}
}