From 5252f99c3d1cdc6e0b7a988d2c30c33f7cb95905 Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 8 Jul 2026 16:49:09 +0200 Subject: [PATCH 1/3] Add warning for no newline at end of file --- simplecpp.cpp | 13 +++++ simplecpp.h | 1 + test.cpp | 148 ++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 122 insertions(+), 40 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index d2c398c4..72807c0e 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -662,6 +662,7 @@ static const std::string COMMENT_END("*/"); void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, OutputList *outputList) { unsigned int multiline = 0U; + bool trailing_nl = true; const Token *oldLastToken = nullptr; @@ -671,6 +672,8 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (!stream.good()) break; + trailing_nl = false; + if (ch >= 0x80) { if (outputList) { simplecpp::Output err{ @@ -693,6 +696,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } else { location.line += multiline + 1; multiline = 0U; + trailing_nl = true; } if (!multiline) location.col = 1; @@ -961,6 +965,15 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location.adjust(currentToken); } + if (!trailing_nl && outputList) { + Output err{ + Output::NO_EOF_NEWLINE, + location, + "No newline at end of file." + }; + outputList->emplace_back(std::move(err)); + } + combineOperators(); } diff --git a/simplecpp.h b/simplecpp.h index 3d6fc526..9e997d2e 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -240,6 +240,7 @@ namespace simplecpp { SYNTAX_ERROR, PORTABILITY_BACKSLASH, UNHANDLED_CHAR_ERROR, + NO_EOF_NEWLINE, EXPLICIT_INCLUDE_NOT_FOUND, FILE_NOT_FOUND, DUI_ERROR diff --git a/test.cpp b/test.cpp index b13140aa..c813e889 100644 --- a/test.cpp +++ b/test.cpp @@ -203,6 +203,9 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::UNHANDLED_CHAR_ERROR: ostr << "unhandled_char_error,"; break; + case simplecpp::Output::Type::NO_EOF_NEWLINE: + ostr << "no_eof_newline,"; + break; case simplecpp::Output::Type::EXPLICIT_INCLUDE_NOT_FOUND: ostr << "explicit_include_not_found,"; break; @@ -224,15 +227,15 @@ static void backslash() // preprocessed differently simplecpp::OutputList outputList; - readfile("//123 \\\n456", &outputList); + readfile("//123 \\\n456\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("//123 \\ \n456", &outputList); + readfile("//123 \\ \n456\n", &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); outputList.clear(); - readfile("#define A \\\n123", &outputList); + readfile("#define A \\\n123\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("#define A \\ \n123", &outputList); + readfile("#define A \\ \n123\n", &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); } @@ -1406,7 +1409,7 @@ static void error4() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtoken = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtoken = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtoken, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -1419,7 +1422,7 @@ static void error5() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -1432,7 +1435,7 @@ static void error6() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error \n", toString(outputList)); } @@ -1444,7 +1447,7 @@ static void error7() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); } @@ -1456,7 +1459,7 @@ static void error8() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); } @@ -2986,9 +2989,9 @@ static void include11() // #669 - -include with preprocess() static void readfile_nullbyte() { - const char code[] = "ab\0cd"; + const char code[] = "ab\0cd\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("ab cd", readfile(code,sizeof(code), &outputList)); + ASSERT_EQUALS("ab cd", readfile(code,sizeof(code)-1, &outputList)); ASSERT_EQUALS(true, outputList.empty()); // should warning be written? } @@ -3103,7 +3106,7 @@ static void readfile_string_error() outputList.clear(); // Don't warn for a multiline define - readfile("#define A \"abs\\\n\"", &outputList); + readfile("#define A \"abs\\\n\"\n", &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -3115,11 +3118,11 @@ static void readfile_cpp14_number() static void readfile_unhandled_chars() { simplecpp::OutputList outputList; - readfile("// 你好世界", &outputList); + readfile("// 你好世界\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("s=\"你好世界\"", &outputList); + readfile("s=\"你好世界\"\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("int 你好世界=0;", &outputList); + readfile("int 你好世界=0;\n", &outputList); ASSERT_EQUALS("file0,1,unhandled_char_error,The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported.\n", toString(outputList)); } @@ -3139,6 +3142,70 @@ static void readfile_file_not_found() ASSERT_EQUALS("file0,0,file_not_found,File is missing: NotAFile\n", toString(outputList)); } +static void readfile_no_eof_newline() +{ + { + const char code[] = ""; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "\\\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "#define A"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "#define A\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "#define A\\"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "// comment"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "// comment\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "/* comment \n comment */"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,2,no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "/* comment \n comment */\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } +} + static void stringify1() { const char code_c[] = "#include \"A.h\"\n" @@ -3282,31 +3349,31 @@ static void unicode() { { const char code[] = "\xFE\xFF\x00\x31\x00\x32"; - ASSERT_EQUALS("12", readfile(code, sizeof(code))); + ASSERT_EQUALS("12", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x31\x00\x32\x00"; - ASSERT_EQUALS("12", readfile(code, sizeof(code))); + ASSERT_EQUALS("12", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x00\x2f\x00\x2f\x00\x0a\x00\x31"; - ASSERT_EQUALS("//\n1", readfile(code, sizeof(code))); + ASSERT_EQUALS("//\n1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x2f\x00\x2f\x00\x0a\x00\x31\x00"; - ASSERT_EQUALS("//\n1", readfile(code, sizeof(code))); + ASSERT_EQUALS("//\n1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x00\x22\x00\x61\x00\x22"; - ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code))); + ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x22\x00\x61\x00\x22\x00"; - ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code))); + ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code)-1)); } { const char code[] = "\xff\xfe\x0d\x00\x0a\x00\x2f\x00\x2f\x00\x31\x00\x0d\x00\x0a\x00"; - ASSERT_EQUALS("\n//1", readfile(code, sizeof(code))); + ASSERT_EQUALS("\n//1", readfile(code, sizeof(code)-1)); } } @@ -3314,35 +3381,35 @@ static void unicode_invalid() { { const char code[] = "\xFF"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFE\xFF\x31"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + const char code[] = "\xFE\xFF\x31\x00"; + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFF\xFE\x31"; - ASSERT_EQUALS("1", readfile(code, sizeof(code))); + const char code[] = "\xFF\xFE\x31\x00"; + ASSERT_EQUALS("1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x31\x32"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x31\x32"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFE\xFF\x00\x31\x00\x32\x33"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + const char code[] = "\xFE\xFF\x00\x31\x00\x32\x33\x00"; + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFF\xFE\x31\x00\x32\x00\x33"; - ASSERT_EQUALS("123", readfile(code, sizeof(code))); + const char code[] = "\xFF\xFE\x31\x00\x32\x00\x33\x00"; + ASSERT_EQUALS("123", readfile(code, sizeof(code)-1)); } } @@ -3710,19 +3777,19 @@ static void tokenlist_api() // sized array + size { char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { const char input[] = "code"; - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { unsigned char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { const unsigned char input[] = "code"; - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } #endif // !defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) // pointer via View @@ -3742,11 +3809,11 @@ static void tokenlist_api() // sized array + size via View/std::span { char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList({input,sizeof(input)},filenames,""); + simplecpp::TokenList({input,sizeof(input)-1},filenames,""); } { const char input[] = "code"; - simplecpp::TokenList({input,sizeof(input)},filenames,""); + simplecpp::TokenList({input,sizeof(input)-1},filenames,""); } // sized array { @@ -4199,6 +4266,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(readfile_unhandled_chars); TEST_CASE(readfile_error); TEST_CASE(readfile_file_not_found); + TEST_CASE(readfile_no_eof_newline); TEST_CASE(stringify1); From f00b2eb5b383993b4ba5c38ea586a55a0741f70b Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 8 Jul 2026 16:53:29 +0200 Subject: [PATCH 2/3] Rename to PORTABILITY_NO_EOF_NEWLINE and add case in main.cpp --- main.cpp | 3 +++ simplecpp.cpp | 2 +- simplecpp.h | 2 +- test.cpp | 16 ++++++++-------- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/main.cpp b/main.cpp index 685ada12..b112ad2f 100644 --- a/main.cpp +++ b/main.cpp @@ -275,6 +275,9 @@ int main(int argc, char **argv) case simplecpp::Output::PORTABILITY_BACKSLASH: std::cerr << "portability: "; break; + case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: + std::cerr << "portability: "; + break; case simplecpp::Output::UNHANDLED_CHAR_ERROR: std::cerr << "unhandled char error: "; break; diff --git a/simplecpp.cpp b/simplecpp.cpp index 72807c0e..d8f35729 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -967,7 +967,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (!trailing_nl && outputList) { Output err{ - Output::NO_EOF_NEWLINE, + Output::PORTABILITY_NO_EOF_NEWLINE, location, "No newline at end of file." }; diff --git a/simplecpp.h b/simplecpp.h index 9e997d2e..c9635a7c 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -239,8 +239,8 @@ namespace simplecpp { INCLUDE_NESTED_TOO_DEEPLY, SYNTAX_ERROR, PORTABILITY_BACKSLASH, + PORTABILITY_NO_EOF_NEWLINE, UNHANDLED_CHAR_ERROR, - NO_EOF_NEWLINE, EXPLICIT_INCLUDE_NOT_FOUND, FILE_NOT_FOUND, DUI_ERROR diff --git a/test.cpp b/test.cpp index c813e889..fa9c4386 100644 --- a/test.cpp +++ b/test.cpp @@ -200,12 +200,12 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::PORTABILITY_BACKSLASH: ostr << "portability_backslash,"; break; + case simplecpp::Output::Type::PORTABILITY_NO_EOF_NEWLINE: + ostr << "portability_no_eof_newline,"; + break; case simplecpp::Output::Type::UNHANDLED_CHAR_ERROR: ostr << "unhandled_char_error,"; break; - case simplecpp::Output::Type::NO_EOF_NEWLINE: - ostr << "no_eof_newline,"; - break; case simplecpp::Output::Type::EXPLICIT_INCLUDE_NOT_FOUND: ostr << "explicit_include_not_found,"; break; @@ -3160,13 +3160,13 @@ static void readfile_no_eof_newline() const char code[] = "\\\n"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, &outputList); - ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "#define A"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, &outputList); - ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "#define A\n"; @@ -3178,13 +3178,13 @@ static void readfile_no_eof_newline() const char code[] = "#define A\\"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, &outputList); - ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "// comment"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, &outputList); - ASSERT_EQUALS("file0,1,no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "// comment\n"; @@ -3196,7 +3196,7 @@ static void readfile_no_eof_newline() const char code[] = "/* comment \n comment */"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, &outputList); - ASSERT_EQUALS("file0,2,no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,2,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "/* comment \n comment */\n"; From 94f55008bd04781573fa401416f068f2c7625e3f Mon Sep 17 00:00:00 2001 From: glank Date: Wed, 8 Jul 2026 17:07:14 +0200 Subject: [PATCH 3/3] Fix integration tests --- integration_test.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/integration_test.py b/integration_test.py index c000b27b..42dda9b2 100644 --- a/integration_test.py +++ b/integration_test.py @@ -17,7 +17,7 @@ def __test_relative_header_create_header(dir, with_pragma_once=True): #error header_was_already_included #endif const int dummy = 1; - """) + """'\n') return header_file, "error: #error header_was_already_included" def __test_relative_header_create_source(dir, include1, include2, is_include1_sys=False, is_include2_sys=False, inv=False): @@ -31,7 +31,7 @@ def __test_relative_header_create_source(dir, include1, include2, is_include1_sy #undef TEST_H_INCLUDED #include {format_include(include1, is_include1_sys)} #include {format_include(include2, is_include2_sys)} - """) + """'\n') return src_file @pytest.mark.parametrize("with_pragma_once", (False, True)) @@ -201,27 +201,27 @@ def test_same_name_header(record_property, tmpdir): #include #include TEST - """) + """'\n') with open(header_a, "wt") as f: f.write(""" #include "same_name.h" - """) + """'\n') with open(header_b, "wt") as f: f.write(""" #include "same_name.h" - """) + """'\n') with open(same_name_a, "wt") as f: f.write(""" #define TEST E - """) + """'\n') with open(same_name_b, "wt") as f: f.write(""" #define TEST OK - """) + """'\n') args = [ format_include_path_arg(include_a), @@ -279,13 +279,13 @@ def test_pragma_once_matching(record_property, tmpdir): for n in names_to_test: f.write(f""" #include {n} - """); + """'\n'); with open(once_header, "wt") as f: f.write(f""" #pragma once ONCE - """); + """'\n'); args = [ format_include_path_arg(test_dir), @@ -463,7 +463,7 @@ def test_include_header_twice(tmpdir): #ifdef BBB # error BBB is defined #endif - """) + """'\n') test_file = os.path.join(tmpdir, 'test.c') with open(test_file, 'wt') as f: @@ -473,7 +473,7 @@ def test_include_header_twice(tmpdir): # define BBB # include "test.h" - """) + """'\n') args = [test_file] @@ -507,7 +507,7 @@ def test_define(record_property, tmpdir): # #589 def test_utf16_bom(tmpdir): test_file = os.path.join(tmpdir, "test.cpp") with open(test_file, 'wb') as f: - f.write(b'\xFF\xFE\x3B\x00') + f.write(b'\xFF\xFE\x3B\x00\x0A\x00') args = [test_file]