| // Copyright (c) 2019, 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:io'; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:dart2js_info/info.dart'; |
| import 'package:dart2js_info/src/io.dart'; |
| import 'package:dart2js_info/src/util.dart'; |
| |
| import 'inject_text.dart'; |
| import 'usage_exception.dart'; |
| |
| /// Shows the contents of an info file as text. |
| class ShowCommand extends Command<void> with PrintUsageException { |
| @override |
| final String name = "show"; |
| @override |
| final String description = "Show a text representation of the info file."; |
| |
| ShowCommand() { |
| argParser.addOption('out', |
| abbr: 'o', help: 'Output file (defauts to stdout)'); |
| |
| argParser.addFlag('inject-text', |
| negatable: false, |
| help: 'Whether to inject output code snippets.\n\n' |
| 'By default dart2js produces code spans, but excludes the text. This\n' |
| 'option can be used to embed the text directly in the output.'); |
| } |
| |
| @override |
| void run() async { |
| final argRes = argResults!; |
| if (argRes.rest.isEmpty) { |
| usageException('Missing argument: <input-info>'); |
| } |
| |
| String filename = argRes.rest[0]; |
| AllInfo info = await infoFromFile(filename); |
| if (argRes['inject-text']) injectText(info); |
| |
| final buffer = StringBuffer(); |
| info.accept(TextPrinter(buffer, argRes['inject-text'])); |
| final outputPath = argRes['out']; |
| if (outputPath == null) { |
| print(buffer); |
| } else { |
| File(outputPath).writeAsStringSync('$buffer'); |
| } |
| } |
| } |
| |
| class TextPrinter implements InfoVisitor<void> { |
| final StringBuffer buffer; |
| final bool injectText; |
| |
| TextPrinter(this.buffer, this.injectText); |
| |
| int _indent = 0; |
| String get _textIndent => " " * _indent; |
| void _writeIndentation() { |
| buffer.write(_textIndent); |
| } |
| |
| void _writeIndented(String s) { |
| _writeIndentation(); |
| buffer.writeln(s.replaceAll('\n', '\n$_textIndent')); |
| } |
| |
| void _writeBlock(String s, void Function() f) { |
| _writeIndented(s); |
| _indent++; |
| f(); |
| _indent--; |
| } |
| |
| @override |
| void visitAll(AllInfo info) { |
| _writeBlock("Summary data", () => visitProgram(info.program!)); |
| buffer.writeln(); |
| _writeBlock("Libraries", () => info.libraries.forEach(visitLibrary)); |
| // Note: classes, functions, typedefs, and fields are group;ed by library. |
| |
| if (injectText) { |
| _writeBlock("Constants", () => info.constants.forEach(visitConstant)); |
| } else { |
| int size = info.constants.fold(0, (n, c) => n + c.size); |
| _writeIndented("All constants: ${_size(size)}"); |
| } |
| _writeBlock("Output units", () => info.outputUnits.forEach(visitOutput)); |
| } |
| |
| @override |
| void visitProgram(ProgramInfo info) { |
| _writeIndented('main: ${longName(info.entrypoint, useLibraryUri: true)}'); |
| _writeIndented('size: ${info.size}'); |
| _writeIndented('dart2js-version: ${info.dart2jsVersion}'); |
| var features = []; |
| if (info.noSuchMethodEnabled) features.add('no-such-method'); |
| if (info.isRuntimeTypeUsed) features.add('runtime-type'); |
| if (info.isFunctionApplyUsed) features.add('function-apply'); |
| if (info.minified) features.add('minified'); |
| if (features.isNotEmpty) { |
| _writeIndented('features: ${features.join(' ')}'); |
| } |
| } |
| |
| String _size(int size) { |
| if (size < 1024) return "$size b"; |
| if (size < (1024 * 1024)) { |
| return "${(size / 1024).toStringAsFixed(2)} Kb ($size b)"; |
| } |
| return "${(size / (1024 * 1024)).toStringAsFixed(2)} Mb ($size b)"; |
| } |
| |
| @override |
| void visitLibrary(LibraryInfo info) { |
| _writeBlock('${info.uri}: ${_size(info.size)}', () { |
| if (info.topLevelFunctions.isNotEmpty) { |
| _writeBlock('Top-level functions', |
| () => info.topLevelFunctions.forEach(visitFunction)); |
| buffer.writeln(); |
| } |
| if (info.topLevelVariables.isNotEmpty) { |
| _writeBlock('Top-level variables', |
| () => info.topLevelVariables.forEach(visitField)); |
| buffer.writeln(); |
| } |
| if (info.classes.isNotEmpty) { |
| _writeBlock('Classes', () => info.classes.forEach(visitClass)); |
| } |
| if (info.classTypes.isNotEmpty) { |
| _writeBlock('Classes', () => info.classTypes.forEach(visitClassType)); |
| } |
| if (info.typedefs.isNotEmpty) { |
| _writeBlock("Typedefs", () => info.typedefs.forEach(visitTypedef)); |
| buffer.writeln(); |
| } |
| buffer.writeln(); |
| }); |
| } |
| |
| @override |
| void visitClass(ClassInfo info) { |
| _writeBlock( |
| '${info.name}: ${_size(info.size)} [${info.outputUnit?.filename}]', () { |
| if (info.functions.isNotEmpty) { |
| _writeBlock('Methods:', () => info.functions.forEach(visitFunction)); |
| } |
| if (info.fields.isNotEmpty) { |
| _writeBlock('Fields:', () => info.fields.forEach(visitField)); |
| } |
| if (info.functions.isNotEmpty || info.fields.isNotEmpty) buffer.writeln(); |
| }); |
| } |
| |
| @override |
| void visitClassType(ClassTypeInfo info) { |
| _writeBlock( |
| '${info.name}: ${_size(info.size)} [${info.outputUnit?.filename}]', |
| () {}); |
| } |
| |
| @override |
| void visitField(FieldInfo info) { |
| _writeBlock('${info.type} ${info.name}: ${_size(info.size)}', () { |
| _writeIndented('inferred type: ${info.inferredType}'); |
| if (injectText) _writeBlock("code:", () => _writeCode(info.code)); |
| if (info.closures.isNotEmpty) { |
| _writeBlock('Closures:', () => info.closures.forEach(visitClosure)); |
| } |
| if (info.uses.isNotEmpty) { |
| _writeBlock('Dependencies:', () => info.uses.forEach(showDependency)); |
| } |
| }); |
| } |
| |
| @override |
| void visitFunction(FunctionInfo info) { |
| var outputUnitFile = ''; |
| if (info.functionKind == FunctionInfo.TOP_LEVEL_FUNCTION_KIND) { |
| outputUnitFile = ' [${info.outputUnit?.filename}]'; |
| } |
| String params = |
| info.parameters.map((p) => "${p.declaredType} ${p.name}").join(', '); |
| _writeBlock( |
| '${info.returnType} ${info.name}($params): ${_size(info.size)}$outputUnitFile', |
| () { |
| String params = info.parameters.map((p) => p.type).join(', '); |
| _writeIndented('declared type: ${info.type}'); |
| _writeIndented( |
| 'inferred type: ${info.inferredReturnType} Function($params)'); |
| _writeIndented('side effects: ${info.sideEffects}'); |
| if (injectText) _writeBlock("code:", () => _writeCode(info.code)); |
| if (info.closures.isNotEmpty) { |
| _writeBlock('Closures:', () => info.closures.forEach(visitClosure)); |
| } |
| if (info.uses.isNotEmpty) { |
| _writeBlock('Dependencies:', () => info.uses.forEach(showDependency)); |
| } |
| }); |
| } |
| |
| void showDependency(DependencyInfo info) { |
| var mask = info.mask ?? ''; |
| _writeIndented('- ${longName(info.target, useLibraryUri: true)} $mask'); |
| } |
| |
| @override |
| void visitTypedef(TypedefInfo info) { |
| _writeIndented('${info.name}: ${info.type}'); |
| } |
| |
| @override |
| void visitClosure(ClosureInfo info) { |
| _writeBlock(info.name, () => visitFunction(info.function)); |
| } |
| |
| @override |
| void visitConstant(ConstantInfo info) { |
| _writeBlock('${_size(info.size)}:', () => _writeCode(info.code)); |
| } |
| |
| void _writeCode(List<CodeSpan> code) { |
| _writeIndented(code.map((c) => c.text).join('\n')); |
| } |
| |
| @override |
| void visitOutput(OutputUnitInfo info) { |
| _writeIndented('${info.filename}: ${_size(info.size)}'); |
| } |
| } |