diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 6772a81a38c..6e02825ca08 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -544,6 +544,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } } + else if (std::strcmp(argv[i], "--cpp-header-probe") == 0) { + mSettings.cppHeaderProbe = true; + } + // Show --debug output after the first simplifications else if (std::strcmp(argv[i], "--debug") == 0 || std::strcmp(argv[i], "--debug-normal") == 0) @@ -887,6 +891,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a return Result::Fail; } + else if (std::strcmp(argv[i], "--no-cpp-header-probe") == 0) { + mSettings.cppHeaderProbe = false; + } + // Write results in file else if (std::strncmp(argv[i], "--output-file=", 14) == 0) mSettings.outputFile = Path::simplifyPath(Path::fromNativeSeparators(argv[i] + 14)); diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 7d3e3ea8e51..345649cfebb 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -182,7 +182,7 @@ static void createDumpFile(const Settings& settings, case Standards::Language::None: { // TODO: error out on unknown language? - const Standards::Language lang = Path::identify(filename); + const Standards::Language lang = Path::identify(filename, settings.cppHeaderProbe); if (lang == Standards::Language::CPP) language = " language=\"cpp\""; else if (lang == Standards::Language::C) @@ -420,7 +420,7 @@ unsigned int CppCheck::checkClang(const std::string &path) mErrorLogger.reportOut(std::string("Checking ") + path + " ...", Color::FgGreen); // TODO: this ignores the configured language - const bool isCpp = Path::identify(path) == Standards::Language::CPP; + const bool isCpp = Path::identify(path, mSettings.cppHeaderProbe) == Standards::Language::CPP; const std::string langOpt = isCpp ? "-x c++" : "-x c"; const std::string analyzerInfo = mSettings.buildDir.empty() ? std::string() : AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, path, emptyString); const std::string clangcmd = analyzerInfo + ".clang-cmd"; @@ -784,7 +784,7 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string TokenList tokenlist(&mSettings); std::istringstream istr2(code); // TODO: asserts when file has unknown extension - tokenlist.createTokens(istr2, Path::identify(*files.begin())); // TODO: check result? + tokenlist.createTokens(istr2, Path::identify(*files.begin(), false)); // TODO: check result? executeRules("define", tokenlist); } #endif diff --git a/lib/path.cpp b/lib/path.cpp index 3857517fc7d..79af3478247 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -20,11 +20,17 @@ #undef __STRICT_ANSI__ #endif +//#define LOG_EMACS_MARKER + #include "path.h" #include "utils.h" #include +#include #include +#ifdef LOG_EMACS_MARKER +#include +#endif #include #include #include @@ -210,16 +216,104 @@ static const std::unordered_set header_exts = { bool Path::acceptFile(const std::string &path, const std::set &extra) { bool header = false; - return (identify(path, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end(); + return (identify(path, false, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end(); } -Standards::Language Path::identify(const std::string &path, bool *header) +static bool hasEmacsCppMarker(const char* path) +{ + // TODO: identify is called three times for each file + // Preprocessor::loadFiles() -> createDUI() + // Preprocessor::preprocess() -> createDUI() + // TokenList::createTokens() -> TokenList::determineCppC() +#ifdef LOG_EMACS_MARKER + std::cout << path << '\n'; +#endif + + FILE *fp = fopen(path, "rt"); + if (!fp) + return false; + std::string buf(128, '\0'); + { + // TODO: read the whole first line only + const char * const res = fgets(const_cast(buf.data()), buf.size(), fp); + fclose(fp); + fp = nullptr; + if (!res) + return false; // failed to read file + } + // TODO: replace with regular expression + const auto pos1 = buf.find("-*-"); + if (pos1 == std::string::npos) + return false; // no start marker + const auto pos_nl = buf.find_first_of("\r\n"); + if (pos_nl != std::string::npos && (pos_nl < pos1)) { +#ifdef LOG_EMACS_MARKER + std::cout << path << " - Emacs marker not on the first line" << '\n'; +#endif + return false; // not on first line + } + const auto pos2 = buf.find("-*-", pos1 + 3); + // TODO: make sure we have read the whole line before bailing out + if (pos2 == std::string::npos) { +#ifdef LOG_EMACS_MARKER + std::cout << path << " - Emacs marker not terminated" << '\n'; +#endif + return false; // no end marker + } +#ifdef LOG_EMACS_MARKER + std::cout << "Emacs marker: '" << buf.substr(pos1, (pos2 + 3) - pos1) << "'" << '\n'; +#endif + // TODO: support /* */ comments + const std::string buf_trim = trim(buf); // trim whitespaces + if (buf_trim[0] != '/' || buf_trim[1] != '/') { +#ifdef LOG_EMACS_MARKER + std::cout << path << " - Emacs marker not in a comment: '" << buf.substr(pos1, (pos2 + 3) - pos1) << "'" << '\n'; +#endif + return false; // not a comment + } + + // there are more variations with lowercase and no whitespaces + // -*- C++ -*- + // -*- Mode: C++; -*- + // -*- Mode: C++; c-basic-offset: 8 -*- + std::string marker = trim(buf.substr(pos1 + 3, pos2 - pos1 - 3), " ;"); + // cut off additional attributes + const auto pos_semi = marker.find(';'); + if (pos_semi != std::string::npos) + marker.resize(pos_semi); + findAndReplace(marker, "mode:", ""); + findAndReplace(marker, "Mode:", ""); + marker = trim(marker); + if (marker == "C++" || marker == "c++") { + // NOLINTNEXTLINE(readability-simplify-boolean-expr) - TODO: FP + return true; // C++ marker found + } + + //if (marker == "C" || marker == "c") + // return false; +#ifdef LOG_EMACS_MARKER + std::cout << path << " - unmatched Emacs marker: '" << marker << "'" << '\n'; +#endif + + return false; // marker is not a C++ one +} + +Standards::Language Path::identify(const std::string &path, bool cppHeaderProbe, bool *header) { // cppcheck-suppress uninitvar - TODO: FP if (header) *header = false; std::string ext = getFilenameExtension(path); + // standard library headers have no extension + if (cppHeaderProbe && ext.empty()) { + if (hasEmacsCppMarker(path.c_str())) { + if (header) + *header = true; + return Standards::Language::CPP; + } + return Standards::Language::None; + } if (ext == ".C") return Standards::Language::CPP; if (c_src_exts.find(ext) != c_src_exts.end()) @@ -230,7 +324,9 @@ Standards::Language Path::identify(const std::string &path, bool *header) if (ext == ".h") { if (header) *header = true; - return Standards::Language::C; // treat as C for now + if (cppHeaderProbe && hasEmacsCppMarker(path.c_str())) + return Standards::Language::CPP; + return Standards::Language::C; } if (cpp_src_exts.find(ext) != cpp_src_exts.end()) return Standards::Language::CPP; @@ -245,7 +341,7 @@ Standards::Language Path::identify(const std::string &path, bool *header) bool Path::isHeader(const std::string &path) { bool header; - (void)Path::identify(path, &header); + (void)identify(path, false, &header); return header; } diff --git a/lib/path.h b/lib/path.h index 59af20b1154..d4955a54155 100644 --- a/lib/path.h +++ b/lib/path.h @@ -163,10 +163,11 @@ class CPPCHECKLIB Path { /** * @brief Identify the language based on the file extension * @param path filename to check. path info is optional + * @param cppHeaderProbe check optional Emacs marker to identify extension-less and *.h files as C++ * @param header if provided indicates if the file is a header * @return the language type */ - static Standards::Language identify(const std::string &path, bool *header = nullptr); + static Standards::Language identify(const std::string &path, bool cppHeaderProbe, bool *header = nullptr); /** * @brief Get filename without a directory path part. diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp index 70e2e560770..bbd0f67ea57 100644 --- a/lib/preprocessor.cpp +++ b/lib/preprocessor.cpp @@ -684,7 +684,7 @@ static simplecpp::DUI createDUI(const Settings &mSettings, const std::string &cf dui.includes = mSettings.userIncludes; // --include // TODO: use mSettings.standards.stdValue instead // TODO: error out on unknown language? - const Standards::Language lang = Path::identify(filename); + const Standards::Language lang = Path::identify(filename, mSettings.cppHeaderProbe); if (lang == Standards::Language::CPP) { dui.std = mSettings.standards.getCPP(); splitcfg(mSettings.platform.getLimitsDefines(Standards::getCPP(dui.std)), dui.defines, ""); diff --git a/lib/settings.h b/lib/settings.h index 750674abd68..2c356d827dc 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -167,6 +167,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** cppcheck.cfg: About text */ std::string cppcheckCfgAbout; + /** @brief check Emacs marker to detect extension-less and *.h files as C++ */ + bool cppHeaderProbe{}; + /** @brief Are we running from DACA script? */ bool daca{}; diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 8e70fb32ad6..d84fe688413 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -8067,7 +8067,7 @@ void Tokenizer::unmatchedToken(const Token *tok) const void Tokenizer::syntaxErrorC(const Token *tok, const std::string &what) const { printDebugOutput(0); - throw InternalError(tok, "Code '"+what+"' is invalid C code. Use --std or --language to configure the language.", InternalError::SYNTAX); + throw InternalError(tok, "Code '"+what+"' is invalid C code.", "Use --std, -x or --language to enforce C++. Or --cpp-header-probe to identify C++ headers via the Emacs marker.", InternalError::SYNTAX); } void Tokenizer::unknownMacroError(const Token *tok1) const diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp index 98511b08fd2..71ad1fadf76 100644 --- a/lib/tokenlist.cpp +++ b/lib/tokenlist.cpp @@ -96,7 +96,7 @@ void TokenList::determineCppC() // only try to determine if it wasn't enforced if (mLang == Standards::Language::None) { ASSERT_LANG(!getSourceFilePath().empty()); - mLang = Path::identify(getSourceFilePath()); + mLang = Path::identify(getSourceFilePath(), mSettings ? mSettings->cppHeaderProbe : false); // TODO: cannot enable assert as this might occur for unknown extensions //ASSERT_LANG(mLang != Standards::Language::None); if (mLang == Standards::Language::None) { diff --git a/releasenotes.txt b/releasenotes.txt index 917d765a4bb..76c2f804e8e 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -17,4 +17,6 @@ Deprecations: - Other: -- Add support for 'CLICOLOR_FORCE'/'NO_COLOR' environment variables to force/disable ANSI color output for diagnostics. \ No newline at end of file +- Add support for 'CLICOLOR_FORCE'/'NO_COLOR' environment variables to force/disable ANSI color output for diagnostics. +- added command-line option `--cpp-header-probe` (and `--no-cpp-header-probe`) to probe headers and extension-less files for Emacs marker (see https://trac.cppcheck.net/ticket/10692 for more details) +- \ No newline at end of file diff --git a/test/cli/other_test.py b/test/cli/other_test.py index b5e02083ed5..99bb3b24f6e 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -1487,3 +1487,32 @@ def test_markup_lang(tmpdir): exitcode, stdout, _ = cppcheck(args) assert exitcode == 0, stdout + + +def test_cpp_probe(tmpdir): + test_file = os.path.join(tmpdir, 'test.h') + with open(test_file, 'wt') as f: + f.writelines([ + 'class A {};' + ]) + + args = ['-q', '--template=simple', '--cpp-header-probe', '--verbose', test_file] + err_lines = [ + # TODO: fix that awkward format + "{}:1:1: error: Code 'classA{{' is invalid C code.: Use --std, -x or --language to enforce C++. Or --cpp-header-probe to identify C++ headers via the Emacs marker. [syntaxError]".format(test_file) + ] + + assert_cppcheck(args, ec_exp=0, err_exp=err_lines, out_exp=[]) + + +def test_cpp_probe_2(tmpdir): + test_file = os.path.join(tmpdir, 'test.h') + with open(test_file, 'wt') as f: + f.writelines([ + '// -*- C++ -*-', + 'class A {};' + ]) + + args = ['-q', '--template=simple', '--cpp-header-probe', test_file] + + assert_cppcheck(args, ec_exp=0, err_exp=[], out_exp=[]) diff --git a/test/cli/testutils.py b/test/cli/testutils.py index d6c7db52a9f..f33f2c507b8 100644 --- a/test/cli/testutils.py +++ b/test/cli/testutils.py @@ -220,7 +220,7 @@ def assert_cppcheck(args, ec_exp=None, out_exp=None, err_exp=None, env=None): assert exitcode == ec_exp, stdout if out_exp is not None: out_lines = stdout.splitlines() - assert out_lines == out_exp, stdout + assert out_lines == out_exp, out_lines if err_exp is not None: err_lines = stderr.splitlines() - assert err_lines == err_exp, stderr + assert err_lines == err_exp, err_lines diff --git a/test/fixture.h b/test/fixture.h index 96aadc7362e..d0820a933c5 100644 --- a/test/fixture.h +++ b/test/fixture.h @@ -294,6 +294,7 @@ class TestFixture : public ErrorLogger { // *INDENT-ON* #define ASSERT_EQUALS_WITHOUT_LINENUMBERS( EXPECTED, ACTUAL ) assertEqualsWithoutLineNumbers(__FILE__, __LINE__, EXPECTED, ACTUAL) #define ASSERT_EQUALS_DOUBLE( EXPECTED, ACTUAL, TOLERANCE ) assertEqualsDouble(__FILE__, __LINE__, EXPECTED, ACTUAL, TOLERANCE) +#define ASSERT_EQUALS_LOC_MSG( EXPECTED, ACTUAL, MSG, FILE_, LINE_ ) assertEquals(FILE_, LINE_, EXPECTED, ACTUAL, MSG) #define ASSERT_EQUALS_MSG( EXPECTED, ACTUAL, MSG ) assertEquals(__FILE__, __LINE__, EXPECTED, ACTUAL, MSG) #define ASSERT_EQUALS_ENUM( EXPECTED, ACTUAL ) if (!assertEqualsEnum(__FILE__, __LINE__, (EXPECTED), (ACTUAL))) return #define ASSERT_THROW( CMD, EXCEPTION ) do { try { CMD; assertThrowFail(__FILE__, __LINE__); } catch (const EXCEPTION&) {} catch (...) { assertThrowFail(__FILE__, __LINE__); } } while (false) diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index e3cb04e7916..5748fc9ad27 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -387,6 +387,10 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(checkLevelNormal); TEST_CASE(checkLevelExhaustive); TEST_CASE(checkLevelUnknown); + TEST_CASE(cppHeaderProbe); + TEST_CASE(cppHeaderProbe2); + TEST_CASE(noCppHeaderProbe); + TEST_CASE(noCppHeaderProbe2); TEST_CASE(ignorepaths1); TEST_CASE(ignorepaths2); @@ -2610,6 +2614,34 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS("cppcheck: error: unknown '--check-level' value 'default'.\n", logger->str()); } + void cppHeaderProbe() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--cpp-header-probe", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS(true, settings->cppHeaderProbe); + } + + void cppHeaderProbe2() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--no-cpp-header-probe", "--cpp-header-probe", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv)); + ASSERT_EQUALS(true, settings->cppHeaderProbe); + } + + void noCppHeaderProbe() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--no-cpp-header-probe", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv)); + ASSERT_EQUALS(false, settings->cppHeaderProbe); + } + + void noCppHeaderProbe2() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--cpp-header-probe", "--no-cpp-header-probe", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv)); + ASSERT_EQUALS(false, settings->cppHeaderProbe); + } + void ignorepaths1() { REDIRECT; const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"}; diff --git a/test/testpath.cpp b/test/testpath.cpp index 9a61b0959d2..3da3f9e3670 100644 --- a/test/testpath.cpp +++ b/test/testpath.cpp @@ -45,6 +45,7 @@ class TestPath : public TestFixture { TEST_CASE(sameFileName); TEST_CASE(getFilenameExtension); TEST_CASE(identify); + TEST_CASE(identifyWithCppProbe); TEST_CASE(is_header); } @@ -213,80 +214,153 @@ class TestPath : public TestFixture { Standards::Language lang; bool header; - ASSERT_EQUALS(Standards::Language::None, Path::identify("")); - ASSERT_EQUALS(Standards::Language::None, Path::identify("c")); - ASSERT_EQUALS(Standards::Language::None, Path::identify("cpp")); - ASSERT_EQUALS(Standards::Language::None, Path::identify("h")); - ASSERT_EQUALS(Standards::Language::None, Path::identify("hpp")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("", false)); + ASSERT_EQUALS(Standards::Language::None, Path::identify("c", false)); + ASSERT_EQUALS(Standards::Language::None, Path::identify("cpp", false)); + ASSERT_EQUALS(Standards::Language::None, Path::identify("h", false)); + ASSERT_EQUALS(Standards::Language::None, Path::identify("hpp", false)); // TODO: what about files starting with a "."? - //ASSERT_EQUALS(Standards::Language::None, Path::identify(".c")); - //ASSERT_EQUALS(Standards::Language::None, Path::identify(".cpp")); - //ASSERT_EQUALS(Standards::Language::None, Path::identify(".h")); - //ASSERT_EQUALS(Standards::Language::None, Path::identify(".hpp")); + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".c", false)); + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".cpp", false)); + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".h", false)); + //ASSERT_EQUALS(Standards::Language::None, Path::identify(".hpp", false)); // C - ASSERT_EQUALS(Standards::Language::C, Path::identify("index.c")); - ASSERT_EQUALS(Standards::Language::C, Path::identify("index.cl")); - ASSERT_EQUALS(Standards::Language::C, Path::identify("C:\\foo\\index.c")); - ASSERT_EQUALS(Standards::Language::C, Path::identify("/mnt/c/foo/index.c")); + ASSERT_EQUALS(Standards::Language::C, Path::identify("index.c", false)); + ASSERT_EQUALS(Standards::Language::C, Path::identify("index.cl", false)); + ASSERT_EQUALS(Standards::Language::C, Path::identify("C:\\foo\\index.c", false)); + ASSERT_EQUALS(Standards::Language::C, Path::identify("/mnt/c/foo/index.c", false)); // In unix .C is considered C++ #ifdef _WIN32 - ASSERT_EQUALS(Standards::Language::C, Path::identify("C:\\foo\\index.C")); + ASSERT_EQUALS(Standards::Language::C, Path::identify("C:\\foo\\index.C", false)); #endif - lang = Path::identify("index.c", &header); + lang = Path::identify("index.c", false, &header); ASSERT_EQUALS(Standards::Language::C, lang); ASSERT_EQUALS(false, header); // C++ - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cpp")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cxx")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cc")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.c++")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.tpp")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.txx")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.ipp")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.ixx")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("C:\\foo\\index.cpp")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("C:\\foo\\index.Cpp")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("/mnt/c/foo/index.cpp")); - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("/mnt/c/foo/index.Cpp")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cpp", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cxx", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.cc", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.c++", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.tpp", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.txx", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.ipp", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.ixx", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("C:\\foo\\index.cpp", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("C:\\foo\\index.Cpp", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("/mnt/c/foo/index.cpp", false)); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("/mnt/c/foo/index.Cpp", false)); // TODO: check for case-insenstive filesystem instead // In unix .C is considered C++ #if !defined(_WIN32) && !(defined(__APPLE__) && defined(__MACH__)) - ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.C")); + ASSERT_EQUALS(Standards::Language::CPP, Path::identify("index.C", false)); #else - ASSERT_EQUALS(Standards::Language::C, Path::identify("index.C")); + ASSERT_EQUALS(Standards::Language::C, Path::identify("index.C", false)); #endif - lang = Path::identify("index.cpp", &header); + lang = Path::identify("index.cpp", false, &header); ASSERT_EQUALS(Standards::Language::CPP, lang); ASSERT_EQUALS(false, header); // headers - lang = Path::identify("index.h", &header); + lang = Path::identify("index.h", false, &header); ASSERT_EQUALS(Standards::Language::C, lang); ASSERT_EQUALS(true, header); - lang = Path::identify("index.hpp", &header); + lang = Path::identify("index.hpp", false, &header); ASSERT_EQUALS(Standards::Language::CPP, lang); ASSERT_EQUALS(true, header); - lang = Path::identify("index.hxx", &header); + lang = Path::identify("index.hxx", false, &header); ASSERT_EQUALS(Standards::Language::CPP, lang); ASSERT_EQUALS(true, header); - lang = Path::identify("index.h++", &header); + lang = Path::identify("index.h++", false, &header); ASSERT_EQUALS(Standards::Language::CPP, lang); ASSERT_EQUALS(true, header); - lang = Path::identify("index.hh", &header); + lang = Path::identify("index.hh", false, &header); ASSERT_EQUALS(Standards::Language::CPP, lang); ASSERT_EQUALS(true, header); - ASSERT_EQUALS(Standards::Language::None, Path::identify("index.header")); - ASSERT_EQUALS(Standards::Language::None, Path::identify("index.htm")); - ASSERT_EQUALS(Standards::Language::None, Path::identify("index.html")); + ASSERT_EQUALS(Standards::Language::None, Path::identify("index.header", false)); + ASSERT_EQUALS(Standards::Language::None, Path::identify("index.htm", false)); + ASSERT_EQUALS(Standards::Language::None, Path::identify("index.html", false)); + } + +#define identifyWithCppProbeInternal(...) identifyWithCppProbeInternal_(__FILE__, __LINE__, __VA_ARGS__) + void identifyWithCppProbeInternal_(const char* file, int line, const std::string& filename, const std::string& marker, Standards::Language std) const + { + const ScopedFile f(filename, marker); + ASSERT_EQUALS_LOC_MSG(std, Path::identify(f.path(), true), filename + "\n" + marker, file, line); + } + + void identifyWithCppProbe() const + { + const std::list markers_cpp = { + "// -*- C++ -*-", + "// -*-C++-*-", + "// -*- Mode: C++; -*-", + "// -*-Mode: C++;-*-", + "// -*- Mode:C++; -*-", + "// -*-Mode:C++;-*-", + "// -*- Mode: C++ -*-", + "// -*-Mode: C++-*-", + "// -*- Mode:C++ -*-", + "// -*-Mode:C++-*-", + "// -*- Mode: C++; c-basic-offset: 8 -*-", + "// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-", + + "// -*- c++ -*-", + "// -*- mode: c++; -*-", + + //"/* -*- C++ -*- */" + + "//-*- C++ -*-", + " //-*- C++ -*-", + "\t//-*- C++ -*-", + "\t //-*- C++ -*-", + " \t//-*- C++ -*-", + "// -----*- C++ -*-----", + "// comment-*- C++ -*-comment", + "//-*- C++ -*-\r// comment", + "//-*- C++ -*-\n// comment", + "//-*- C++ -*-\r\n// comment", + + //"/* -*-C++-*- */" + //"/*-*-C++-*-*/" + }; + + for (const auto& f : { "cppprobe.h", "cppprobe" }) { + for (const auto& m : markers_cpp) { + identifyWithCppProbeInternal(f, m, Standards::Language::CPP); + } + } + + const std::list markers_c = { + "-*- C++ -*-", // needs to be in comment + "// -*- C++", // no end marker + "// -*- C++ --*-", // incorrect end marker + "// -*- C++/-*-", // unexpected character + "// comment\n// -*-C", // not on the first line + "// comment\r// -*-C", // not on the first line + "// comment\r\n// -*-C", // not on the first line + "// -*- C -*-", + "// -*- Mode: C; -*-", + "// -*- f90 -*-", + "// -*- fortran -*-", + "// -*- c-basic-offset: 2 -*-", + "// -*- c-basic-offset:4; indent-tabs-mode:nil -*-" + }; + + for (const auto& m : markers_c) { + identifyWithCppProbeInternal("cppprobe.h", m, Standards::Language::C); + } + for (const auto& m : markers_c) { + identifyWithCppProbeInternal("cppprobe", m, Standards::Language::None); + } } void is_header() const {