diff --git a/.clang-format b/.clang-format index fb14c1c..4bf0a3c 100644 --- a/.clang-format +++ b/.clang-format @@ -1,29 +1,9 @@ -# MIT License -# -# Copyright (c) 2026 Andrew Smith -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - +# SPDX-License-Identifier: MIT; Copyright 2026 Andrew D Smith BasedOnStyle: LLVM ColumnLimit: 80 IndentWidth: 2 -AlwaysBreakAfterReturnType: All +BreakAfterReturnType: All +AlignAfterOpenBracket: true ContinuationIndentWidth: 2 ConstructorInitializerIndentWidth: 2 BraceWrapping: @@ -32,3 +12,7 @@ BraceWrapping: BreakBeforeBraces: Custom BreakConstructorInitializers: AfterColon SpacesBeforeTrailingComments: 2 +PackParameters: + BinPack: OnePerLine + BreakAfter: 0 +AllowShortFunctionsOnASingleLine: All diff --git a/.clang-tidy b/.clang-tidy index 3db09f5..be97404 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,9 +1,17 @@ Checks: " + concurrency-*, cert-*, cppcoreguidelines-*, performance-*, clang-diagnostic-*, clang-analyzer-*, + modernize-*, + misc-*, + portability-*, + -misc-include-cleaner, + -misc-use-anonymous-namespace, + -misc-header-include-cycle, + -modernize-use-trailing-return-type, -clang-diagnostic-unqualified-std-cast-call, -clang-diagnostic-unknown-warning-option, -clang-analyzer-unix.BlockInCriticalSection, @@ -28,17 +36,14 @@ CheckOptions: cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' cert-err33-c.AllowCastToVoid: 'true' cert-err33-c.CheckedFunctions: '*' - cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' - cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' - cppcoreguidelines-non-private-member-variables-in-classes.IgnorePublicMemberVariables: 'true' cppcoreguidelines-rvalue-reference-param-not-moved.AllowPartialMove: 'true' cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues: '1;2;3;4;10' cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues: '1.0;2.0;10.0;100.0' google-readability-braces-around-statements.ShortStatementLines: '1' - google-readability-function-size.StatementThreshold: '800' google-readability-namespace-comments.ShortNamespaceLines: '10' google-readability-namespace-comments.SpacesBeforeComments: '2' - llvm-else-after-return.WarnOnConditionVariables: 'false' - llvm-else-after-return.WarnOnUnfixable: 'false' - llvm-qualified-auto.AddConstToQualified: 'false' + llvm-else-after-return.WarnOnConditionVariables: 'true' + llvm-else-after-return.WarnOnUnfixable: 'true' + llvm-qualified-auto.AddConstToQualified: 'true' + modernize-use-trailing-return-type.TransformLambdas: none SystemHeaders: 'false' diff --git a/src/adapter_matcher.hpp b/src/adapter_matcher.hpp index 154d6aa..987ecc4 100644 --- a/src/adapter_matcher.hpp +++ b/src/adapter_matcher.hpp @@ -71,12 +71,14 @@ struct adapter_matcher { get_grade(const std::uint64_t n_reads) const -> std::string; [[nodiscard]] auto - report(const std::uint64_t n_reads, const std::uint64_t max_read_len, + report(const std::uint64_t n_reads, + const std::uint64_t max_read_len, const base_group_vec &groups, const file_grades &grades) const -> std::string; [[nodiscard]] auto - html(const std::uint64_t n_reads, const std::uint64_t max_read_len, + html(const std::uint64_t n_reads, + const std::uint64_t max_read_len, const base_group_vec &groups, const file_grades &grades) const -> std::string; }; diff --git a/src/bam_file.cpp b/src/bam_file.cpp index bee2257..27d0acc 100644 --- a/src/bam_file.cpp +++ b/src/bam_file.cpp @@ -27,30 +27,31 @@ estimate_n_reads_bam(const std::string &filename) -> std::tuple { static constexpr auto max_n_reads = 128 * 1024; - std::unique_ptr f( + const std::unique_ptr in( hts_open(std::data(filename), "r"), &hts_close); - if (!f) + if (!in) throw std::system_error(std::make_error_code(std::errc(errno)), "failed to open file: " + filename); - std::unique_ptr h(sam_hdr_read(f.get()), - &sam_hdr_destroy); + const std::unique_ptr h( + sam_hdr_read(in.get()), &sam_hdr_destroy); if (!h) throw std::system_error(std::make_error_code(std::errc(errno)), "failed to read header: " + filename); - const auto format = hts_get_format(f.get()); + const auto format = hts_get_format(in.get()); if (!format) throw std::runtime_error("failed to identify file format: " + filename); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) - const auto fp = format->format == bam ? f->fp.bgzf->fp : f->fp.hfile; + const auto fp = format->format == bam ? in->fp.bgzf->fp : in->fp.hfile; const auto pos_after_header = htell(fp); - std::unique_ptr rec(bam_init1(), &bam_destroy1); + const std::unique_ptr rec(bam_init1(), + &bam_destroy1); std::uint64_t n_reads{}; std::uint64_t total_read_len{}; int r{}; while (n_reads++ < max_n_reads && - (r = sam_read1(f.get(), h.get(), rec.get())) >= 0) + (r = sam_read1(in.get(), h.get(), rec.get())) >= 0) total_read_len += rec->core.l_qseq; if (r < -1) // error throw std::system_error(std::make_error_code(std::errc(errno)), @@ -71,13 +72,13 @@ init_dups(const std::string &filename, const std::uint64_t n_unique) return "TNGNNNCNNNNNNNNNNNNA"[x - 'A']; }; static constexpr auto n_unique_multiplier = 10; - std::unique_ptr f( + const std::unique_ptr in( hts_open(std::data(filename), "r"), &hts_close); - if (!f) + if (!in) throw std::system_error(std::make_error_code(std::errc(errno)), "failed to open file: " + filename); - std::unique_ptr h(sam_hdr_read(f.get()), - &sam_hdr_destroy); + const std::unique_ptr h( + sam_hdr_read(in.get()), &sam_hdr_destroy); if (!h) throw std::system_error(std::make_error_code(std::errc(errno)), "failed to read header: " + filename); @@ -90,7 +91,7 @@ init_dups(const std::string &filename, const std::uint64_t n_unique) std::uint64_t n_reads{}; const auto max_n_reads = n_unique_multiplier * n_unique; while (n_reads++ < max_n_reads && std::size(dups) < n_unique && - (r = sam_read1(f.get(), h.get(), rec.get())) >= 0) { + (r = sam_read1(in.get(), h.get(), rec.get())) >= 0) { const auto l_qseq = rec->core.l_qseq; const auto seq = bam_get_seq(rec.get()); if (std::ssize(buffer) < l_qseq) @@ -111,8 +112,8 @@ init_dups(const std::string &filename, const std::uint64_t n_unique) } auto -bam_file::load_next(const std::int32_t file_id, // - task_queue &tq, // +bam_file::load_next(const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) -> void { is_first_load = false; if (output_cursor > 0) { @@ -133,12 +134,12 @@ bam_file::load_next(const std::int32_t file_id, // input_last = std::distance(std::data(input_buffer), in_itr); } -[[nodiscard]] inline auto -partition(auto itr, // - const auto end, // - const std::int64_t n_chunks, // - const std::int32_t file_id, // - task_queue &tq, // +[[nodiscard]] static inline auto +partition(auto itr, + const auto end, + const std::int64_t n_chunks, + const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) { // ADS: this isn't working as desired: the end position of each part should be // the first record end past the 'end_itr' below unless end_itr == end @@ -146,10 +147,10 @@ partition(auto itr, // const auto chunk_size = (dist + n_chunks - 1) / n_chunks; while (itr != end) { dist = std::distance(itr, end); - auto end_itr = itr + (dist < chunk_size ? dist : chunk_size); + const auto end_itr = itr + (dist < chunk_size ? dist : chunk_size); // ADS: find_end_pos doesn't find end pos of a record, but of a range, so // includes multiple records - auto next_itr = bamrec::find_end_pos(itr, end_itr); + const auto next_itr = bamrec::find_end_pos(itr, end_itr); if (next_itr == itr) break; ++n_tasks; @@ -160,9 +161,9 @@ partition(auto itr, // } auto -bam_file::get_chunks(const std::int64_t n_chunks, // - const std::int32_t file_id, // - task_queue &tq, // +bam_file::get_chunks(const std::int64_t n_chunks, + const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) -> void { // Swap so the input buffer can be used to inflate more data and the former // input buffer has been inflated and will provide data for analysis @@ -179,9 +180,9 @@ bam_file::get_chunks(const std::int64_t n_chunks, // } auto -bam_file::make_tasks(const std::int64_t n_threads, // - const std::int32_t file_id, // - task_queue &tq, // +bam_file::make_tasks(const std::int64_t n_threads, + const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) -> void { static constexpr auto n_chunks_per_thread = 8; const auto n_chunks = n_chunks_per_thread * n_threads; diff --git a/src/bam_file.hpp b/src/bam_file.hpp index 93b52e4..00a7f27 100644 --- a/src/bam_file.hpp +++ b/src/bam_file.hpp @@ -49,9 +49,9 @@ class bam_file { public: bam_file(const std::string &filename, const std::int64_t buf_size) : - input_buffer(get_input_buffer_size(buf_size) + min_buf_size), // - output_buffer(get_output_buffer_size(buf_size) + min_buf_size), // - br(filename, get_reader_buffer_size(buf_size)) // + input_buffer(get_input_buffer_size(buf_size) + min_buf_size), + output_buffer(get_output_buffer_size(buf_size) + min_buf_size), + br(filename, get_reader_buffer_size(buf_size)) // {} // clang-format off @@ -71,10 +71,10 @@ class bam_file { reset(bam_file &reads_file) -> void; friend auto - make_tasks(bam_file &reads_file, // - const std::int64_t n_threads, // - const std::int32_t file_id, // - task_queue &tq, // + make_tasks(bam_file &reads_file, + const std::int64_t n_threads, + const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) -> void; private: @@ -88,20 +88,26 @@ class bam_file { } auto - get_chunks(const std::int64_t n_chunks, const std::int32_t file_id, - task_queue &tq, std::atomic_int32_t &n_tasks) -> void; + get_chunks(const std::int64_t n_chunks, + const std::int32_t file_id, + task_queue &tq, + std::atomic_int32_t &n_tasks) -> void; auto - load_next(const std::int32_t file_id, task_queue &tq, + load_next(const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) -> void; auto - make_tasks_inflate(const std::int32_t file_id, task_queue &tq, + make_tasks_inflate(const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) -> void; auto - make_tasks(const std::int64_t n_chunks, const std::int32_t file_id, - task_queue &tq, std::atomic_int32_t &n_tasks) -> void; + make_tasks(const std::int64_t n_chunks, + const std::int32_t file_id, + task_queue &tq, + std::atomic_int32_t &n_tasks) -> void; [[nodiscard]] auto inflate_only() const -> bool { @@ -128,10 +134,10 @@ init_dups(const std::string &filename, const std::uint64_t n_unique) -> dups_map_t; inline auto -make_tasks(bam_file &reads_file, // - const std::int64_t n_threads, // - const std::int32_t file_id, // - task_queue &tq, // +make_tasks(bam_file &reads_file, + const std::int64_t n_threads, + const std::int32_t file_id, + task_queue &tq, std::atomic_int32_t &n_tasks) -> void { n_tasks = 1; // for current task, which makes tasks if (!reads_file.inflate_only()) diff --git a/src/bam_header.cpp b/src/bam_header.cpp index 5f7bd3e..13b9275 100644 --- a/src/bam_header.cpp +++ b/src/bam_header.cpp @@ -6,8 +6,8 @@ #include [[nodiscard]] auto -bam_header::update(const_iterator itr, - const const_iterator end) -> const_iterator { +bam_header::update(const_iterator itr, const const_iterator end) + -> const_iterator { static constexpr auto msg = "incorrect BAM magic identified: {} at {}"; const auto update_u32 = [](auto &val, const auto inc, const auto the_byte) { // NOLINTNEXTLINE(*-avoid-magic-numbers) diff --git a/src/bam_header.hpp b/src/bam_header.hpp index 8f4068f..0905849 100644 --- a/src/bam_header.hpp +++ b/src/bam_header.hpp @@ -56,10 +56,14 @@ struct bam_header { } } - NLOHMANN_DEFINE_TYPE_INTRUSIVE(bam_header, magic_bytes_remaining, - l_text_bytes_remaining, l_text, - n_ref_bytes_remaining, n_ref, - l_name_bytes_remaining, name_bytes_remaining, + NLOHMANN_DEFINE_TYPE_INTRUSIVE(bam_header, + magic_bytes_remaining, + l_text_bytes_remaining, + l_text, + n_ref_bytes_remaining, + n_ref, + l_name_bytes_remaining, + name_bytes_remaining, l_ref_bytes_remaining); }; diff --git a/src/bamrec.cpp b/src/bamrec.cpp index b951e6c..ed9eb6a 100644 --- a/src/bamrec.cpp +++ b/src/bamrec.cpp @@ -64,8 +64,8 @@ assign_sequence(bidir_itr_t first, auto last, output_itr_t d_first) { } [[nodiscard]] auto -bamrec::get_next(bamrec::pos_t &itr, const bamrec::pos_t end, - bamrec &rec) -> bool { +bamrec::get_next(bamrec::pos_t &itr, const bamrec::pos_t end, bamrec &rec) + -> bool { if (std::distance(itr, end) < bam_core_t::sz) return false; bam_core_t core{}; @@ -80,7 +80,7 @@ bamrec::get_next(bamrec::pos_t &itr, const bamrec::pos_t end, auto out_itr = std::begin(rec.buffer); std::copy_n(itr + bam_core_t::read_name_offset, rec.name_len, out_itr); out_itr += rec.name_len; // increment data cursor to sequence - auto seq_in = itr + core.seq_offset(); + const auto seq_in = itr + core.seq_offset(); if (core.bam_is_rev()) assign_sequence_revcomp(seq_in, core.l_seq, out_itr); else @@ -102,8 +102,8 @@ bamrec::get_next(bamrec::pos_t &itr, const bamrec::pos_t end, } [[nodiscard]] auto -bamrec::find_end_pos(bamrec::pos_t itr, - const bamrec::pos_t end) -> bamrec::pos_t { +bamrec::find_end_pos(bamrec::pos_t itr, const bamrec::pos_t end) + -> bamrec::pos_t { static constexpr std::int64_t record_size_size = sizeof(std::uint32_t); std::uint32_t record_size{}; while (itr != end) { diff --git a/src/base_groups.cpp b/src/base_groups.cpp index ab09bbc..331cca7 100644 --- a/src/base_groups.cpp +++ b/src/base_groups.cpp @@ -36,7 +36,8 @@ get_linear_interval(const std::uint64_t n_bases, } [[nodiscard]] auto -make_base_groups(const std::uint64_t n_bases, const std::uint64_t n_initial, +make_base_groups(const std::uint64_t n_bases, + const std::uint64_t n_initial, const std::uint64_t n_groups_target) -> base_group_vec { static constexpr auto make_one_group = [](const auto a, const auto b) { return base_group_t{a, b}; diff --git a/src/base_groups.hpp b/src/base_groups.hpp index 2dcbc06..a07e7f2 100644 --- a/src/base_groups.hpp +++ b/src/base_groups.hpp @@ -19,7 +19,8 @@ using base_group_t = std::pair; using base_group_vec = std::vector; [[nodiscard]] auto -make_base_groups(const std::uint64_t n_bases, const std::uint64_t n_initial, +make_base_groups(const std::uint64_t n_bases, + const std::uint64_t n_initial, const std::uint64_t n_groups_target) -> base_group_vec; [[nodiscard]] auto diff --git a/src/bgzf_block.cpp b/src/bgzf_block.cpp index d22f1b2..67a106c 100644 --- a/src/bgzf_block.cpp +++ b/src/bgzf_block.cpp @@ -6,6 +6,7 @@ #include #include +#include auto bgzf_block_t::decompress() -> void { @@ -21,6 +22,6 @@ bgzf_block_t::decompress() -> void { size, // &inflated_size); assert(result == LIBDEFLATE_SUCCESS && - inflated_size == static_cast(size)); + std::cmp_equal(inflated_size, static_cast(size))); libdeflate_free_decompressor(decompressor); } diff --git a/src/bgzf_reader.cpp b/src/bgzf_reader.cpp index 767a14a..a23f34e 100644 --- a/src/bgzf_reader.cpp +++ b/src/bgzf_reader.cpp @@ -34,7 +34,7 @@ get_isize(const auto data, const auto data_size) { return data_size < isize_size ? 0 : get_unaligned_le32(data_isize); } -auto +static inline auto assign(gzip_header &hdr, const auto data) -> void { // ADS: data from the file takes 18 bytes, the fields of the struct take 18 // bytes, but the struct occupies 20 due to uint32_t members @@ -75,7 +75,7 @@ bgzf_reader::read_data() -> bool { return n_bytes > 0; } -[[nodiscard]] inline constexpr auto +[[nodiscard]] static inline constexpr auto get_gzip_body_size(const gzip_header &gh) -> std::uint32_t { return (static_cast(gh.size) + 1) - gzip_header_size; } diff --git a/src/duplication_results.cpp b/src/duplication_results.cpp index a1f6a03..0a33242 100644 --- a/src/duplication_results.cpp +++ b/src/duplication_results.cpp @@ -127,8 +127,8 @@ duplication_results::get_overrepresented(const std::uint64_t n_reads) const } auto -duplication_results::initialize(const run_mode &mode, - const file_info &info) -> void { +duplication_results::initialize(const run_mode &mode, const file_info &info) + -> void { read_skip = info.n_reads_est < max_n_reads_total ? 0 @@ -138,7 +138,8 @@ duplication_results::initialize(const run_mode &mode, } auto -duplication_results::initialize(const run_mode &mode, const file_info &info, +duplication_results::initialize(const run_mode &mode, + const file_info &info, const dups_init_t &dups_init) -> void { initialize(mode, info); dups = dups_init.dups_zero; @@ -194,9 +195,10 @@ make_bins(const auto &breaks, const auto &hist) { } // ADS: for original dups, from FastQC extrapolation of dup counts. -[[nodiscard]] auto +[[nodiscard]] static auto get_corrected_count(const std::uint64_t count_at_limit, - const std::uint64_t n_reads, const std::uint64_t dup_level, + const std::uint64_t n_reads, + const std::uint64_t dup_level, const std::uint64_t n_obs) -> double { static constexpr auto epsilon = 0.01; if (count_at_limit == n_reads) // we saw everything @@ -243,10 +245,10 @@ duplication_results::get_dups_summary(const std::uint64_t n_reads) const [](const auto x) { return std::get<0>(x) * std::get<1>(x); }) | std::ranges::to(); return dup_summary_t{ - max_dup, - n_reads, // number of counted reads - std::move(hist_mass), // move to avoid copying when making tuple - std::move(hist_dedup), // move to avoid copying when making tuple + .max_dup = max_dup, + .n_reads = n_reads, // number of counted reads + .hist_mass = std::move(hist_mass), // move to avoid copy making tuple + .hist_dedup = std::move(hist_dedup), // move to avoid copy making tuple }; } @@ -264,10 +266,10 @@ duplication_results::get_dups_summary() const -> dup_summary_t { [](const auto x) { return std::get<0>(x) * std::get<1>(x); }) | std::ranges::to(); return dup_summary_t{ - max_dup, - get_n_counted_reads(), // number of counted reads - std::move(hist_mass), // move to avoid copying when making tuple - std::move(hist_dedup), // move to avoid copying when making tuple + .max_dup = max_dup, + .n_reads = get_n_counted_reads(), // number of counted reads + .hist_mass = std::move(hist_mass), // move to avoid copy making tuple + .hist_dedup = std::move(hist_dedup), // move to avoid copy making tuple }; } @@ -285,8 +287,8 @@ get_grade_duplication(const dup_summary_t &summary) -> std::string { } [[nodiscard]] auto -duplication_report(const dup_summary_t &summary, - const file_grades &grades) -> std::string { +duplication_report(const dup_summary_t &summary, const file_grades &grades) + -> std::string { static constexpr auto label = "duplication"; static constexpr auto start_tag = ">>Sequence Duplication Levels\t{}\n" "#Total Deduplicated Percentage\t{:.6f}\n"; @@ -349,8 +351,8 @@ overrepresented_html(const std::vector &overrep, } [[nodiscard]] auto -duplication_html(const dup_summary_t &summary, - const file_grades &grades) -> std::string { +duplication_html(const dup_summary_t &summary, const file_grades &grades) + -> std::string { static constexpr auto label = "duplication"; static constexpr auto plot_format = R"(