diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index ce2b5fa..8dc3581 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -32,7 +32,7 @@ jobs: run: dart analyze --fatal-infos - name: Run tests (& collect coverage) - run: dart run coverage:test_with_coverage + run: dart run coverage:test_with_coverage --function-coverage --branch-coverage - name: Upload coverage report uses: codecov/codecov-action@v3 diff --git a/.gitignore b/.gitignore index 564869a..2ebc698 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # https://dart.dev/guides/libraries/private-files # Created by `dart pub` .dart_tool/ +dist/ # vscode settings .vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d70a9f..61756a6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: check-added-large-files - id: mixed-line-ending args: [--fix=lf] - - repo: https://github.com/fluttercommunity/import_sorter - rev: "f350497a11b1285c695595049e95a420068e7a9f" - hooks: - - id: dart-import-sorter + # - repo: https://github.com/fluttercommunity/import_sorter + # rev: "f350497a11b1285c695595049e95a420068e7a9f" + # hooks: + # - id: dart-import-sorter diff --git a/bin/cpp_linter_dart.dart b/bin/cpp_linter_dart.dart index 35b4a5b..5f92f6c 100644 --- a/bin/cpp_linter_dart.dart +++ b/bin/cpp_linter_dart.dart @@ -68,6 +68,7 @@ Future main(List arguments) async { files = listSourceFiles(extensions, ignored, notIgnored); } endLogGroup(); + if (files.isEmpty) { return setExitCode(0); } @@ -83,13 +84,6 @@ Future main(List arguments) async { ); startLogGroup('Posting comment(s)'); - var threadCommentsAllowed = true; - if (githubEventPath.isNotEmpty) { - var repoInfo = ghEventPayload['repository'] as Map?; - if (repoInfo != null && repoInfo.keys.contains('private')) { - threadCommentsAllowed = repoInfo['private'] as bool == false; - } - } final commentBody = makeComment(formatAdvice, tidyNotes, linesChangedOnly); final commentPreamble = '\n# Cpp-Linter Report '; final commentPs = '\n\nHave any feedback or feature suggestions? [Share it ' @@ -97,6 +91,14 @@ Future main(List arguments) async { final lgtm = '$commentPreamble:heavy_check_mark:\nNo problems need attention.' '$commentPs'; final fullComment = '$commentPreamble$commentBody$commentPs'; + + var threadCommentsAllowed = true; + if (githubEventPath.isNotEmpty) { + var repoInfo = ghEventPayload['repository'] as Map?; + if (repoInfo != null && repoInfo.keys.contains('private')) { + threadCommentsAllowed = repoInfo['private'] as bool == false; + } + } if (args['thread-comments'] != 'false' && threadCommentsAllowed) { bool updateOnly = args['thread-comments'] == 'update'; if (args['lgtm'] && commentBody.isEmpty) { @@ -111,16 +113,14 @@ Future main(List arguments) async { mode: FileMode.append, ); } - setExitCode( - makeAnnotations( - formatAdvice, - tidyNotes, - args['file-annotations'], - args['style'], - linesChangedOnly, - ), + var exitCode = makeAnnotations( + formatAdvice, + tidyNotes, + args['file-annotations'], + args['style'], + linesChangedOnly, ); endLogGroup(); - return setExitCode(0); + return setExitCode(exitCode); } diff --git a/lib/clang_format.dart b/lib/clang_format.dart index 07c646f..b969be8 100644 --- a/lib/clang_format.dart +++ b/lib/clang_format.dart @@ -87,6 +87,7 @@ class FormatFix { } /// NOTE: This is currently broken and needs much improvement! + // coverage:ignore-start List getSuggestions({bool lineChangesOnly = false}) { List? linesChanged; if (lineChangesOnly) { @@ -136,7 +137,7 @@ class FormatFix { ); } return result; - } + } // coverage:ignore-end } /// Parse the [xmlOut] from running clang-format on a single [file]. diff --git a/lib/clang_tidy.dart b/lib/clang_tidy.dart index b39c1db..bb722b2 100644 --- a/lib/clang_tidy.dart +++ b/lib/clang_tidy.dart @@ -10,6 +10,7 @@ import 'package:yaml/yaml.dart'; import 'common.dart'; import 'logger.dart'; +// coverage:ignore-start class TidyReplacement { int rmLength; int line; @@ -158,6 +159,7 @@ TidyAdvice parseYmlAdvice(FileObj file, {bool lineChangesOnly = false}) { } return yamlAdvice; } +// coverage:ignore-end /// A class to represent clang-tidy notifications (parsed from the stdout of a /// dry run). @@ -204,6 +206,7 @@ class TidyNotification { List parseTidyOutput(FileObj file, String output) { var notifications = []; + if (output.isEmpty) return notifications; for (final line in output.split('\n')) { var match = RegExp(r"^(.+):(\d+):(\d+):\s(\w+):(.*)\[([a-zA-Z\d\-\.]+)\]$") .matchAsPrefix(line); @@ -226,8 +229,9 @@ List parseTidyOutput(FileObj file, String output) { ), ); } else { - assert(notifications.isNotEmpty); - notifications.last.srcLines.add(line); + if (notifications.isNotEmpty) { + notifications.last.srcLines.add(line); + } } } return notifications; @@ -253,7 +257,11 @@ Future> runClangTidy( if (checks.isNotEmpty) args.add("--checks='$checks'"); if (database.isNotEmpty) { args.addAll( - ['-p', p.isRelative(database) ? p.absolute(database) : database]); + [ + '-p', + p.normalize(p.isRelative(database) ? p.absolute(database) : database), + ], + ); } var ranges = file.linesAdded; if (ranges.isNotEmpty) { @@ -265,7 +273,7 @@ Future> runClangTidy( } if (extraArgs != null && extraArgs.isNotEmpty) { for (var arg in extraArgs) { - args.add('--extra-args=$arg'); + args.add("--extra-arg='$arg'"); } } args.add(file.name.replaceAll('\\', '/')); diff --git a/lib/common.dart b/lib/common.dart index fb21fc3..1ea2368 100644 --- a/lib/common.dart +++ b/lib/common.dart @@ -111,7 +111,7 @@ String makeClangToolExeVersion(String tool, String version) { return '$tool-$version$suffix'; } // treat version as an explicit path - var versionPath = p.absolute(version); + var versionPath = p.absolute(p.normalize(version)); var possibles = [ File('$versionPath/bin/$tool$suffix'), File('$versionPath/$tool$suffix'), diff --git a/lib/git.dart b/lib/git.dart index 16fd54a..f325d15 100644 --- a/lib/git.dart +++ b/lib/git.dart @@ -16,6 +16,7 @@ Future getSha({int parent = 1}) async { return subprocessRun('git', args: ["log", "-$parent", "--format=%H"]); } +// coverage:ignore-start /// Get the diff for the currently staged files or the current commit if no /// changes were made. Future getDiff(bool debug) async { @@ -41,7 +42,7 @@ Future getDiff(bool debug) async { ); } return diff; -} +} // coverage:ignore-end /// Parses a file's name from the diff chunk's front matter. Binary files are /// ignored (returns `null`). diff --git a/lib/run.dart b/lib/run.dart index 9b39e09..50612ef 100644 --- a/lib/run.dart +++ b/lib/run.dart @@ -123,7 +123,7 @@ List listSourceFiles( } var relPath = p.relative(path.path); - var file = FileObj(relPath); + var file = FileObj(relPath.replaceAll(p.separator, '/')); var ignore = isFileInSet(file, ignored); if (ignore && isFileInSet(file, notIgnored)) { ignore = true; diff --git a/pubspec.lock b/pubspec.lock index c5de0a6..ca9704f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "36a321c3d2cbe01cbcb3540a87b8843846e0206df3e691fa7b23e19e78de6d49" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "65.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: dfe03b90ec022450e22513b5e5ca1f01c0c01de9c3fba2f7fd233cb57a6b9a07 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.0" args: dependency: "direct main" description: @@ -41,14 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - charcode: - dependency: transitive - description: - name: charcode - sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 - url: "https://pub.dev" - source: hosted - version: "1.3.1" collection: dependency: transitive description: @@ -69,10 +61,10 @@ packages: dependency: "direct dev" description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9 url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.7.1" crypto: dependency: transitive description: @@ -109,10 +101,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" http_multi_server: dependency: transitive description: @@ -189,10 +181,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" mime: dependency: transitive description: @@ -221,18 +213,18 @@ packages: dependency: "direct main" description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" petitparser: dependency: transitive description: name: petitparser - sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "6.0.2" pool: dependency: transitive description: @@ -245,10 +237,10 @@ packages: dependency: "direct main" description: name: process_run - sha256: ceacfac6d566a36c895d64edc7e429efb2d6b6303b5e28d5c13bc59fe6e8974e + sha256: "94413ec6bbd05dfe7c3002c379724f75f185ff7f0a5dfb1851a42473d84c956e" url: "https://pub.dev" source: hosted - version: "0.13.1" + version: "0.13.3" pub_semver: dependency: transitive description: @@ -357,10 +349,10 @@ packages: dependency: "direct dev" description: name: test - sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f url: "https://pub.dev" source: hosted - version: "1.24.6" + version: "1.24.9" test_api: dependency: transitive description: @@ -373,10 +365,10 @@ packages: dependency: transitive description: name: test_core - sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a url: "https://pub.dev" source: hosted - version: "0.5.6" + version: "0.5.9" tint: dependency: transitive description: @@ -397,10 +389,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "13.0.0" watcher: dependency: transitive description: @@ -409,6 +401,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: edc8a9573dd8c5a83a183dae1af2b6fd4131377404706ca4e5420474784906fa + url: "https://pub.dev" + source: hosted + version: "0.4.0" web_socket_channel: dependency: transitive description: @@ -421,18 +421,18 @@ packages: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" xml: dependency: "direct main" description: name: xml - sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.4.2" + version: "6.5.0" yaml: dependency: "direct main" description: @@ -442,4 +442,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.6 <4.0.0" + dart: ">=3.2.0 <4.0.0" diff --git a/test/common_test.dart b/test/common_test.dart new file mode 100644 index 0000000..6c27ee7 --- /dev/null +++ b/test/common_test.dart @@ -0,0 +1,12 @@ +import 'package:cpp_linter_dart/common.dart'; +import 'package:test/test.dart'; +import 'dart:io'; + +void main() { + test('makeExeName', () { + expect( + makeClangToolExeVersion('clang-tidy', '12'), + 'clang-tidy${Platform.isWindows ? '.exe' : '-12'}', + ); + }); +} diff --git a/test/run_test.dart b/test/run_test.dart index b3beda2..8d251a4 100644 --- a/test/run_test.dart +++ b/test/run_test.dart @@ -2,13 +2,15 @@ import 'dart:io'; // Package imports: +import 'package:cpp_linter_dart/cli.dart'; import 'package:test/test.dart'; // Project imports: import 'package:cpp_linter_dart/git.dart'; import 'package:cpp_linter_dart/run.dart'; +import '../bin/cpp_linter_dart.dart' as bin; -void main() { +void main() async { test('parseIgnore', () { , Set)>{ '.github': ({'.github'}, {}), @@ -33,4 +35,58 @@ void main() { // instances of `FileObj` will differ, but the List.length should be equal expect(filesWalked.length, filesFromDiff.length); }); + + /* + * This test basically does what main() in bin/cpp_linter_dart.dart does. + * Here we limit the input options (see the List of parsed args below). + */ + test('main-mock', () async { + final argParser = getParser(); + var args = argParser.parse( + [ + '--version', + String.fromEnvironment('CLANG_TOOLS_VERSION', defaultValue: '12'), + '--verbosity', // enable debug output + '-p', // database path + 'test/demo', // will be made absolute + '--', // the rest will be clang-tidy extra-args + '--std=cxx14', + ], + ); + final ext = (args['extensions'] as String) + .split(',') + .map((e) => e.startsWith('.') ? e : '.$e') + .toList(); + final (ignored, notIgnored) = parseIgnoredOption(args['ignore']); + final files = listSourceFiles(ext, ignored, notIgnored); + final (formatAdvice, _, tidyNotes) = await captureClangToolsOutput( + files, + args['version'], + args['lines-changed-only'], + args['style'], + args['tidy-checks'], + args['database'], + args.rest, + args['verbosity'], + ); + expect( + tidyNotes.map((element) => element.file.name).toSet().toList(), + ['test/demo/demo.cpp', 'test/demo/demo.hpp'], + ); + expect( + formatAdvice.map((element) => element.file.name).toSet().toList(), + ['test/demo/demo.cpp', 'test/demo/demo.hpp'], + ); + }); + + /* + * This test runs actual main() in bin/cpp_linter_dart.dart + * Since the function only output a exit code, introspection isn't + * suitable here. Instead, we'll just just assert that the exit code is as + * expected. + */ + test('main-actual', () async { + expect(await bin.main(['--help']), 0); + expect(await bin.main([]), 8); + }); }