|
| 1 | +// Copyright (c) 2023, 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 | +import 'dart:io'; |
| 5 | + |
| 6 | +/// Calculates the amount of formatting changes in a given directory of code. |
| 7 | +/// |
| 8 | +/// This should be run with a path to a directory. That directory should |
| 9 | +/// contain a Git repo. The committed state of the repo should be the formatted |
| 10 | +/// output before the change in question. Then the result of the new formatting |
| 11 | +/// should be unstaged changes. |
| 12 | +/// |
| 13 | +/// Uses `git diff --shortstat` to calculate the number of changed lines. |
| 14 | +/// |
| 15 | +/// Counts the number of lines of Dart code by reading the files. |
| 16 | +void main(List<String> arguments) { |
| 17 | + if (arguments.length != 1) { |
| 18 | + print('Usage: change_metrics.dart <dir>'); |
| 19 | + exit(1); |
| 20 | + } |
| 21 | + |
| 22 | + var directory = arguments[0]; |
| 23 | + var totalLines = 0; |
| 24 | + var totalFiles = 0; |
| 25 | + |
| 26 | + print('Counting lines...'); |
| 27 | + for (var entry in Directory(directory).listSync(recursive: true)) { |
| 28 | + if (entry is File && entry.path.endsWith('.dart')) { |
| 29 | + try { |
| 30 | + var lines = entry.readAsLinesSync(); |
| 31 | + totalFiles++; |
| 32 | + totalLines += lines.length; |
| 33 | + } catch (error) { |
| 34 | + print('Could not read ${entry.path}:\n$error'); |
| 35 | + } |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + print('Getting diff stats...'); |
| 40 | + var result = Process.runSync('git', ['diff', '--shortstat'], |
| 41 | + // Make sure the user's local Git config doesn't affect the output. |
| 42 | + environment: { |
| 43 | + 'GIT_CONFIG_NOGLOBAL': 'true', |
| 44 | + 'GIT_CONFIG_NOSYSTEM': 'true', |
| 45 | + }, |
| 46 | + workingDirectory: directory); |
| 47 | + if (result.exitCode != 0) { |
| 48 | + print('Git failure:\n${result.stdout}\n${result.stderr}'); |
| 49 | + exit(1); |
| 50 | + } |
| 51 | + |
| 52 | + var stdout = result.stdout as String; |
| 53 | + var insertions = _parseGitStdout(stdout, r'(\d+) insertions'); |
| 54 | + var deletions = _parseGitStdout(stdout, r'(\d+) deletions'); |
| 55 | + var changes = insertions + deletions; |
| 56 | + |
| 57 | + print('$totalLines lines in $totalFiles files'); |
| 58 | + print('$insertions insertions + $deletions deletions = $changes changes'); |
| 59 | + var linesPerChange = totalLines / changes; |
| 60 | + print('1 changed line for every ${linesPerChange.toStringAsFixed(2)} ' |
| 61 | + 'lines of code'); |
| 62 | + |
| 63 | + var changesPerLine = 1000.0 * changes / totalLines; |
| 64 | + print('${changesPerLine.toStringAsFixed(4)} ' |
| 65 | + 'changed lines for every 1,000 lines of code'); |
| 66 | +} |
| 67 | + |
| 68 | +int _parseGitStdout(String stdout, String pattern) { |
| 69 | + var match = RegExp(pattern).firstMatch(stdout); |
| 70 | + if (match == null) { |
| 71 | + print('Could not parse Git output:\n$stdout'); |
| 72 | + exit(1); |
| 73 | + } |
| 74 | + |
| 75 | + return int.parse(match[1]!); |
| 76 | +} |
0 commit comments