| // Copyright (c) 2015, 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. |
| |
| library analyzer_cli.src.package_analyzer; |
| |
| import 'dart:convert'; |
| import 'dart:core' hide Resource; |
| import 'dart:io' as io; |
| |
| import 'package:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/file_system/physical_file_system.dart'; |
| import 'package:analyzer/source/package_map_resolver.dart'; |
| import 'package:analyzer/src/context/cache.dart'; |
| import 'package:analyzer/src/context/context.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/error.dart'; |
| import 'package:analyzer/src/generated/resolver.dart'; |
| import 'package:analyzer/src/generated/sdk_io.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/summary/format.dart'; |
| import 'package:analyzer/src/summary/idl.dart'; |
| import 'package:analyzer/src/summary/resynthesize.dart'; |
| import 'package:analyzer/src/summary/summarize_elements.dart'; |
| import 'package:analyzer/src/summary/summary_sdk.dart'; |
| import 'package:analyzer/src/task/dart.dart'; |
| import 'package:analyzer/task/dart.dart'; |
| import 'package:analyzer/task/model.dart'; |
| import 'package:analyzer_cli/src/analyzer_impl.dart'; |
| import 'package:analyzer_cli/src/driver.dart'; |
| import 'package:analyzer_cli/src/error_formatter.dart'; |
| import 'package:analyzer_cli/src/options.dart'; |
| import 'package:crypto/crypto.dart'; |
| import 'package:path/path.dart' as pathos; |
| |
| /** |
| * If [uri] has the `package` scheme in form of `package:pkg/file.dart`, |
| * return the `pkg` name. Otherwise return `null`. |
| */ |
| String getPackageName(Uri uri) { |
| if (uri.scheme != 'package') { |
| return null; |
| } |
| String path = uri.path; |
| int index = path.indexOf('/'); |
| if (index == -1) { |
| return null; |
| } |
| return path.substring(0, index); |
| } |
| |
| /** |
| * A concrete resynthesizer that serves summaries from given file paths. |
| */ |
| class FileBasedSummaryResynthesizer extends SummaryResynthesizer { |
| final Map<String, UnlinkedUnit> unlinkedMap = <String, UnlinkedUnit>{}; |
| final Map<String, LinkedLibrary> linkedMap = <String, LinkedLibrary>{}; |
| |
| FileBasedSummaryResynthesizer( |
| SummaryResynthesizer parent, |
| AnalysisContext context, |
| TypeProvider typeProvider, |
| SourceFactory sourceFactory, |
| bool strongMode, |
| List<String> summaryPaths) |
| : super(parent, context, typeProvider, sourceFactory, strongMode) { |
| summaryPaths.forEach(_fillMaps); |
| } |
| |
| @override |
| LinkedLibrary getLinkedSummary(String uri) { |
| return linkedMap[uri]; |
| } |
| |
| @override |
| UnlinkedUnit getUnlinkedSummary(String uri) { |
| return unlinkedMap[uri]; |
| } |
| |
| @override |
| bool hasLibrarySummary(String uri) { |
| return linkedMap.containsKey(uri); |
| } |
| |
| void _fillMaps(String path) { |
| io.File file = new io.File(path); |
| List<int> buffer = file.readAsBytesSync(); |
| PackageBundle bundle = new PackageBundle.fromBuffer(buffer); |
| for (int i = 0; i < bundle.unlinkedUnitUris.length; i++) { |
| unlinkedMap[bundle.unlinkedUnitUris[i]] = bundle.unlinkedUnits[i]; |
| } |
| for (int i = 0; i < bundle.linkedLibraryUris.length; i++) { |
| linkedMap[bundle.linkedLibraryUris[i]] = bundle.linkedLibraries[i]; |
| } |
| } |
| } |
| |
| /** |
| * The [ResultProvider] that provides results from input package summaries. |
| */ |
| class InputPackagesResultProvider extends ResultProvider { |
| final InternalAnalysisContext context; |
| final Map<String, String> packageSummaryInputs; |
| |
| FileBasedSummaryResynthesizer resynthesizer; |
| SummaryResultProvider sdkProvider; |
| |
| InputPackagesResultProvider(this.context, this.packageSummaryInputs) { |
| InternalAnalysisContext sdkContext = context.sourceFactory.dartSdk.context; |
| sdkProvider = sdkContext.resultProvider; |
| // Set the type provider to prevent the context from computing it. |
| context.typeProvider = sdkContext.typeProvider; |
| // Create a chained resynthesizer. |
| resynthesizer = new FileBasedSummaryResynthesizer( |
| sdkProvider.resynthesizer, |
| context, |
| context.typeProvider, |
| context.sourceFactory, |
| context.analysisOptions.strongMode, |
| packageSummaryInputs.values.toList()); |
| } |
| |
| bool compute(CacheEntry entry, ResultDescriptor result) { |
| if (sdkProvider.compute(entry, result)) { |
| return true; |
| } |
| AnalysisTarget target = entry.target; |
| // Only library results are supported for now. |
| if (target is Source) { |
| Uri uri = target.uri; |
| // We know how to server results to input packages. |
| String sourcePackageName = getPackageName(uri); |
| if (!packageSummaryInputs.containsKey(sourcePackageName)) { |
| return false; |
| } |
| // Provide known results. |
| String uriString = uri.toString(); |
| if (result == LIBRARY_ELEMENT1 || |
| result == LIBRARY_ELEMENT2 || |
| result == LIBRARY_ELEMENT3 || |
| result == LIBRARY_ELEMENT4 || |
| result == LIBRARY_ELEMENT5 || |
| result == LIBRARY_ELEMENT6 || |
| result == LIBRARY_ELEMENT7 || |
| result == LIBRARY_ELEMENT8 || |
| result == LIBRARY_ELEMENT || |
| false) { |
| LibraryElement libraryElement = |
| resynthesizer.getLibraryElement(uriString); |
| entry.setValue(result, libraryElement, TargetedResult.EMPTY_LIST); |
| return true; |
| } else if (result == READY_LIBRARY_ELEMENT2 || |
| result == READY_LIBRARY_ELEMENT5 || |
| result == READY_LIBRARY_ELEMENT6) { |
| entry.setValue(result, true, TargetedResult.EMPTY_LIST); |
| return true; |
| } else if (result == SOURCE_KIND) { |
| if (resynthesizer.linkedMap.containsKey(uriString)) { |
| entry.setValue(result, SourceKind.LIBRARY, TargetedResult.EMPTY_LIST); |
| return true; |
| } |
| if (resynthesizer.unlinkedMap.containsKey(uriString)) { |
| entry.setValue(result, SourceKind.PART, TargetedResult.EMPTY_LIST); |
| return true; |
| } |
| return false; |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * The [UriResolver] that knows about sources that are parts of packages which |
| * are served from their summaries. |
| */ |
| class InSummaryPackageUriResolver extends UriResolver { |
| final Map<String, String> packageSummaryInputs; |
| |
| InSummaryPackageUriResolver(this.packageSummaryInputs); |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| actualUri ??= uri; |
| String packageName = getPackageName(actualUri); |
| if (packageSummaryInputs.containsKey(packageName)) { |
| return new InSummarySource(actualUri); |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * A placeholder of a source that is part of a package whose analysis results |
| * are served from its summary. This source uses its URI as [fullName] and has |
| * empty contents. |
| */ |
| class InSummarySource extends Source { |
| final Uri uri; |
| |
| InSummarySource(this.uri); |
| |
| @override |
| TimestampedData<String> get contents => new TimestampedData<String>(0, ''); |
| |
| @override |
| String get encoding => uri.toString(); |
| |
| @override |
| String get fullName => encoding; |
| |
| @override |
| bool get isInSystemLibrary => false; |
| |
| @override |
| int get modificationStamp => 0; |
| |
| @override |
| String get shortName => pathos.basename(fullName); |
| |
| @override |
| UriKind get uriKind => UriKind.PACKAGE_URI; |
| |
| @override |
| bool exists() => true; |
| |
| @override |
| Uri resolveRelativeUri(Uri relativeUri) { |
| Uri baseUri = uri; |
| return baseUri.resolveUri(relativeUri); |
| } |
| |
| @override |
| String toString() => uri.toString(); |
| } |
| |
| /** |
| * The hermetic whole package analyzer. |
| */ |
| class PackageAnalyzer { |
| final CommandLineOptions options; |
| |
| String packagePath; |
| String packageLibPath; |
| |
| final ResourceProvider resourceProvider = PhysicalResourceProvider.INSTANCE; |
| InternalAnalysisContext context; |
| final List<Source> explicitSources = <Source>[]; |
| |
| final List<String> linkedLibraryUris = <String>[]; |
| final List<LinkedLibraryBuilder> linkedLibraries = <LinkedLibraryBuilder>[]; |
| final List<String> unlinkedUnitUris = <String>[]; |
| final List<UnlinkedUnitBuilder> unlinkedUnits = <UnlinkedUnitBuilder>[]; |
| final List<String> unlinkedUnitHashes = <String>[]; |
| |
| PackageAnalyzer(this.options); |
| |
| /** |
| * Perform package analysis according to the given [options]. |
| */ |
| ErrorSeverity analyze() { |
| packagePath = options.packageModePath; |
| packageLibPath = resourceProvider.pathContext.join(packagePath, 'lib'); |
| if (packageLibPath == null) { |
| errorSink.writeln('--package-mode-path must be set to the root ' |
| 'folder of the package to analyze.'); |
| io.exitCode = ErrorSeverity.ERROR.ordinal; |
| return ErrorSeverity.ERROR; |
| } |
| |
| // Write the progress message. |
| if (!options.machineFormat) { |
| outSink.writeln("Analyzing sources ${options.sourceFiles}..."); |
| } |
| |
| // Prepare the analysis context. |
| _createContext(); |
| |
| // Add sources. |
| ChangeSet changeSet = new ChangeSet(); |
| for (String path in options.sourceFiles) { |
| if (AnalysisEngine.isDartFileName(path)) { |
| path = resourceProvider.pathContext.absolute(path); |
| File file = resourceProvider.getFile(path); |
| if (!file.exists) { |
| errorSink.writeln('File not found: $path'); |
| io.exitCode = ErrorSeverity.ERROR.ordinal; |
| return ErrorSeverity.ERROR; |
| } |
| Source source = _createSourceInContext(file); |
| explicitSources.add(source); |
| changeSet.addedSource(source); |
| } |
| } |
| context.applyChanges(changeSet); |
| |
| // Perform full analysis. |
| while (true) { |
| AnalysisResult analysisResult = context.performAnalysisTask(); |
| if (!analysisResult.hasMoreWork) { |
| break; |
| } |
| } |
| |
| // Write summary for Dart libraries. |
| if (options.packageSummaryOutput != null) { |
| for (Source source in context.librarySources) { |
| if (pathos.isWithin(packageLibPath, source.fullName)) { |
| LibraryElement libraryElement = context.getLibraryElement(source); |
| if (libraryElement != null) { |
| _serializeSingleLibrary(libraryElement); |
| } |
| } |
| } |
| // Write the whole package bundle. |
| PackageBundleBuilder sdkBundle = new PackageBundleBuilder( |
| linkedLibraryUris: linkedLibraryUris, |
| linkedLibraries: linkedLibraries, |
| unlinkedUnitUris: unlinkedUnitUris, |
| unlinkedUnits: unlinkedUnits, |
| unlinkedUnitHashes: unlinkedUnitHashes); |
| io.File file = new io.File(options.packageSummaryOutput); |
| file.writeAsBytesSync(sdkBundle.toBuffer(), mode: io.FileMode.WRITE_ONLY); |
| } |
| |
| // Process errors. |
| _printErrors(); |
| return _computeMaxSeverity(); |
| } |
| |
| ErrorSeverity _computeMaxSeverity() { |
| ErrorSeverity maxSeverity = ErrorSeverity.NONE; |
| for (Source source in explicitSources) { |
| AnalysisErrorInfo errorInfo = context.getErrors(source); |
| for (AnalysisError error in errorInfo.errors) { |
| ProcessedSeverity processedSeverity = |
| AnalyzerImpl.processError(error, options, context); |
| if (processedSeverity != null) { |
| maxSeverity = maxSeverity.max(processedSeverity.severity); |
| } |
| } |
| } |
| return maxSeverity; |
| } |
| |
| void _createContext() { |
| DirectoryBasedDartSdk sdk = DirectoryBasedDartSdk.defaultSdk; |
| sdk.useSummary = true; |
| |
| // Create the context. |
| context = AnalysisEngine.instance.createAnalysisContext(); |
| context.typeProvider = sdk.context.typeProvider; |
| context.sourceFactory = new SourceFactory(<UriResolver>[ |
| new DartUriResolver(sdk), |
| new InSummaryPackageUriResolver(options.packageSummaryInputs), |
| new PackageMapUriResolver(resourceProvider, <String, List<Folder>>{ |
| options.packageName: <Folder>[ |
| resourceProvider.getFolder(packageLibPath) |
| ], |
| }), |
| new FileUriResolver() |
| ]); |
| |
| // Set context options. |
| Driver.setAnalysisContextOptions( |
| context, options, (AnalysisOptionsImpl contextOptions) {}); |
| |
| // Set the result provider. |
| context.resultProvider = |
| new InputPackagesResultProvider(context, options.packageSummaryInputs); |
| } |
| |
| /** |
| * Create and return a source representing the given [file]. |
| */ |
| Source _createSourceInContext(File file) { |
| Source source = file.createSource(); |
| if (context == null) { |
| return source; |
| } |
| Uri uri = context.sourceFactory.restoreUri(source); |
| return file.createSource(uri); |
| } |
| |
| /** |
| * Compute a hash of the given file contents. |
| */ |
| String _hash(String contents) { |
| MD5 md5 = new MD5(); |
| md5.add(UTF8.encode(contents)); |
| return CryptoUtils.bytesToHex(md5.close()); |
| } |
| |
| /** |
| * Print errors for all explicit sources. |
| */ |
| void _printErrors() { |
| StringSink sink = options.machineFormat ? errorSink : outSink; |
| ErrorFormatter formatter = new ErrorFormatter( |
| sink, |
| options, |
| (AnalysisError error) => |
| AnalyzerImpl.processError(error, options, context)); |
| for (Source source in explicitSources) { |
| AnalysisErrorInfo errorInfo = context.getErrors(source); |
| formatter.formatErrors([errorInfo]); |
| } |
| } |
| |
| /** |
| * Serialize the library with the given [element]. |
| */ |
| void _serializeSingleLibrary(LibraryElement element) { |
| String uri = element.source.uri.toString(); |
| LibrarySerializationResult libraryResult = |
| serializeLibrary(element, context.typeProvider, options.strongMode); |
| linkedLibraryUris.add(uri); |
| linkedLibraries.add(libraryResult.linked); |
| unlinkedUnitUris.addAll(libraryResult.unitUris); |
| unlinkedUnits.addAll(libraryResult.unlinkedUnits); |
| for (Source source in libraryResult.unitSources) { |
| unlinkedUnitHashes.add(_hash(source.contents.data)); |
| } |
| } |
| } |