blob: ad6158b477205ed33856dfecb8de4830b2c2777e [file] [log] [blame]
// 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)}');
}
}