Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(255)

Side by Side Diff: pkg/analyzer_cli/lib/src/driver.dart

Issue 1459683003: `analyzer_cli` move to SDK. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: master merge Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « pkg/analyzer_cli/lib/src/bootloader.dart ('k') | pkg/analyzer_cli/lib/src/error_formatter.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library analyzer_cli.src.driver;
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:io';
10
11 import 'package:analyzer/file_system/file_system.dart' as fileSystem;
12 import 'package:analyzer/file_system/physical_file_system.dart';
13 import 'package:analyzer/plugin/options.dart';
14 import 'package:analyzer/source/analysis_options_provider.dart';
15 import 'package:analyzer/source/package_map_provider.dart';
16 import 'package:analyzer/source/package_map_resolver.dart';
17 import 'package:analyzer/source/pub_package_map_provider.dart';
18 import 'package:analyzer/source/sdk_ext.dart';
19 import 'package:analyzer/src/generated/constant.dart';
20 import 'package:analyzer/src/generated/engine.dart';
21 import 'package:analyzer/src/generated/error.dart';
22 import 'package:analyzer/src/generated/interner.dart';
23 import 'package:analyzer/src/generated/java_engine.dart';
24 import 'package:analyzer/src/generated/java_io.dart';
25 import 'package:analyzer/src/generated/sdk_io.dart';
26 import 'package:analyzer/src/generated/source.dart';
27 import 'package:analyzer/src/generated/source_io.dart';
28 import 'package:analyzer/src/services/lint.dart';
29 import 'package:analyzer/src/task/options.dart';
30 import 'package:analyzer_cli/src/analyzer_impl.dart';
31 import 'package:analyzer_cli/src/options.dart';
32 import 'package:linter/src/plugin/linter_plugin.dart';
33 import 'package:package_config/discovery.dart' as pkgDiscovery;
34 import 'package:package_config/packages.dart' show Packages;
35 import 'package:package_config/packages_file.dart' as pkgfile show parse;
36 import 'package:package_config/src/packages_impl.dart' show MapPackages;
37 import 'package:path/path.dart' as path;
38 import 'package:plugin/plugin.dart';
39 import 'package:yaml/yaml.dart';
40
41 /// The maximum number of sources for which AST structures should be kept in the
42 /// cache.
43 const int _maxCacheSize = 512;
44
45 /// Shared IO sink for standard error reporting.
46 ///
47 /// *Visible for testing.*
48 StringSink errorSink = stderr;
49
50 /// Shared IO sink for standard out reporting.
51 ///
52 /// *Visible for testing.*
53 StringSink outSink = stdout;
54
55 /// Test this option map to see if it specifies lint rules.
56 bool containsLintRuleEntry(Map<String, YamlNode> options) {
57 var linterNode = options['linter'];
58 return linterNode is YamlMap && linterNode.containsKey('rules');
59 }
60
61 typedef ErrorSeverity _BatchRunnerHandler(List<String> args);
62
63 class Driver {
64 /// The plugins that are defined outside the `analyzer_cli` package.
65 List<Plugin> _userDefinedPlugins = <Plugin>[];
66
67 /// Indicates whether the analyzer is running in batch mode.
68 bool _isBatch;
69
70 /// The context that was most recently created by a call to [_analyzeAll], or
71 /// `null` if [_analyzeAll] hasn't been called yet.
72 AnalysisContext _context;
73
74 /// If [_context] is not `null`, the [CommandLineOptions] that guided its
75 /// creation.
76 CommandLineOptions _previousOptions;
77
78 /// This Driver's current analysis context.
79 ///
80 /// *Visible for testing.*
81 AnalysisContext get context => _context;
82
83 /// Set the [plugins] that are defined outside the `analyzer_cli` package.
84 void set userDefinedPlugins(List<Plugin> plugins) {
85 _userDefinedPlugins = plugins == null ? <Plugin>[] : plugins;
86 }
87
88 /// Use the given command-line [args] to start this analysis driver.
89 void start(List<String> args) {
90 StringUtilities.INTERNER = new MappedInterner();
91
92 _processPlugins();
93
94 // Parse commandline options.
95 CommandLineOptions options = CommandLineOptions.parse(args);
96
97 // Cache options of interest to inform analysis.
98 _setupEnv(options);
99
100 // Do analysis.
101 if (_isBatch) {
102 _BatchRunner.runAsBatch(args, (List<String> args) {
103 CommandLineOptions options = CommandLineOptions.parse(args);
104 return _analyzeAll(options);
105 });
106 } else {
107 ErrorSeverity severity = _analyzeAll(options);
108 // In case of error propagate exit code.
109 if (severity == ErrorSeverity.ERROR) {
110 exitCode = severity.ordinal;
111 }
112 }
113 }
114
115 /// Perform analysis according to the given [options].
116 ErrorSeverity _analyzeAll(CommandLineOptions options) {
117 if (!options.machineFormat) {
118 outSink.writeln("Analyzing ${options.sourceFiles}...");
119 }
120
121 // Create a context, or re-use the previous one.
122 try {
123 _createAnalysisContext(options);
124 } on _DriverError catch (error) {
125 outSink.writeln(error.msg);
126 return ErrorSeverity.ERROR;
127 }
128
129 // Add all the files to be analyzed en masse to the context. Skip any
130 // files that were added earlier (whether explicitly or implicitly) to
131 // avoid causing those files to be unnecessarily re-read.
132 Set<Source> knownSources = _context.sources.toSet();
133 List<Source> sourcesToAnalyze = <Source>[];
134 ChangeSet changeSet = new ChangeSet();
135 for (String sourcePath in options.sourceFiles) {
136 sourcePath = sourcePath.trim();
137 // Check that file exists.
138 if (!new File(sourcePath).existsSync()) {
139 errorSink.writeln('File not found: $sourcePath');
140 exitCode = ErrorSeverity.ERROR.ordinal;
141 //Fail fast; don't analyze more files
142 return ErrorSeverity.ERROR;
143 }
144 // Check that file is Dart file.
145 if (!AnalysisEngine.isDartFileName(sourcePath)) {
146 errorSink.writeln('$sourcePath is not a Dart file');
147 exitCode = ErrorSeverity.ERROR.ordinal;
148 // Fail fast; don't analyze more files.
149 return ErrorSeverity.ERROR;
150 }
151 Source source = _computeLibrarySource(sourcePath);
152 if (!knownSources.contains(source)) {
153 changeSet.addedSource(source);
154 }
155 sourcesToAnalyze.add(source);
156 }
157 _context.applyChanges(changeSet);
158
159 // Analyze the libraries.
160 ErrorSeverity allResult = ErrorSeverity.NONE;
161 var libUris = <Uri>[];
162 var parts = <Source>[];
163 for (Source source in sourcesToAnalyze) {
164 if (context.computeKindOf(source) == SourceKind.PART) {
165 parts.add(source);
166 continue;
167 }
168 ErrorSeverity status = _runAnalyzer(source, options);
169 allResult = allResult.max(status);
170 libUris.add(source.uri);
171 }
172
173 // Check that each part has a corresponding source in the input list.
174 for (Source part in parts) {
175 bool found = false;
176 for (var lib in context.getLibrariesContaining(part)) {
177 if (libUris.contains(lib.uri)) {
178 found = true;
179 }
180 }
181 if (!found) {
182 errorSink.writeln("${part.fullName} is a part and cannot be analyzed.");
183 errorSink.writeln("Please pass in a library that contains this part.");
184 exitCode = ErrorSeverity.ERROR.ordinal;
185 allResult = allResult.max(ErrorSeverity.ERROR);
186 }
187 }
188
189 return allResult;
190 }
191
192 /// Determine whether the context created during a previous call to
193 /// [_analyzeAll] can be re-used in order to analyze using [options].
194 bool _canContextBeReused(CommandLineOptions options) {
195 // TODO(paulberry): add a command-line option that disables context re-use.
196 if (_context == null) {
197 return false;
198 }
199 if (options.packageRootPath != _previousOptions.packageRootPath) {
200 return false;
201 }
202 if (options.packageConfigPath != _previousOptions.packageConfigPath) {
203 return false;
204 }
205 if (!_equalMaps(
206 options.definedVariables, _previousOptions.definedVariables)) {
207 return false;
208 }
209 if (options.log != _previousOptions.log) {
210 return false;
211 }
212 if (options.disableHints != _previousOptions.disableHints) {
213 return false;
214 }
215 if (options.enableStrictCallChecks !=
216 _previousOptions.enableStrictCallChecks) {
217 return false;
218 }
219 if (options.showPackageWarnings != _previousOptions.showPackageWarnings) {
220 return false;
221 }
222 if (options.showSdkWarnings != _previousOptions.showSdkWarnings) {
223 return false;
224 }
225 if (options.lints != _previousOptions.lints) {
226 return false;
227 }
228 if (options.strongMode != _previousOptions.strongMode) {
229 return false;
230 }
231 if (options.enableSuperMixins != _previousOptions.enableSuperMixins) {
232 return false;
233 }
234 return true;
235 }
236
237 /// Decide on the appropriate policy for which files need to be fully parsed
238 /// and which files need to be diet parsed, based on [options], and return an
239 /// [AnalyzeFunctionBodiesPredicate] that implements this policy.
240 AnalyzeFunctionBodiesPredicate _chooseDietParsingPolicy(
241 CommandLineOptions options) {
242 if (_isBatch) {
243 // As analyzer is currently implemented, once a file has been diet
244 // parsed, it can't easily be un-diet parsed without creating a brand new
245 // context and losing caching. In batch mode, we can't predict which
246 // files we'll need to generate errors and warnings for in the future, so
247 // we can't safely diet parse anything.
248 return (Source source) => true;
249 }
250
251 // Determine the set of packages requiring a full parse. Use null to
252 // represent the case where all packages require a full parse.
253 Set<String> packagesRequiringFullParse;
254 if (options.showPackageWarnings) {
255 // We are showing warnings from all packages so all packages require a
256 // full parse.
257 packagesRequiringFullParse = null;
258 } else {
259 // We aren't showing warnings for dependent packages, but we may still
260 // need to show warnings for "self" packages, so we need to do a full
261 // parse in any package containing files mentioned on the command line.
262 // TODO(paulberry): implement this. As a temporary workaround, we're
263 // fully parsing all packages.
264 packagesRequiringFullParse = null;
265 }
266 return (Source source) {
267 if (source.uri.scheme == 'dart') {
268 return options.showSdkWarnings;
269 } else if (source.uri.scheme == 'package') {
270 if (packagesRequiringFullParse == null) {
271 return true;
272 } else if (source.uri.pathSegments.length == 0) {
273 // We should never see a URI like this, but fully parse it to be
274 // safe.
275 return true;
276 } else {
277 return packagesRequiringFullParse
278 .contains(source.uri.pathSegments[0]);
279 }
280 } else {
281 return true;
282 }
283 };
284 }
285
286 /// Decide on the appropriate method for resolving URIs based on the given
287 /// [options] and [customUrlMappings] settings, and return a
288 /// [SourceFactory] that has been configured accordingly.
289 SourceFactory _chooseUriResolutionPolicy(CommandLineOptions options) {
290 Packages packages;
291 Map<String, List<fileSystem.Folder>> packageMap;
292 UriResolver packageUriResolver;
293
294 // Process options, caching package resolution details.
295 if (options.packageConfigPath != null) {
296 String packageConfigPath = options.packageConfigPath;
297 Uri fileUri = new Uri.file(packageConfigPath);
298 try {
299 File configFile = new File.fromUri(fileUri).absolute;
300 List<int> bytes = configFile.readAsBytesSync();
301 Map<String, Uri> map = pkgfile.parse(bytes, configFile.uri);
302 packages = new MapPackages(map);
303 packageMap = _getPackageMap(packages);
304 } catch (e) {
305 printAndFail(
306 'Unable to read package config data from $packageConfigPath: $e');
307 }
308 } else if (options.packageRootPath != null) {
309 packageMap = _PackageRootPackageMapBuilder
310 .buildPackageMap(options.packageRootPath);
311
312 JavaFile packageDirectory = new JavaFile(options.packageRootPath);
313 packageUriResolver = new PackageUriResolver([packageDirectory]);
314 } else {
315 fileSystem.Resource cwd =
316 PhysicalResourceProvider.INSTANCE.getResource('.');
317
318 // Look for .packages.
319 packages = _discoverPackagespec(new Uri.directory(cwd.path));
320
321 if (packages != null) {
322 packageMap = _getPackageMap(packages);
323 } else {
324 // Fall back to pub list-package-dirs.
325
326 PubPackageMapProvider pubPackageMapProvider =
327 new PubPackageMapProvider(PhysicalResourceProvider.INSTANCE, sdk);
328 PackageMapInfo packageMapInfo =
329 pubPackageMapProvider.computePackageMap(cwd);
330 packageMap = packageMapInfo.packageMap;
331
332 // Only create a packageUriResolver if pub list-package-dirs succeeded.
333 // If it failed, that's not a problem; it simply means we have no way
334 // to resolve packages.
335 if (packageMapInfo.packageMap != null) {
336 packageUriResolver = new PackageMapUriResolver(
337 PhysicalResourceProvider.INSTANCE, packageMap);
338 }
339 }
340 }
341
342 // Now, build our resolver list.
343
344 // 'dart:' URIs come first.
345 List<UriResolver> resolvers = [new DartUriResolver(sdk)];
346
347 // Next SdkExts.
348 if (packageMap != null) {
349 resolvers.add(new SdkExtUriResolver(packageMap));
350 }
351
352 // Then package URIs.
353 if (packageUriResolver != null) {
354 resolvers.add(packageUriResolver);
355 }
356
357 // Finally files.
358 resolvers.add(new FileUriResolver());
359
360 return new SourceFactory(resolvers, packages);
361 }
362
363 /// Convert the given [sourcePath] (which may be relative to the current
364 /// working directory) to a [Source] object that can be fed to the analysis
365 /// context.
366 Source _computeLibrarySource(String sourcePath) {
367 sourcePath = _normalizeSourcePath(sourcePath);
368 JavaFile sourceFile = new JavaFile(sourcePath);
369 Source source = sdk.fromFileUri(sourceFile.toURI());
370 if (source != null) {
371 return source;
372 }
373 source = new FileBasedSource(sourceFile, sourceFile.toURI());
374 Uri uri = _context.sourceFactory.restoreUri(source);
375 if (uri == null) {
376 return source;
377 }
378 return new FileBasedSource(sourceFile, uri);
379 }
380
381 /// Create an analysis context that is prepared to analyze sources according
382 /// to the given [options], and store it in [_context].
383 void _createAnalysisContext(CommandLineOptions options) {
384 if (_canContextBeReused(options)) {
385 return;
386 }
387 _previousOptions = options;
388 // Choose a package resolution policy and a diet parsing policy based on
389 // the command-line options.
390 SourceFactory sourceFactory = _chooseUriResolutionPolicy(options);
391 AnalyzeFunctionBodiesPredicate dietParsingPolicy =
392 _chooseDietParsingPolicy(options);
393 // Create a context using these policies.
394 AnalysisContext context = AnalysisEngine.instance.createAnalysisContext();
395
396 context.sourceFactory = sourceFactory;
397
398 Map<String, String> definedVariables = options.definedVariables;
399 if (!definedVariables.isEmpty) {
400 DeclaredVariables declaredVariables = context.declaredVariables;
401 definedVariables.forEach((String variableName, String value) {
402 declaredVariables.define(variableName, value);
403 });
404 }
405
406 if (options.log) {
407 AnalysisEngine.instance.logger = new StdLogger();
408 }
409
410 // Set context options.
411 AnalysisOptionsImpl contextOptions = new AnalysisOptionsImpl();
412 contextOptions.cacheSize = _maxCacheSize;
413 contextOptions.hint = !options.disableHints;
414 contextOptions.enableStrictCallChecks = options.enableStrictCallChecks;
415 contextOptions.enableSuperMixins = options.enableSuperMixins;
416 contextOptions.analyzeFunctionBodiesPredicate = dietParsingPolicy;
417 contextOptions.generateImplicitErrors = options.showPackageWarnings;
418 contextOptions.generateSdkErrors = options.showSdkWarnings;
419 contextOptions.lint = options.lints;
420 contextOptions.strongMode = options.strongMode;
421 context.analysisOptions = contextOptions;
422 _context = context;
423
424 // Process analysis options file (and notify all interested parties).
425 _processAnalysisOptions(options, context);
426 }
427
428 /// Return discovered packagespec, or `null` if none is found.
429 Packages _discoverPackagespec(Uri root) {
430 try {
431 Packages packages = pkgDiscovery.findPackagesFromFile(root);
432 if (packages != Packages.noPackages) {
433 return packages;
434 }
435 } catch (_) {
436 // Ignore and fall through to null.
437 }
438
439 return null;
440 }
441
442 fileSystem.File _getOptionsFile(CommandLineOptions options) {
443 fileSystem.File file;
444 String filePath = options.analysisOptionsFile;
445 if (filePath != null) {
446 file = PhysicalResourceProvider.INSTANCE.getFile(filePath);
447 if (!file.exists) {
448 printAndFail('Options file not found: $filePath',
449 exitCode: ErrorSeverity.ERROR.ordinal);
450 }
451 } else {
452 filePath = AnalysisEngine.ANALYSIS_OPTIONS_FILE;
453 file = PhysicalResourceProvider.INSTANCE.getFile(filePath);
454 }
455 return file;
456 }
457
458 Map<String, List<fileSystem.Folder>> _getPackageMap(Packages packages) {
459 if (packages == null) {
460 return null;
461 }
462
463 Map<String, List<fileSystem.Folder>> folderMap =
464 new Map<String, List<fileSystem.Folder>>();
465 packages.asMap().forEach((String packagePath, Uri uri) {
466 folderMap[packagePath] = [
467 PhysicalResourceProvider.INSTANCE.getFolder(path.fromUri(uri))
468 ];
469 });
470 return folderMap;
471 }
472
473 void _processAnalysisOptions(
474 CommandLineOptions options, AnalysisContext context) {
475 fileSystem.File file = _getOptionsFile(options);
476 List<OptionsProcessor> optionsProcessors =
477 AnalysisEngine.instance.optionsPlugin.optionsProcessors;
478 try {
479 AnalysisOptionsProvider analysisOptionsProvider =
480 new AnalysisOptionsProvider();
481 Map<String, YamlNode> optionMap =
482 analysisOptionsProvider.getOptionsFromFile(file);
483 optionsProcessors.forEach(
484 (OptionsProcessor p) => p.optionsProcessed(context, optionMap));
485
486 // Fill in lint rule defaults in case lints are enabled and rules are
487 // not specified in an options file.
488 if (options.lints && !containsLintRuleEntry(optionMap)) {
489 setLints(context, linterPlugin.contributedRules);
490 }
491
492 // Ask engine to further process options.
493 if (optionMap != null) {
494 configureContextOptions(context, optionMap);
495 }
496 } on Exception catch (e) {
497 optionsProcessors.forEach((OptionsProcessor p) => p.onError(e));
498 }
499 }
500
501 void _processPlugins() {
502 List<Plugin> plugins = <Plugin>[];
503 plugins.add(linterPlugin);
504 plugins.addAll(_userDefinedPlugins);
505 AnalysisEngine.instance.userDefinedPlugins = plugins;
506
507 // This ensures that AE extension manager processes plugins.
508 AnalysisEngine.instance.taskManager;
509 }
510
511 /// Analyze a single source.
512 ErrorSeverity _runAnalyzer(Source source, CommandLineOptions options) {
513 int startTime = currentTimeMillis();
514 AnalyzerImpl analyzer =
515 new AnalyzerImpl(_context, source, options, startTime);
516 var errorSeverity = analyzer.analyzeSync();
517 if (errorSeverity == ErrorSeverity.ERROR) {
518 exitCode = errorSeverity.ordinal;
519 }
520 if (options.warningsAreFatal && errorSeverity == ErrorSeverity.WARNING) {
521 exitCode = errorSeverity.ordinal;
522 }
523 return errorSeverity;
524 }
525
526 void _setupEnv(CommandLineOptions options) {
527 // In batch mode, SDK is specified on the main command line rather than in
528 // the command lines sent to stdin. So process it before deciding whether
529 // to activate batch mode.
530 if (sdk == null) {
531 sdk = new DirectoryBasedDartSdk(new JavaFile(options.dartSdkPath));
532 }
533 _isBatch = options.shouldBatch;
534 }
535
536 /// Perform a deep comparison of two string maps.
537 static bool _equalMaps(Map<String, String> m1, Map<String, String> m2) {
538 if (m1.length != m2.length) {
539 return false;
540 }
541 for (String key in m1.keys) {
542 if (!m2.containsKey(key) || m1[key] != m2[key]) {
543 return false;
544 }
545 }
546 return true;
547 }
548
549 /// Convert [sourcePath] into an absolute path.
550 static String _normalizeSourcePath(String sourcePath) =>
551 path.normalize(new File(sourcePath).absolute.path);
552 }
553
554 /// Provides a framework to read command line options from stdin and feed them
555 /// to a callback.
556 class _BatchRunner {
557 /// Run the tool in 'batch' mode, receiving command lines through stdin and
558 /// returning pass/fail status through stdout. This feature is intended for
559 /// use in unit testing.
560 static void runAsBatch(List<String> sharedArgs, _BatchRunnerHandler handler) {
561 outSink.writeln('>>> BATCH START');
562 Stopwatch stopwatch = new Stopwatch();
563 stopwatch.start();
564 int testsFailed = 0;
565 int totalTests = 0;
566 ErrorSeverity batchResult = ErrorSeverity.NONE;
567 // Read line from stdin.
568 Stream cmdLine =
569 stdin.transform(UTF8.decoder).transform(new LineSplitter());
570 cmdLine.listen((String line) {
571 // Maybe finish.
572 if (line.isEmpty) {
573 var time = stopwatch.elapsedMilliseconds;
574 outSink.writeln(
575 '>>> BATCH END (${totalTests - testsFailed}/$totalTests) ${time}ms') ;
576 exitCode = batchResult.ordinal;
577 }
578 // Prepare aruments.
579 var args;
580 {
581 var lineArgs = line.split(new RegExp('\\s+'));
582 args = new List<String>();
583 args.addAll(sharedArgs);
584 args.addAll(lineArgs);
585 args.remove('-b');
586 args.remove('--batch');
587 }
588 // Analyze single set of arguments.
589 try {
590 totalTests++;
591 ErrorSeverity result = handler(args);
592 bool resultPass = result != ErrorSeverity.ERROR;
593 if (!resultPass) {
594 testsFailed++;
595 }
596 batchResult = batchResult.max(result);
597 // Write stderr end token and flush.
598 errorSink.writeln('>>> EOF STDERR');
599 String resultPassString = resultPass ? 'PASS' : 'FAIL';
600 outSink.writeln(
601 '>>> TEST $resultPassString ${stopwatch.elapsedMilliseconds}ms');
602 } catch (e, stackTrace) {
603 errorSink.writeln(e);
604 errorSink.writeln(stackTrace);
605 errorSink.writeln('>>> EOF STDERR');
606 outSink.writeln('>>> TEST CRASH');
607 }
608 });
609 }
610 }
611
612 class _DriverError implements Exception {
613 String msg;
614 _DriverError(this.msg);
615 }
616
617 /// [SdkExtUriResolver] needs a Map from package name to folder. In the case
618 /// that the analyzer is invoked with a --package-root option, we need to
619 /// manually create this mapping. Given [packageRootPath],
620 /// [_PackageRootPackageMapBuilder] creates a simple mapping from package name
621 /// to full path on disk (resolving any symbolic links).
622 class _PackageRootPackageMapBuilder {
623 static Map<String, List<fileSystem.Folder>> buildPackageMap(
624 String packageRootPath) {
625 var packageRoot = new Directory(packageRootPath);
626 if (!packageRoot.existsSync()) {
627 throw new _DriverError(
628 'Package root directory ($packageRootPath) does not exist.');
629 }
630 var packages = packageRoot.listSync(followLinks: false);
631 var result = new Map<String, List<fileSystem.Folder>>();
632 for (var package in packages) {
633 var packageName = path.basename(package.path);
634 var realPath = package.resolveSymbolicLinksSync();
635 result[packageName] = [
636 PhysicalResourceProvider.INSTANCE.getFolder(realPath)
637 ];
638 }
639 return result;
640 }
641 }
OLDNEW
« no previous file with comments | « pkg/analyzer_cli/lib/src/bootloader.dart ('k') | pkg/analyzer_cli/lib/src/error_formatter.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698