import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/source/line_info.dart'; import 'package:codebrowser_dart/project.dart'; import 'package:codebrowser_dart/pub_get_runner.dart'; import 'package:path/path.dart' as path; import 'package:analyzer/file_system/physical_file_system.dart'; import 'dart:io'; import 'package:chalkdart/chalk.dart'; import 'package:codebrowser_dart/generator.dart'; import 'package:codebrowser_dart/browserastvisitor.dart'; List getContextRoots(Directory sourceDir) { FileSystemEntity e = sourceDir; String normalizedSourceDir = path.normalize(e.absolute.path); final collection = AnalysisContextCollection( includedPaths: [normalizedSourceDir], resourceProvider: PhysicalResourceProvider.INSTANCE); return collection.contexts.map((ctx) => ctx.contextRoot.root.path).toList(); } /// This function process a given project [sourceDir] Future processDirectory({ required Project project, required Directory sourceDir, required Directory outputDir, }) async { final blue = chalk.blueBright.bold; final red = chalk.redBright.bold; try { // Create project dir in outputDir String projectOutputDirPath = path.absolute(outputDir.path, project.name); final projectOutputDir = Directory.fromUri(Uri.parse(projectOutputDirPath)); if (!projectOutputDir.existsSync()) { projectOutputDir.createSync(recursive: true); } final dataPath = path.canonicalize("${outputDir.path}${path.separator}../data"); final refsPath = path.canonicalize("${outputDir.path}${path.separator}refs"); print("dataPath: $dataPath\nrefsPath: $refsPath"); final refs = Directory(refsPath); if (!refs.existsSync()) { refs.createSync(recursive: true); } final fileIndexPath = path.join(outputDir.path, "fileIndex"); final fnSearchPath = path.join(outputDir.path, "fnSearch"); final fnSearch = Directory(fnSearchPath); if (!fnSearch.existsSync()) { fnSearch.createSync(); } FileSystemEntity e = sourceDir; String normalizedSourceDir = path.normalize(e.absolute.path); // Often one context is returned, but depending on the project structure we // can see multiple contexts. /* Multiline * comment */ final Map pathToUnitLineInfos = {}; final List ctxRoots = getContextRoots(sourceDir); int c = 1; int total = ctxRoots.length; for (final ctx in ctxRoots) { Stopwatch s = Stopwatch(); s.start(); runPubGetInDir(ctx); final collection2 = AnalysisContextCollection( includedPaths: [ctx], resourceProvider: PhysicalResourceProvider.INSTANCE); for (final context in collection2.contexts) { if (context.contextRoot.root.path != ctx) { continue; } try { for (final filePath in context.contextRoot.analyzedFiles()) { if (!filePath.endsWith('.dart')) { continue; } final res = await context.currentSession.getResolvedUnit(filePath); final unit = res as ResolvedUnitResult?; if (unit == null) { print(red("Failed to parse unit for $filePath")); continue; } pathToUnitLineInfos[unit.path] = unit.lineInfo; final htmlFile = _getOutputFilePath( filePath, normalizedSourceDir, projectOutputDirPath, ); String relativeFilePath = path.relative(htmlFile, from: outputDir.path); File(fileIndexPath).writeAsStringSync("$relativeFilePath\n", mode: FileMode.writeOnlyAppend); final g = Generator(); g.syntaxHighlight(unit.unit.beginToken, unit.unit.endToken); final v = BrowserAstVisitor( refsPath, normalizedSourceDir, relativeFilePath, pathToUnitLineInfos, unit, g.tags, ); unit.unit.accept(v); // Stopwatch s = Stopwatch(); // s.start(); g.generateHtml( project, unit.content, htmlFile, dataPath, outputDir.path); // final dur = chalk.underlined("${s.elapsedMilliseconds}ms"); _writeFnSearch(fnSearchPath, v.unitSymbols); // print("Generated $htmlFile in $dur"); } } catch (e, s) { print(red( "Failed to analyze ${context.contextRoot.root.path} - $e, $s")); c++; continue; } } print(blue('[$c/$total] Analyzed $ctx in ${s.elapsedMilliseconds}ms')); c++; } } catch (e, s) { print(red("Error processing directory: $e, stack:\n$s")); } } String _getOutputFilePath( String sourceFile, String sourceDirRoot, String projectOutputDir) { // get the dir name without project_root_path String fileDirName = path.dirname(path.relative(sourceFile, from: sourceDirRoot)); String fileOutDir = path.join(projectOutputDir, fileDirName); Directory(fileOutDir).createSync(recursive: true); // get the basename only sourceFile = path.basename(sourceFile); // join return path.absolute(fileOutDir, sourceFile); } /// Write fnSearch, the function search index void _writeFnSearch(String fnSearchPath, Map symbols) { for (final e in symbols.entries) { var name = e.value; if (name.length < 4) continue; final firstTwo = name.substring(0, 2).toLowerCase(); File f = File(path.join(fnSearchPath, firstTwo)); f.writeAsStringSync("${e.value}|${e.key}\n", mode: FileMode.writeOnlyAppend); } }