diff --git a/Dependencies.cmake b/Dependencies.cmake index 45e58a7..c9c515b 100644 --- a/Dependencies.cmake +++ b/Dependencies.cmake @@ -53,6 +53,13 @@ dependency( DEPENDENCY_LINK_TARGETS tree-sitter-typescript ) +dependency( + DEPENDENCY_NAME tree-sitter-cpp + DEPENDENCY_RESOURCE kilo52/tree-sitter-cpp + DEPENDENCY_VERSION v0.23.4-patch-cmakeliststxt + DEPENDENCY_LINK_TARGETS tree-sitter-cpp +) + dependency( DEPENDENCY_NAME utf8proc DEPENDENCY_RESOURCE JuliaStrings/utf8proc diff --git a/Dependencies.cmake.sha256 b/Dependencies.cmake.sha256 index 935a344..68f4d6b 100644 --- a/Dependencies.cmake.sha256 +++ b/Dependencies.cmake.sha256 @@ -1 +1 @@ -8648e7957901a3029837287b247ad68b2b2dce86ab816eaa4fc18505449f6701 \ No newline at end of file +56e55b81f62920d03317875cd291ba2c7dcfe72d01ae3f85cf75dbf072d811d8 \ No newline at end of file diff --git a/cmake/ConfigTree-sitter-cpp.cmake b/cmake/ConfigTree-sitter-cpp.cmake new file mode 100644 index 0000000..fb77663 --- /dev/null +++ b/cmake/ConfigTree-sitter-cpp.cmake @@ -0,0 +1,6 @@ +# Configuration options for the Tree-Sitter-Cpp dependency + +# Define the program variable to avoid errors when custom command +# is executed since the CLI might not actually be available. +set(TREE_SITTER_CLI "") +set(BUILD_TESTING OFF) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 88bfaf2..b6902c9 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -39,6 +39,7 @@ target_sources( "c/lang_javascript.c" "c/lang_typescript.c" "c/lang_bash.c" + "c/lang_cpp.c" "c/logical.c" "c/physical.c" "c/statistics.c" diff --git a/src/lib/c/factories.c b/src/lib/c/factories.c index e993c72..140007c 100644 --- a/src/lib/c/factories.c +++ b/src/lib/c/factories.c @@ -28,6 +28,7 @@ TSParser* createParserPython(void); TSParser* createParserJavaScript(void); TSParser* createParserTypeScript(void); TSParser* createParserBash(void); +TSParser* createParserCpp(void); void evaluateNodeC(TSNode node, NodeEvalTrace* trace); void evaluateNodeJava(TSNode node, NodeEvalTrace* trace); @@ -35,6 +36,7 @@ void evaluateNodePython(TSNode node, NodeEvalTrace* trace); void evaluateNodeJavaScript(TSNode node, NodeEvalTrace* trace); void evaluateNodeTypeScript(TSNode node, NodeEvalTrace* trace); void evaluateNodeBash(TSNode node, NodeEvalTrace* trace); +void evaluateNodeCpp(TSNode node, NodeEvalTrace* trace); TSParser* createParser(RcnTextFormat language) { switch (language) { @@ -50,6 +52,8 @@ TSParser* createParser(RcnTextFormat language) { return createParserTypeScript(); case RCN_LANG_BASH: return createParserBash(); + case RCN_LANG_CPP: + return createParserCpp(); default: return NULL; } @@ -69,6 +73,8 @@ NodeVisitor createEvaluationFunction(RcnTextFormat language) { return evaluateNodeTypeScript; case RCN_LANG_BASH: return evaluateNodeBash; + case RCN_LANG_CPP: + return evaluateNodeCpp; default: return NULL; } @@ -81,6 +87,7 @@ const char* getInlineSourceCommentString(RcnTextFormat language) { return "#"; case RCN_LANG_C: case RCN_LANG_JAVA: + case RCN_LANG_CPP: case RCN_LANG_JAVASCRIPT: case RCN_LANG_TYPESCRIPT: default: @@ -165,6 +172,20 @@ SourceFormatDetection detectSourceFormat(const RcnSourceFile* file) { } else if (strcmp(extension, "txt") == 0) { detection.isSupportedFormat = true; detection.format = RCN_TEXT_UNFORMATTED; + } else if (strcmp(extension, "cpp") == 0 + || strcmp(extension, "cc") == 0 + || strcmp(extension, "cxx") == 0 + || strcmp(extension, "c++") == 0 + || strcmp(extension, "hpp") == 0 + || strcmp(extension, "hxx") == 0 + || strcmp(extension, "cppm") == 0 + || strcmp(extension, "ccm") == 0 + || strcmp(extension, "cxxm") == 0 + || strcmp(extension, "c++m") == 0 + || strcmp(extension, "ixx") == 0) { + detection.isSupportedFormat = true; + detection.isProgrammingLanguage = true; + detection.format = RCN_LANG_CPP; } return detection; diff --git a/src/lib/c/lang_cpp.c b/src/lib/c/lang_cpp.c new file mode 100644 index 0000000..9ee496c --- /dev/null +++ b/src/lib/c/lang_cpp.c @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2026 Raven Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "tree_sitter/api.h" + +#include "reckon/reckon.h" +#include "reckon_export.h" +#include "evaluation.h" + +RECKON_NO_EXPORT const TSLanguage* tree_sitter_cpp(void); + +/** + * These are the symbol identifiers as defined by the C++ language parser + * of tree-sitter. We have only copied the symbol identifiers that we are + * interested in evaluating or counting. Others do not contribute to the weight + * of a node in the AST. + */ +enum SymbolIdentifiersCpp { + sym_preproc_directive = 19, + sym_preproc_include = 222, + sym_preproc_def = 223, + sym_preproc_function_def = 224, + sym_preproc_if = 227, + sym_preproc_ifdef = 228, + sym_preproc_else = 229, + sym_preproc_elif = 230, + sym_preproc_elifdef = 231, + sym_function_definition = 254, + sym_declaration = 255, + sym_type_definition = 256, + sym__type_definition_type = 257, + sym__type_definition_declarators = 258, + sym__declaration_modifiers = 259, + sym__declaration_specifiers = 260, + sym_linkage_specification = 261, + sym_attribute_specifier = 262, + sym_attribute = 263, + sym_declaration_list = 270, + sym__declarator = 271, + sym__type_declarator = 273, + sym__abstract_declarator = 274, + sym_attributed_declarator = 279, + sym_attributed_type_declarator = 281, + sym_type_specifier = 299, + sym_enum_specifier = 301, + sym_struct_specifier = 303, + sym_union_specifier = 304, + sym_field_declaration = 307, + sym_enumerator = 309, + sym_attributed_statement = 312, + sym_statement = 313, + sym__top_level_statement = 314, + sym_labeled_statement = 315, + sym__top_level_expression_statement = 316, + sym_expression_statement = 317, + sym_if_statement = 318, + sym_else_clause = 319, + sym_switch_statement = 320, + sym_case_statement = 321, + sym_while_statement = 322, + sym_do_statement = 323, + sym_for_statement = 324, + sym_return_statement = 326, + sym_break_statement = 327, + sym_continue_statement = 328, + sym_goto_statement = 329, + sym_expression = 334, + sym_class_specifier = 379, + sym_inline_method_definition = 400, + sym_constructor_or_destructor_definition = 405, + sym_friend_declaration = 410, + sym_template_declaration = 386, + sym_template_instantiation = 387, + sym_namespace_definition = 430, + sym_namespace_alias_definition = 431, + sym_using_declaration = 434, + sym_alias_declaration = 435, + sym_static_assert_declaration = 436, + sym_concept_definition = 437, + sym_for_range_loop = 438, + sym_co_return_statement = 443, + sym_co_yield_statement = 444, + sym_throw_statement = 445, + sym_try_statement = 446, + sym_catch_clause = 447, +}; + +TSParser* createParserCpp(void) { + TSParser* parser = ts_parser_new(); + if (parser) { + if (!ts_parser_set_language(parser, tree_sitter_cpp())) { + // LCOV_EXCL_START + ts_parser_delete(parser); + return NULL; + // LCOV_EXCL_STOP + } + } + return parser; +} + +static RcnCount evaluateNodeWeightCppImpl(TSNode node, NodeEvalTrace* trace) { + RcnCount weight = 0; + TSSymbol sym = ts_node_grammar_symbol(node); + switch (sym) { + case sym_for_statement: + case sym_for_range_loop: + trace->idxLastForSym = trace->idx; + weight += 1; + break; + case sym_declaration: + trace->lnLastDecl = currentLine(node); + // Do not count variable declarations inside for-statement + // Check if the following is present: + // for_statement / for_range_loop + // for + // ( + // declaration + if (trace->idxLastForSym != (trace->idx - 3)) { + weight += 1; + } + break; + case sym_do_statement: + weight += 2; + break; + case sym_type_definition: + trace->idxLastTypeDef = trace->idx; + weight += 1; + break; + case sym_struct_specifier: + case sym_class_specifier: + if (trace->idxLastTypeDef == (trace->idx - 2)) { + break; + } + if (trace->lnLastDecl == currentLine(node)) { + break; + } + if (trace->lnLastExpr == currentLine(node)) { + break; + } + weight += 1; + break; + case sym_enum_specifier: + case sym_union_specifier: + if (trace->idxLastTypeDef == (trace->idx - 2) + || trace->lnLastDecl == currentLine(node)) { + break; + } + weight += 1; + break; + case sym__top_level_expression_statement: + case sym_expression_statement: + trace->lnLastExpr = currentLine(node); + weight += 1; + break; + case sym_if_statement: + // else-if counts as one + // Nodes are: else_clause -> else -> if_statement + if (trace->idxLastElse == (trace->idx - 2)) { + break; + } + weight += 1; + break; + case sym_else_clause: + trace->idxLastElse = trace->idx; + weight += 1; + break; + case sym_preproc_directive: + case sym_preproc_include: + case sym_preproc_def: + case sym_preproc_function_def: + case sym_preproc_if: + case sym_preproc_ifdef: + case sym_preproc_else: + case sym_preproc_elif: + case sym_preproc_elifdef: + case sym_function_definition: + case sym__type_definition_type: + case sym__type_definition_declarators: + case sym__declaration_modifiers: + case sym__declaration_specifiers: + case sym_linkage_specification: + case sym_attribute_specifier: + case sym_attribute: + case sym_declaration_list: + case sym__declarator: + case sym__type_declarator: + case sym__abstract_declarator: + case sym_attributed_declarator: + case sym_attributed_type_declarator: + case sym_type_specifier: + case sym_field_declaration: + case sym_enumerator: + case sym_attributed_statement: + case sym_statement: + case sym__top_level_statement: + case sym_labeled_statement: + case sym_switch_statement: + case sym_case_statement: + case sym_while_statement: + case sym_return_statement: + case sym_break_statement: + case sym_continue_statement: + case sym_goto_statement: + case sym_expression: + case sym_inline_method_definition: + case sym_constructor_or_destructor_definition: + case sym_friend_declaration: + case sym_template_declaration: + case sym_template_instantiation: + case sym_namespace_definition: + case sym_namespace_alias_definition: + case sym_using_declaration: + case sym_alias_declaration: + case sym_static_assert_declaration: + case sym_concept_definition: + case sym_co_return_statement: + case sym_co_yield_statement: + case sym_throw_statement: + case sym_try_statement: + case sym_catch_clause: + weight += 1; + default: + break; + } + return weight; +} + +void evaluateNodeCpp(TSNode node, NodeEvalTrace* trace) { + trace->result->count += evaluateNodeWeightCppImpl(node, trace); + trace->idx++; +} diff --git a/src/lib/include/reckon/reckon.h b/src/lib/include/reckon/reckon.h index 5206866..03cdcba 100644 --- a/src/lib/include/reckon/reckon.h +++ b/src/lib/include/reckon/reckon.h @@ -100,7 +100,7 @@ extern "C" { * The total number of supported text formats, including * supported programming languages. */ -#define RECKON_NUM_SUPPORTED_FORMATS 16 +#define RECKON_NUM_SUPPORTED_FORMATS 17 /** * Macro to create a format option bitmask. @@ -197,6 +197,11 @@ typedef enum RcnTextFormat { */ RCN_LANG_PYTHON, + /** + * Source files for the C++ programming language. + */ + RCN_LANG_CPP, + /** * Source files for the JavaScript programming language. */ @@ -806,6 +811,12 @@ typedef enum RcnFormatOption { */ RCN_OPT_LANG_PYTHON = RECKON_MK_FRMT_OPT(RCN_LANG_PYTHON), + /** + * Option to select statistics for source code files written in + * the C++ programming language. + */ + RCN_OPT_LANG_CPP = RECKON_MK_FRMT_OPT(RCN_LANG_CPP), + /** * Option to select statistics for source code files written in * the JavaScript programming language. diff --git a/src/lib/tests/CMakeLists.txt b/src/lib/tests/CMakeLists.txt index cdc00dc..79a6d56 100644 --- a/src/lib/tests/CMakeLists.txt +++ b/src/lib/tests/CMakeLists.txt @@ -99,6 +99,13 @@ add_test_suite( TEST_SUITE_LINK ${RECKON_TARGET_LIB_OBJ} ) +add_test_suite( + TEST_SUITE_NAME CppLanguageUnitTest + TEST_SUITE_TARGET test_lang_cpp + TEST_SUITE_SOURCE unit/c/test_lang_cpp.c + TEST_SUITE_LINK ${RECKON_TARGET_LIB_OBJ} +) + add_test_suite( TEST_SUITE_NAME JavaScriptLanguageUnitTest TEST_SUITE_TARGET test_lang_javascript diff --git a/src/lib/tests/res/cpp/Sample.cpp b/src/lib/tests/res/cpp/Sample.cpp new file mode 100644 index 0000000..d44cd4c --- /dev/null +++ b/src/lib/tests/res/cpp/Sample.cpp @@ -0,0 +1,359 @@ +/* Golden sample: Contains C++ language features and constructs */ + +/** + * block comment + */ + +// line comment + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__cplusplus) +# if (__cplusplus >= 201703L) +# define SAMPLE_CPP_CPP17 1 +# else +# define SAMPLE_CPP_CPP17 0 +# endif +#else +# define SAMPLE_CPP_CPP17 0 +#endif + +/* Pragmas */ +#pragma once + +/* Preprocessor macros */ +#define SAMPLE_VERSION 1 +#define STR(x) #x +#define XSTR(x) STR(x) +#define CONCAT(a, b) a##b + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define UNUSED(x) ((void)(x)) + +/* Forward declarations */ +class Shape; +class Circle; +class Rectangle; + +/* Type alias */ +using StringList = std::vector; +using StringMap = std::map; + +/* Namespace alias */ +namespace fs = std::filesystem; + +/* Enum */ +enum Color { + RED, + GREEN, + BLUE +}; + +/* Enum class (C++11) */ +enum class Direction { + NORTH, + SOUTH, + EAST, + WEST +}; + +/* Struct */ +struct Point { + double x; + double y; +}; + +/* Typedef struct */ +typedef struct { + int width; + int height; +} Size; + +/* Union */ +union Data { + int i; + float f; + char c; +}; + +/* Namespace */ +namespace geometry { + +/* Abstract base class */ +class Shape { +public: + Shape() = default; + virtual ~Shape() = default; + + virtual double area() const = 0; + virtual double perimeter() const = 0; + + std::string name() const { + return name_; + } + +protected: + std::string name_; +}; + +/* Derived class */ +class Circle : public Shape { +public: + explicit Circle(double radius) : radius_(radius) { + name_ = "Circle"; + } + + ~Circle() override = default; + + double area() const override { + return 3.14159265358979 * radius_ * radius_; + } + + double perimeter() const override { + return 2.0 * 3.14159265358979 * radius_; + } + + double radius() const { + return radius_; + } + +private: + double radius_; +}; + +/* Another derived class */ +class Rectangle : public Shape { +public: + Rectangle(double width, double height) + : width_(width), height_(height) { + name_ = "Rectangle"; + } + + ~Rectangle() override = default; + + double area() const override { + return width_ * height_; + } + + double perimeter() const override { + return 2.0 * (width_ + height_); + } + +private: + double width_; + double height_; +}; + +} // namespace geometry + +/* Template class */ +template +class Stack { +public: + Stack() = default; + + void push(const T& value) { + data_.push_back(value); + } + + T pop() { + if (data_.empty()) { + throw std::out_of_range("Stack is empty"); + } + T value = data_.back(); + data_.pop_back(); + return value; + } + + bool empty() const { + return data_.empty(); + } + + size_t size() const { + return data_.size(); + } + +private: + std::vector data_; +}; + +/* Template function */ +template +T clamp(T value, T min_val, T max_val) { + if (value < min_val) { + return min_val; + } + if (value > max_val) { + return max_val; + } + return value; +} + +/* Template specialization */ +template<> +const char* clamp( + const char* value, + const char* min_val, + const char* max_val +) { + if (std::string(value) < std::string(min_val)) { + return min_val; + } + if (std::string(value) > std::string(max_val)) { + return max_val; + } + return value; +} + +/* Static assert */ +static_assert(sizeof(int) >= 4, "int must be at least 4 bytes"); + +/* Using declaration */ +using std::cout; +using std::endl; +using std::string; + +/* Free function with control flow */ +int processValues(const std::vector& values) { + if (values.empty()) { + return 0; + } + + int result = 0; + + for (int v : values) { + result += v; + } + + for (size_t i = 0; i < values.size(); ++i) { + if (values[i] < 0) { + continue; + } + result += values[i]; + } + + return result; +} + +/* Function with switch statement */ +std::string colorName(Color c) { + switch (c) { + case RED: + return "red"; + case GREEN: + return "green"; + case BLUE: + return "blue"; + default: + return "unknown"; + } +} + +/* Function with try/catch */ +double safeDivide(double a, double b) { + if (b == 0.0) { + throw std::invalid_argument("Division by zero"); + } + return a / b; +} + +double trySafeDivide(double a, double b) { + try { + return safeDivide(a, b); + } catch (const std::invalid_argument& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 0.0; + } catch (...) { + std::cerr << "Unknown error" << std::endl; + return 0.0; + } +} + +/* Function with while and do-while loops */ +int countDown(int start) { + int count = 0; + while (start > 0) { + --start; + ++count; + } + do { + ++count; + } while (count < 5); + return count; +} + +/* Lambda expression usage */ +void sortAndPrint(std::vector& data) { + std::sort(data.begin(), data.end(), [](int a, int b) { + return a < b; + }); + std::for_each(data.begin(), data.end(), [](int v) { + std::cout << v << " "; + }); +} + +/* Smart pointer usage */ +std::unique_ptr createShape(const std::string& type) { + if (type == "circle") { + return std::make_unique(5.0); + } else if (type == "rectangle") { + return std::make_unique(3.0, 4.0); + } else { + return nullptr; + } +} + +/* extern "C" linkage */ +extern "C" { + int cCompatibleFunc(int x) { + return x * 2; + } +} + +/* Main function */ +int main() { + geometry::Circle circle(5.0); + geometry::Rectangle rect(3.0, 4.0); + + std::cout << "Circle area: " << circle.area() << std::endl; + std::cout << "Rect area: " << rect.area() << std::endl; + + Stack stack; + stack.push(1); + stack.push(2); + stack.push(3); + + while (!stack.empty()) { + std::cout << stack.pop() << std::endl; + } + + std::vector nums = {5, 3, 8, 1, 9, 2, 7, 4, 6}; + sortAndPrint(nums); + std::cout << std::endl; + + auto shape = createShape("circle"); + if (shape) { + std::cout << "Shape: " << shape->name() << std::endl; + } + + double result = trySafeDivide(10.0, 2.0); + std::cout << "Result: " << result << std::endl; + + StringList names = {"Alice", "Bob", "Charlie"}; + for (const auto& name : names) { + std::cout << name << std::endl; + } + + return 0; +} diff --git a/src/lib/tests/res/cpp/SampleAnnotated.cpp b/src/lib/tests/res/cpp/SampleAnnotated.cpp new file mode 100644 index 0000000..de9a869 --- /dev/null +++ b/src/lib/tests/res/cpp/SampleAnnotated.cpp @@ -0,0 +1,359 @@ +/* Golden sample: Contains C++ language features and constructs */ + +/** + * block comment + */ + +// line comment + +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) +#include // +1 (preproc include) + +#if defined(__cplusplus) // +1 (preproc if) +# if (__cplusplus >= 201703L) // +1 (preproc if) +# define SAMPLE_CPP_CPP17 1 // +1 (preproc def) +# else // +1 (preproc else) +# define SAMPLE_CPP_CPP17 0 // +1 (preproc def) +# endif +#else // +1 (preproc else) +# define SAMPLE_CPP_CPP17 0 // +1 (preproc def) +#endif + +/* Pragmas */ +#pragma once // +1 (preproc directive) + +/* Preprocessor macros */ +#define SAMPLE_VERSION 1 // +1 (preproc def) +#define STR(x) #x // +1 (preproc function def) +#define XSTR(x) STR(x) // +1 (preproc function def) +#define CONCAT(a, b) a##b // +1 (preproc function def) + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) // +1 (preproc function def) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) // +1 (preproc function def) + +#define UNUSED(x) ((void)(x)) // +1 (preproc function def) + +/* Forward declarations */ +class Shape; // +1 (class specifier) +class Circle; // +1 (class specifier) +class Rectangle; // +1 (class specifier) + +/* Type alias */ +using StringList = std::vector; // +1 (alias declaration) +using StringMap = std::map; // +1 (alias declaration) + +/* Namespace alias */ +namespace fs = std::filesystem; // +1 (namespace alias definition) + +/* Enum */ +enum Color { // +1 (enum specifier) + RED, // +1 (enumerator) + GREEN, // +1 (enumerator) + BLUE // +1 (enumerator) +}; + +/* Enum class (C++11) */ +enum class Direction { // +1 (enum specifier) + NORTH, // +1 (enumerator) + SOUTH, // +1 (enumerator) + EAST, // +1 (enumerator) + WEST // +1 (enumerator) +}; + +/* Struct */ +struct Point { // +1 (struct specifier) + double x; // +1 (field declaration) + double y; // +1 (field declaration) +}; + +/* Typedef struct */ +typedef struct { // +1 (type definition) + int width; // +1 (field declaration) + int height; // +1 (field declaration) +} Size; + +/* Union */ +union Data { // +1 (union specifier) + int i; // +1 (field declaration) + float f; // +1 (field declaration) + char c; // +1 (field declaration) +}; + +/* Namespace */ +namespace geometry { // +2 (namespace definition, declaration list) + +/* Abstract base class */ +class Shape { // +1 (class specifier) +public: + Shape() = default; // +1 (function definition) + virtual ~Shape() = default; // +1 (function definition) + + virtual double area() const = 0; // +1 (field declaration) + virtual double perimeter() const = 0; // +1 (field declaration) + + std::string name() const { // +1 (function definition) + return name_; // +1 (return statement) + } + +protected: + std::string name_; // +1 (field declaration) +}; + +/* Derived class */ +class Circle : public Shape { // +1 (class specifier) +public: + explicit Circle(double radius) : radius_(radius) { // +1 (function definition) + name_ = "Circle"; // +1 (expression statement) + } + + ~Circle() override = default; // +1 (function definition) + + double area() const override { // +1 (function definition) + return 3.14159265358979 * radius_ * radius_; // +1 (return statement) + } + + double perimeter() const override { // +1 (function definition) + return 2.0 * 3.14159265358979 * radius_; // +1 (return statement) + } + + double radius() const { // +1 (function definition) + return radius_; // +1 (return statement) + } + +private: + double radius_; // +1 (field declaration) +}; + +/* Another derived class */ +class Rectangle : public Shape { // +1 (class specifier) +public: + Rectangle(double width, double height) // +1 (function definition) + : width_(width), height_(height) { + name_ = "Rectangle"; // +1 (expression statement) + } + + ~Rectangle() override = default; // +1 (function definition) + + double area() const override { // +1 (function definition) + return width_ * height_; // +1 (return statement) + } + + double perimeter() const override { // +1 (function definition) + return 2.0 * (width_ + height_); // +1 (return statement) + } + +private: + double width_; // +1 (field declaration) + double height_; // +1 (field declaration) +}; + +} // namespace geometry + +/* Template class */ +template // +1 (template declaration) +class Stack { // +1 (class specifier) +public: + Stack() = default; // +1 (function definition) + + void push(const T& value) { // +1 (function definition) + data_.push_back(value); // +1 (expression statement) + } + + T pop() { // +1 (function definition) + if (data_.empty()) { // +1 (if statement) + throw std::out_of_range("Stack is empty"); // +1 (throw statement) + } + T value = data_.back(); // +1 (declaration) + data_.pop_back(); // +1 (expression statement) + return value; // +1 (return statement) + } + + bool empty() const { // +1 (function definition) + return data_.empty(); // +1 (return statement) + } + + size_t size() const { // +1 (function definition) + return data_.size(); // +1 (return statement) + } + +private: + std::vector data_; // +1 (field declaration) +}; + +/* Template function */ +template // +1 (template declaration) +T clamp(T value, T min_val, T max_val) { // +1 (function definition) + if (value < min_val) { // +1 (if statement) + return min_val; // +1 (return statement) + } + if (value > max_val) { // +1 (if statement) + return max_val; // +1 (return statement) + } + return value; // +1 (return statement) +} + +/* Template specialization */ +template<> // +1 (template declaration) +const char* clamp( // +1 (function definition) + const char* value, + const char* min_val, + const char* max_val +) { + if (std::string(value) < std::string(min_val)) { // +1 (if statement) + return min_val; // +1 (return statement) + } + if (std::string(value) > std::string(max_val)) { // +1 (if statement) + return max_val; // +1 (return statement) + } + return value; // +1 (return statement) +} + +/* Static assert */ +static_assert(sizeof(int) >= 4, "int must be at least 4 bytes"); // +1 (static assert declaration) + +/* Using declaration */ +using std::cout; // +1 (using declaration) +using std::endl; // +1 (using declaration) +using std::string; // +1 (using declaration) + +/* Free function with control flow */ +int processValues(const std::vector& values) { // +1 (function definition) + if (values.empty()) { // +1 (if statement) + return 0; // +1 (return statement) + } + + int result = 0; // +1 (declaration) + + for (int v : values) { // +1 (for range loop) + result += v; // +1 (expression statement) + } + + for (size_t i = 0; i < values.size(); ++i) { // +1 (for statement) + if (values[i] < 0) { // +1 (if statement) + continue; // +1 (continue statement) + } + result += values[i]; // +1 (expression statement) + } + + return result; // +1 (return statement) +} + +/* Function with switch statement */ +std::string colorName(Color c) { // +1 (function definition) + switch (c) { // +1 (switch statement) + case RED: // +1 (case statement) + return "red"; // +1 (return statement) + case GREEN: // +1 (case statement) + return "green"; // +1 (return statement) + case BLUE: // +1 (case statement) + return "blue"; // +1 (return statement) + default: // +1 (case statement) + return "unknown"; // +1 (return statement) + } +} + +/* Function with try/catch */ +double safeDivide(double a, double b) { // +1 (function definition) + if (b == 0.0) { // +1 (if statement) + throw std::invalid_argument("Division by zero"); // +1 (throw statement) + } + return a / b; // +1 (return statement) +} + +double trySafeDivide(double a, double b) { // +1 (function definition) + try { // +1 (try statement) + return safeDivide(a, b); // +1 (return statement) + } catch (const std::invalid_argument& e) { // +1 (catch clause) + std::cerr << "Error: " << e.what() << std::endl; // +1 (expression statement) + return 0.0; // +1 (return statement) + } catch (...) { // +1 (catch clause) + std::cerr << "Unknown error" << std::endl; // +1 (expression statement) + return 0.0; // +1 (return statement) + } +} + +/* Function with while and do-while loops */ +int countDown(int start) { // +1 (function definition) + int count = 0; // +1 (declaration) + while (start > 0) { // +1 (while statement) + --start; // +1 (expression statement) + ++count; // +1 (expression statement) + } + do { // +2 (do statement) + ++count; // +1 (expression statement) + } while (count < 5); + return count; // +1 (return statement) +} + +/* Lambda expression usage */ +void sortAndPrint(std::vector& data) { // +1 (function definition) + std::sort(data.begin(), data.end(), [](int a, int b) { // +1 (expression statement) + return a < b; // +1 (return statement) + }); + std::for_each(data.begin(), data.end(), [](int v) { // +1 (expression statement) + std::cout << v << " "; // +1 (expression statement) + }); +} + +/* Smart pointer usage */ +std::unique_ptr createShape(const std::string& type) { // +1 (function definition) + if (type == "circle") { // +1 (if statement) + return std::make_unique(5.0); // +1 (return statement) + } else if (type == "rectangle") { // +1 (else clause) + return std::make_unique(3.0, 4.0); // +1 (return statement) + } else { // +1 (else clause) + return nullptr; // +1 (return statement) + } +} + +/* extern "C" linkage */ +extern "C" { // +2 (linkage specification, declaration list) + int cCompatibleFunc(int x) { // +1 (function definition) + return x * 2; // +1 (return statement) + } +} + +/* Main function */ +int main() { // +1 (function definition) + geometry::Circle circle(5.0); // +1 (declaration) + geometry::Rectangle rect(3.0, 4.0); // +1 (declaration) + + std::cout << "Circle area: " << circle.area() << std::endl; // +1 (expression statement) + std::cout << "Rect area: " << rect.area() << std::endl; // +1 (expression statement) + + Stack stack; // +1 (declaration) + stack.push(1); // +1 (expression statement) + stack.push(2); // +1 (expression statement) + stack.push(3); // +1 (expression statement) + + while (!stack.empty()) { // +1 (while statement) + std::cout << stack.pop() << std::endl; // +1 (expression statement) + } + + std::vector nums = {5, 3, 8, 1, 9, 2, 7, 4, 6}; // +1 (declaration) + sortAndPrint(nums); // +1 (expression statement) + std::cout << std::endl; // +1 (expression statement) + + auto shape = createShape("circle"); // +1 (declaration) + if (shape) { // +1 (if statement) + std::cout << "Shape: " << shape->name() << std::endl; // +1 (expression statement) + } + + double result = trySafeDivide(10.0, 2.0); // +1 (declaration) + std::cout << "Result: " << result << std::endl; // +1 (expression statement) + + StringList names = {"Alice", "Bob", "Charlie"}; // +1 (declaration) + for (const auto& name : names) { // +1 (for range loop) + std::cout << name << std::endl; // +1 (expression statement) + } + + return 0; // +1 (return statement) +} diff --git a/src/lib/tests/res/cpp/SampleMinFormatting.cpp b/src/lib/tests/res/cpp/SampleMinFormatting.cpp new file mode 100644 index 0000000..df1f19a --- /dev/null +++ b/src/lib/tests/res/cpp/SampleMinFormatting.cpp @@ -0,0 +1,200 @@ +/* Golden sample: Contains C++ language features and constructs */ +/** + * block comment + */ +// line comment +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__cplusplus) +# if (__cplusplus >= 201703L) +# define SAMPLE_CPP_CPP17 1 +# else +# define SAMPLE_CPP_CPP17 0 +# endif +#else +# define SAMPLE_CPP_CPP17 0 +#endif +/* Pragmas */ +#pragma once +/* Preprocessor macros */ +#define SAMPLE_VERSION 1 +#define STR(x) #x +#define XSTR(x) STR(x) +#define CONCAT(a, b) a##b +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define UNUSED(x) ((void)(x)) +/* Forward declarations */ +class Shape; +class Circle; +class Rectangle; +/* Type alias */ +using StringList = std::vector; +using StringMap = std::map; +/* Namespace alias */ +namespace fs = std::filesystem; +/* Enum */ +enum Color { RED, GREEN, BLUE }; +/* Enum class (C++11) */ +enum class Direction { NORTH, SOUTH, EAST, WEST }; +/* Struct */ +struct Point { double x; double y; }; +/* Typedef struct */ +typedef struct { int width; int height; } Size; +/* Union */ +union Data { int i; float f; char c; }; +/* Namespace */ +namespace geometry { +/* Abstract base class */ +class Shape { +public: + Shape() = default; + virtual ~Shape() = default; + virtual double area() const = 0; + virtual double perimeter() const = 0; + std::string name() const { return name_; } +protected: + std::string name_; +}; +/* Derived class */ +class Circle : public Shape { +public: + explicit Circle(double radius) : radius_(radius) { name_ = "Circle"; } + ~Circle() override = default; + double area() const override { return 3.14159265358979 * radius_ * radius_; } + double perimeter() const override { return 2.0 * 3.14159265358979 * radius_; } + double radius() const { return radius_; } +private: + double radius_; +}; +/* Another derived class */ +class Rectangle : public Shape { +public: + Rectangle(double width, double height) : width_(width), height_(height) { name_ = "Rectangle"; } + ~Rectangle() override = default; + double area() const override { return width_ * height_; } + double perimeter() const override { return 2.0 * (width_ + height_); } +private: + double width_; + double height_; +}; +} // namespace geometry +/* Template class */ +template +class Stack { +public: + Stack() = default; + void push(const T& value) { data_.push_back(value); } + T pop() { + if (data_.empty()) { throw std::out_of_range("Stack is empty"); } + T value = data_.back(); + data_.pop_back(); + return value; + } + bool empty() const { return data_.empty(); } + size_t size() const { return data_.size(); } +private: + std::vector data_; +}; +/* Template function */ +template +T clamp(T value, T min_val, T max_val) { + if (value < min_val) { return min_val; } + if (value > max_val) { return max_val; } + return value; +} +/* Template specialization */ +template<> +const char* clamp(const char* value, const char* min_val, const char* max_val) { + if (std::string(value) < std::string(min_val)) { return min_val; } + if (std::string(value) > std::string(max_val)) { return max_val; } + return value; +} +/* Static assert */ +static_assert(sizeof(int) >= 4, "int must be at least 4 bytes"); +/* Using declaration */ +using std::cout; +using std::endl; +using std::string; +/* Free function with control flow */ +int processValues(const std::vector& values) { + if (values.empty()) { return 0; } + int result = 0; + for (int v : values) { result += v; } + for (size_t i = 0; i < values.size(); ++i) { + if (values[i] < 0) { continue; } + result += values[i]; + } + return result; +} +/* Function with switch statement */ +std::string colorName(Color c) { + switch (c) { + case RED: return "red"; + case GREEN: return "green"; + case BLUE: return "blue"; + default: return "unknown"; + } +} +/* Function with try/catch */ +double safeDivide(double a, double b) { + if (b == 0.0) { throw std::invalid_argument("Division by zero"); } + return a / b; +} +double trySafeDivide(double a, double b) { + try { return safeDivide(a, b); } + catch (const std::invalid_argument& e) { std::cerr << "Error: " << e.what() << std::endl; return 0.0; } + catch (...) { std::cerr << "Unknown error" << std::endl; return 0.0; } +} +/* Function with while and do-while loops */ +int countDown(int start) { + int count = 0; + while (start > 0) { --start; ++count; } + do { ++count; } while (count < 5); + return count; +} +/* Lambda expression usage */ +void sortAndPrint(std::vector& data) { + std::sort(data.begin(), data.end(), [](int a, int b) { return a < b; }); + std::for_each(data.begin(), data.end(), [](int v) { std::cout << v << " "; }); +} +/* Smart pointer usage */ +std::unique_ptr createShape(const std::string& type) { + if (type == "circle") { return std::make_unique(5.0); } + else if (type == "rectangle") { return std::make_unique(3.0, 4.0); } + else { return nullptr; } +} +/* extern "C" linkage */ +extern "C" { + int cCompatibleFunc(int x) { return x * 2; } +} +/* Main function */ +int main() { + geometry::Circle circle(5.0); + geometry::Rectangle rect(3.0, 4.0); + std::cout << "Circle area: " << circle.area() << std::endl; + std::cout << "Rect area: " << rect.area() << std::endl; + Stack stack; + stack.push(1); + stack.push(2); + stack.push(3); + while (!stack.empty()) { std::cout << stack.pop() << std::endl; } + std::vector nums = {5, 3, 8, 1, 9, 2, 7, 4, 6}; + sortAndPrint(nums); + std::cout << std::endl; + auto shape = createShape("circle"); + if (shape) { std::cout << "Shape: " << shape->name() << std::endl; } + double result = trySafeDivide(10.0, 2.0); + std::cout << "Result: " << result << std::endl; + StringList names = {"Alice", "Bob", "Charlie"}; + for (const auto& name : names) { std::cout << name << std::endl; } + return 0; +} diff --git a/src/lib/tests/unit/c/test_lang_cpp.c b/src/lib/tests/unit/c/test_lang_cpp.c new file mode 100644 index 0000000..8234c1e --- /dev/null +++ b/src/lib/tests/unit/c/test_lang_cpp.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2026 Raven Computing + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "unity.h" + +#include "reckon/reckon.h" +#include "fileio.h" + +#define TEST_RES_DIR RECKON_TEST_PATH_RES_BASE "/cpp" +#define TEST_SAMPLE TEST_RES_DIR "/Sample.cpp" +#define TEST_SAMPLE_ANNOTATED TEST_RES_DIR "/SampleAnnotated.cpp" +#define TEST_SAMPLE_MIN_FORMATTING TEST_RES_DIR "/SampleMinFormatting.cpp" + +char* cppSourceWithSyntaxError = + "class Hello {\n" + " void myFunc() {\n" + " std::cout << \"Hello!\"\n" // Missing semicolon + " }\n" + "};\n"; + +void setUp(void) { } + +void tearDown(void) { } + +void testCppLogicalLineCountIsCorrect(void) { + RcnSourceFile* file = newSourceFile(TEST_SAMPLE); + readSourceFileContent(file); + RcnCountResult result = rcnCountLogicalLines( + RCN_LANG_CPP, + file->content + ); + freeSourceFile(file); + TEST_ASSERT_TRUE(result.state.ok); + TEST_ASSERT_EQUAL_INT(197, result.count); + TEST_ASSERT_EQUAL_INT(RCN_ERR_NONE, result.state.errorCode); + TEST_ASSERT_NULL(result.state.errorMessage); +} + +void testCppLogicalLineCountIsLenientWithSyntaxError(void) { + RcnSourceText source = { + .text = cppSourceWithSyntaxError, + .size = strlen(cppSourceWithSyntaxError) + }; + RcnCountResult result = rcnCountLogicalLines(RCN_LANG_CPP, source); + TEST_ASSERT_TRUE(result.state.ok); + TEST_ASSERT_EQUAL_INT(RCN_ERR_NONE, result.state.errorCode); + TEST_ASSERT_NULL(result.state.errorMessage); + TEST_ASSERT_EQUAL_INT(2, result.count); +} + +void testCppLogicalLineCountStrictFailsWithSyntaxError(void) { + RcnSourceText source = { + .text = cppSourceWithSyntaxError, + .size = strlen(cppSourceWithSyntaxError) + }; + RcnCountResult result = rcnCountLogicalLinesStrict(RCN_LANG_CPP, source); + TEST_ASSERT_FALSE(result.state.ok); + TEST_ASSERT_EQUAL_INT(0, result.count); + TEST_ASSERT_EQUAL_INT(RCN_ERR_SYNTAX_ERROR, result.state.errorCode); + TEST_ASSERT_EQUAL_STRING( + "Syntax error detected in source code", + result.state.errorMessage + ); +} + +void testCppPhysicalLineCountIsCorrect(void) { + RcnSourceFile* file = newSourceFile(TEST_SAMPLE); + readSourceFileContent(file); + RcnCountResult result = rcnCountPhysicalLines(file->content); + freeSourceFile(file); + TEST_ASSERT_TRUE(result.state.ok); + TEST_ASSERT_EQUAL_INT(359, result.count); + TEST_ASSERT_EQUAL_INT(RCN_ERR_NONE, result.state.errorCode); + TEST_ASSERT_NULL(result.state.errorMessage); +} + +void testCppPhysicalLineCountWithSyntacticallyIncorrectCode(void) { + RcnSourceText source = { + .text = cppSourceWithSyntaxError, + .size = strlen(cppSourceWithSyntaxError) + }; + RcnCountResult result = rcnCountPhysicalLines(source); + TEST_ASSERT_TRUE(result.state.ok); + TEST_ASSERT_EQUAL_INT(5, result.count); + TEST_ASSERT_EQUAL_INT(RCN_ERR_NONE, result.state.errorCode); + TEST_ASSERT_NULL(result.state.errorMessage); +} + +void testCppLogicalLineCountMarksAreCorrect(void) { + RcnSourceFile* file = newSourceFile(TEST_SAMPLE); + RcnSourceFile* goldenSample = newSourceFile(TEST_SAMPLE_ANNOTATED); + readSourceFileContent(file); + readSourceFileContent(goldenSample); + RcnSourceText actual = rcnMarkLogicalLinesInSourceText( + RCN_LANG_CPP, + file->content + ); + TEST_ASSERT_NOT_NULL(actual.text); + TEST_ASSERT_EQUAL_INT(11706, actual.size); + TEST_ASSERT_EQUAL_STRING(goldenSample->content.text, actual.text); + freeSourceFile(file); + freeSourceFile(goldenSample); + free(actual.text); +} + +void testCppLogicalLineCountMarksForFilePathInput(void) { + char* path = TEST_SAMPLE; + RcnSourceFile* goldenSample = newSourceFile(TEST_SAMPLE_ANNOTATED); + readSourceFileContent(goldenSample); + RcnSourceText annotated = rcnMarkLogicalLinesInFile(path); + TEST_ASSERT_NOT_NULL(annotated.text); + TEST_ASSERT_EQUAL_INT(11706, annotated.size); + TEST_ASSERT_EQUAL_STRING(goldenSample->content.text, annotated.text); + freeSourceFile(goldenSample); + rcnFreeSourceText(&annotated); +} + +void testCppCountAllIsCorrect(void) { + RcnCountStatistics* stats = rcnCreateCountStatistics(TEST_RES_DIR); + RcnStatOptions options = {0}; + options.formats = RCN_OPT_LANG_CPP; + rcnCount(stats, options); + TEST_ASSERT_TRUE(stats->state.ok); + TEST_ASSERT_EQUAL_INT(RCN_ERR_NONE, stats->state.errorCode); + TEST_ASSERT_NULL(stats->state.errorMessage); + TEST_ASSERT_EQUAL_INT(591, stats->totalLogicalLines); + TEST_ASSERT_EQUAL_INT(918, stats->totalPhysicalLines); + TEST_ASSERT_EQUAL_INT(24662, stats->totalSourceSize); + TEST_ASSERT_EQUAL_INT(591, stats->logicalLines[RCN_LANG_CPP]); + TEST_ASSERT_EQUAL_INT(918, stats->physicalLines[RCN_LANG_CPP]); + TEST_ASSERT_EQUAL_INT(24662, stats->sourceSize[RCN_LANG_CPP]); + TEST_ASSERT_EQUAL_INT(3, stats->count.size); + TEST_ASSERT_EQUAL_INT(3, stats->count.sizeProcessed); + RcnSourceFile* filelist = stats->count.files; + TEST_ASSERT_EQUAL_STRING("Sample.cpp", filelist[0].name); + TEST_ASSERT_EQUAL_STRING("SampleAnnotated.cpp", filelist[1].name); + TEST_ASSERT_EQUAL_STRING("SampleMinFormatting.cpp", filelist[2].name); + rcnFreeCountStatistics(stats); +} + +void testCppLogicalLineCountForMinimizedFormattingIsCorrect(void) { + RcnSourceFile* file = newSourceFile(TEST_SAMPLE_MIN_FORMATTING); + readSourceFileContent(file); + RcnCountResult result = rcnCountLogicalLines( + RCN_LANG_CPP, + file->content + ); + freeSourceFile(file); + TEST_ASSERT_TRUE(result.state.ok); + TEST_ASSERT_EQUAL_INT(197, result.count); + TEST_ASSERT_EQUAL_INT(RCN_ERR_NONE, result.state.errorCode); + TEST_ASSERT_NULL(result.state.errorMessage); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(testCppLogicalLineCountIsCorrect); + RUN_TEST(testCppLogicalLineCountIsLenientWithSyntaxError); + RUN_TEST(testCppLogicalLineCountStrictFailsWithSyntaxError); + RUN_TEST(testCppPhysicalLineCountIsCorrect); + RUN_TEST(testCppPhysicalLineCountWithSyntacticallyIncorrectCode); + RUN_TEST(testCppLogicalLineCountMarksAreCorrect); + RUN_TEST(testCppLogicalLineCountMarksForFilePathInput); + RUN_TEST(testCppCountAllIsCorrect); + RUN_TEST(testCppLogicalLineCountForMinimizedFormattingIsCorrect); + return UNITY_END(); +} diff --git a/src/scount/c/print.c b/src/scount/c/print.c index 5d6334d..1fa29dc 100644 --- a/src/scount/c/print.c +++ b/src/scount/c/print.c @@ -31,7 +31,7 @@ #define MAX_DIGITS_INT64 22ULL static const size_t BUFFER_CAPACITY_INIT = 1024; -static const size_t LARGE_RESULT_THRESHOLD = 32; +static const size_t LARGE_RESULT_THRESHOLD = 40; static const int WIDTH_COL0 = 26; // File static const int WIDTH_COL1 = 11; // LLC @@ -639,6 +639,10 @@ static void prSummaryRows( label = "Python"; hasLogicalLines = true; break; + case RCN_LANG_CPP: + label = "C++"; + hasLogicalLines = true; + break; case RCN_LANG_JAVASCRIPT: label = "JavaScript"; hasLogicalLines = true; diff --git a/src/scount/tests/functionality/res/expected/mixed.txt b/src/scount/tests/functionality/res/expected/mixed.txt index 3a5759d..1c6a68c 100644 --- a/src/scount/tests/functionality/res/expected/mixed.txt +++ b/src/scount/tests/functionality/res/expected/mixed.txt @@ -1,5 +1,5 @@ Directory: mixed -Scanned files: 32 +Scanned files: 34 o---------- File ----------o--- LLC ---o--- PHL ---o--- WRD ---o--- CHR ---o--- SZE ---o | CMakeLists.txt | n/a | 10 | 21 | 250 | 250 | @@ -8,6 +8,7 @@ Scanned files: 32 | Sample2.java | 4 | 13 | 38 | 286 | 286 | | sample1.R | n/a | 4 | 9 | 28 | 28 | | sample1.c | 4 | 10 | 29 | 180 | 180 | + | sample1.cpp | 4 | 10 | 33 | 201 | 201 | | sample1.css | n/a | 8 | 17 | 98 | 98 | | sample1.html | n/a | 9 | 14 | 121 | 121 | | sample1.js | 5 | 10 | 22 | 184 | 184 | @@ -22,6 +23,7 @@ Scanned files: 32 | sample1.yaml | n/a | 6 | 11 | 73 | 73 | | sample2.bash | 5 | 11 | 25 | 153 | 153 | | sample2.c | 5 | 11 | 33 | 219 | 219 | + | sample2.cpp | 5 | 11 | 41 | 256 | 256 | | sample2.css | n/a | 11 | 21 | 136 | 136 | | sample2.html | n/a | 11 | 16 | 151 | 151 | | sample2.js | 5 | 9 | 15 | 147 | 147 | @@ -51,12 +53,13 @@ Summary: | C | 9 | 21 | 62 | 399 | 399 | | Java | 7 | 25 | 72 | 519 | 519 | | Python | 13 | 25 | 60 | 493 | 493 | + | C++ | 9 | 21 | 74 | 457 | 457 | | JavaScript | 10 | 19 | 37 | 331 | 331 | | TypeScript | 11 | 27 | 51 | 436 | 436 | | R | n/a | 8 | 18 | 86 | 86 | | Shell | 8 | 18 | 41 | 250 | 250 | o==========================o===========o===========o===========o===========o===========o - | Total: | 58 | 293 | 655 | 5069 | 5069 | + | Total: | 67 | 314 | 729 | 5526 | 5526 | o==========================o===========o===========o===========o===========o===========o diff --git a/src/scount/tests/functionality/res/mixed/sample1.cpp b/src/scount/tests/functionality/res/mixed/sample1.cpp new file mode 100644 index 0000000..c2cfb18 --- /dev/null +++ b/src/scount/tests/functionality/res/mixed/sample1.cpp @@ -0,0 +1,10 @@ +// +// Compile with: g++ sample1.cpp -o sample +// + +#include + +int main(int argc, char** argv) { + std::cout << "This is a first sample program written in C++" << std::endl; + return 0; +} diff --git a/src/scount/tests/functionality/res/mixed/sample2.cpp b/src/scount/tests/functionality/res/mixed/sample2.cpp new file mode 100644 index 0000000..16a945f --- /dev/null +++ b/src/scount/tests/functionality/res/mixed/sample2.cpp @@ -0,0 +1,11 @@ +// +// Compile with: g++ sample2.cpp -o sample +// + +#include + +int main(int argc, char** argv) { + std::cout << "This is a second sample program written in C++" << std::endl; + std::cout << "Here is another line" << std::endl; + return 0; +} diff --git a/src/scount/tests/unit/c/test_print.c b/src/scount/tests/unit/c/test_print.c index 9a4aac1..6b4e57d 100644 --- a/src/scount/tests/unit/c/test_print.c +++ b/src/scount/tests/unit/c/test_print.c @@ -174,14 +174,14 @@ void testPrintMultiResultForDirectoryInputWithManyFiles(void) { ); RcnCountStatistics* stats = mkStats( "SomeFile.java", - 35, 1, 2, 3, 4, 5 + 45, 1, 2, 3, 4, 5 ); PrintBuffer buffer = mkBufferAllMetrics(); printResultsMultiple("/some/path/to/myDirectory", stats, &buffer); TEST_ASSERT_NOT_NULL(buffer.text); TEST_ASSERT_TRUE(strlen(buffer.text) > 1); TEST_ASSERT_NOT_NULL(strstr(buffer.text, "Directory: myDirectory\n")); - TEST_ASSERT_NOT_NULL(strstr(buffer.text, "Scanned files: 35\n")); + TEST_ASSERT_NOT_NULL(strstr(buffer.text, "Scanned files: 45\n")); TEST_ASSERT_NOT_NULL(strstr(buffer.text, expectedFileRow)); TEST_ASSERT_NOT_NULL(strstr(buffer.text, expectedEllipsisRow)); free(buffer.text);