Ms Windows Resource Lint Source

last modified: April 19, 2011

Part of MsWindowsResourceLint. Save as rcLint.cpp, and create a project file for this:

//  this program parses RC files (as VC++ outputs them)
//  and reports on CUA style issues


    #include "rcLint.h"
    #include "test.h"
    #include <fstream>

  using std::stringstream;
  using std::ofstream;
  bool TestCase::all_tests_passed(true);
  TestCase::TestCases_t TestCase::cases;

    ////////////////////////////////////////////////////
    ///   tests on the token source


    TEST_(TestCase, pullNextToken)
    {

  Source aSource("a b\nc\n  d");

  string 
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("c", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("" , token);  //  EOF!

    },


    TEST_(TestCase, pullNextToken_comma)
    {

  Source aSource("a , b\nc, \n  d");

  string 
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("c", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!

    },


  struct
    TestTokens:  TestCase
    {

  void
    test_a_b_d(string input)
  {
  Source aSource(input);
  string 
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
    //    token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("c", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!
  },

    },;


    TEST_(TestTokens, elideComments)
    {   
  test_a_b_d("a b\n //c\n  d");
  test_a_b_d("a b\n//c \n  d");
  test_a_b_d("a b\n // c \"neither\" \n  d");
  test_a_b_d("a b\n // c \"neither\" \n  d//");
  test_a_b_d("//\na b\n // c \"neither\" \n  d//");
  test_a_b_d("//c\na b\n // c \"neither\" \n  d//");
  test_a_b_d("// c\na b\n // c \"neither\" \n  d//");
  test_a_b_d("//c \na b\n // c \"neither\" \n  d//");
  test_a_b_d("// \na b\n // c \"neither\" \n  d//");
  test_a_b_d(" // \na b\n // c \"neither\" \n  d//");
    },


    TEST_(TestTokens, elideStreamComments)
    {   
  test_a_b_d("a b\n /*c*/\n  d");
  test_a_b_d("a b\n/*c*/ \n  d");
  test_a_b_d("a b\n /* c \"neither\" */\n  d");
  test_a_b_d("a b\n /* c \"neither\" \n */ d//");
  test_a_b_d("//\na b\n /* c \"neither\" */ \n  d/**/");
  test_a_b_d("//c\na b\n // c \"neither\" \n  d/* */");
  test_a_b_d("/* c\n*/a b\n // c \"neither\" \n  d//");
  test_a_b_d("//c \na b\n // c \"neither\" \n  d//");
  test_a_b_d("// \na b\n // c \"neither\" \n  d//");
  test_a_b_d(" // \na b\n // c \"neither\" \n  d//");
    },


    TEST_(TestTokens, elidePreprocessorStatements)
    {
  test_a_b_d("a b\n #c\n  d");
  test_a_b_d("a b\n#c \n  d");
  test_a_b_d("a b\n # c \"neither\" \n  d");
  test_a_b_d("a b\n #\n  d");
  test_a_b_d("a b\n#\n  d");
  Source aSource("a b # \n  d");
  string 
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("a", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("b", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("#", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("d", token);
  token = aSource.pullNextToken();  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!
    },


    TEST_(TestCase, pullNextTokenString)
    {

  Source aSource("a b\n\"c\\n  d\"");

  string token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("a", token);

  token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("b", token);

  token = aSource.pullNextToken();
  string expect = "c\n  d";
  CPPUNIT_ASSERT_EQUAL(expect, token);

  token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!

    },


    TEST_(TestCase, pullNextTokenWithComma)
    {

  Source aSource("a b\n\"c  d\",e,f, g");

  string token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("a", token);

  token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("b", token);

  token = aSource.pullNextToken();
  string expect = "c  d";
  CPPUNIT_ASSERT_EQUAL(expect, token);

  token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("e", token);

  token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("f", token);

  token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("g", token);

  token = aSource.pullNextToken();
  CPPUNIT_ASSERT_EQUAL("", token);  //  EOF!

    },

    ////////////////////////////////////////////////////
    ///   tests on the parser & object model


    TEST_(TestCase, StringTable)
    {

  string rc = "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN  //  this is a comment \n"
                  "    IDS_OBVIOUS1  \"I like food\"\n"
                  "    IDS_OBVIOUS2  \"Food is good\"\n"
                  "    IDS_OBVIOUS3  \"\"\"Eat good food\"\"\"\n"  //  <-- escaped strings
                  " // IDS_DONT_PARSE_ME  \"Don't parse me\"\n"  //  <-- escaped strings
                  "END\n";

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc);

  CPPUNIT_ASSERT_EQUAL("I like food", aResourceFile.getString("IDS_OBVIOUS1"));
  CPPUNIT_ASSERT_EQUAL("Food is good", aResourceFile.getString("IDS_OBVIOUS2"));
  string expect = "\"Eat good food\"";
  CPPUNIT_ASSERT_EQUAL(expect, aResourceFile.getString("IDS_OBVIOUS3"));
  CPPUNIT_ASSERT_EQUAL("", aResourceFile.getString("IDS_NOT_OBVIOUS"));

    },


    TEST_(TestCase, Dialog)
    {

  string rc = "IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 217, 55\n"
                  "STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU\n"
                  "CAPTION \"About HostApp\"\n"
                  "FONT 8, \"MS Sans Serif\"\n"
                  "BEGIN\n"
                  "    ICON            IDR_MAINFRAME,IDC_STATIC,11,17,20,20\n"
                  "    LTEXT           \"Pyramus && Thisby\",IDC_STATIC,40,10,119,8,SS_NOPREFIX\n"
                  "    LTEXT           \"Copyright (C) 2098\",IDC_STATIC,40,25,119,8\n"
                  "    DEFPUSHBUTTON   \"Okay\",IDOK,178,7,32,14,WS_GROUP\n"
                  "END\n";

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc);
  Resource * pDlg = aResourceFile.get("IDD_ABOUTBOX");
  CPPUNIT_ASSERT_EQUAL("IDD_ABOUTBOX",      pDlg->getID());
  CPPUNIT_ASSERT_EQUAL(8,                   pDlg->get("0")->getSpecCount());
  CPPUNIT_ASSERT_EQUAL("ICON",              pDlg->get("0")->getSpec(1));
  CPPUNIT_ASSERT_EQUAL("Pyramus && Thisby", pDlg->get("1")->getSpec(2));
  CPPUNIT_ASSERT_EQUAL("Pyramus && Thisby", pDlg->get("1")->getLabel());
  CPPUNIT_ASSERT_EQUAL("IDC_STATIC",        pDlg->get("2")->getSpec(3));
  CPPUNIT_ASSERT_EQUAL("178",               pDlg->get("3")->getSpec(4));
  CPPUNIT_ASSERT_EQUAL(NULL,                pDlg->get("4"));

    },


    TEST_(TestCase, AccelTable)
    {

  string rc = "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
                  "BEGIN\n"
                  "    VK_INSERT, IDM_EDITCOPY,   VIRTKEY, CONTROL\n"
                  "    VK_INSERT, IDM_EDITPASTE,  VIRTKEY, SHIFT\n"
                  "    VK_INSERT, IDM_EDITPASTE,  VIRTKEY, SHIFT | CONTROL\n"
                  "    VK_INSERT, IDM_EDITPASTE,  VIRTKEY, SHIFT | CONTROL | ALT\n"
                  "    VK_BACK,   IDM_EDITUNDO,   VIRTKEY, ALT\n"
                  "    VK_F5,     IDM_EDITTIME,   VIRTKEY\n"
                  "    \"A\",     IDM_SEARCHNEXT, VIRTKEY, CONTROL\n"
                  "    \"B\",     IDM_SEARCHPREV, VIRTKEY\n"
                  "END\n";

    //  TODO  can the control items pipe? Can they be in any order?

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc);
  Resource * anAccelTable = aResourceFile.get("IDPLATYPUS ACCELERATORS");
  assert(anAccelTable);
  CPPUNIT_ASSERT_EQUAL(5,               anAccelTable->get("0")->getSpecCount());
  CPPUNIT_ASSERT_EQUAL("VK_INSERT",     anAccelTable->get("0")->getSpec(1));
  CPPUNIT_ASSERT_EQUAL("IDM_EDITPASTE", anAccelTable->get("1")->getSpec(2));
  CPPUNIT_ASSERT_EQUAL("VIRTKEY",       anAccelTable->get("2")->getSpec(3));
  CPPUNIT_ASSERT_EQUAL(9,               anAccelTable->get("3")->getSpecCount());
  CPPUNIT_ASSERT_EQUAL("SHIFT",         anAccelTable->get("3")->getSpec(4));
  CPPUNIT_ASSERT_EQUAL("|",             anAccelTable->get("3")->getSpec(5));
  CPPUNIT_ASSERT_EQUAL(5,               anAccelTable->get("4")->getSpecCount());
  CPPUNIT_ASSERT_EQUAL("ALT",           anAccelTable->get("4")->getSpec(4));

    },


    TEST_(TestCase, Menu)
    {

  string rc = "IDPLATYPUS MENU LOADONCALL MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    POPUP \"&File\"\n"
                  "    BEGIN\n"
                  "        MENUITEM \"&New\",          IDM_FILENEW\n"
                  "        MENUITEM \"&Open...\",      IDM_FILEOPEN, GRAYED\n"
                  "  //      MENUITEM \"&Kozmik...\",      IDM_KOZMIK\n"
                  "    END\n"
                  "END\n";

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc);

  Resource & aMenu = *aResourceFile.get("IDPLATYPUS");
  CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", aMenu.getID());

  string popupID = "&File";
  Resource * pPopup = aMenu.get(popupID);
  CPPUNIT_ASSERT_EQUAL(popupID, pPopup->getID());

  CPPUNIT_ASSERT_EQUAL("&New",     pPopup->get("IDM_FILENEW" )->getLabel());
  CPPUNIT_ASSERT_EQUAL("&Open...", pPopup->get("IDM_FILEOPEN")->getLabel());

    },


    TEST_(TestCase, SubMenu)
    {

  string rc = "IDPLATYPUS MENU LOADONCALL MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    POPUP \"&File\"\n"
                  "    BEGIN\n"
                  "        MENUITEM \"&New\",               IDM_FILENEW\n"
                  "        MENUITEM \"&Open...\",           IDM_FILEOPEN\n"
                  "        POPUP \"&Menu\"\n"
                  "        BEGIN\n"
                  "            MENUITEM \"&Frog\",          IDM_FROG\n"
                  "            MENUITEM \"&Soup\",          IDM_SOUP\n"
                  "        END\n"
                  "    END\n"
                  "END\n";

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc);

  Resource & aMenu = *aResourceFile.get("IDPLATYPUS");
  CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", aMenu.getID());

  string popupID = "&File";
  Resource * pPopup = aMenu.get(popupID);
  CPPUNIT_ASSERT_EQUAL(popupID, pPopup->getID());

  CPPUNIT_ASSERT_EQUAL("&New",       pPopup->get("IDM_FILENEW" )->getLabel());
  CPPUNIT_ASSERT_EQUAL("&Open...",   pPopup->get("IDM_FILEOPEN")->getLabel());

  Resource & bPopup = *pPopup->get("&Menu");

  CPPUNIT_ASSERT_EQUAL("&Frog",      bPopup.get("IDM_FROG")->getLabel());
  CPPUNIT_ASSERT_EQUAL("&Soup",      bPopup.get("IDM_SOUP")->getLabel());
  CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", dynamic_cast<MenuItem*>(bPopup.get("IDM_FROG"))->getMenuID());    
  CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", dynamic_cast<MenuItem*>(bPopup.get("IDM_SOUP"))->getMenuID());

    },


  struct
    TestResourceFile:  public TestCase
    {

  ResourceFile m_aResourceFile;

  void
    setUp()
  {
  m_aResourceFile.parseFile("NIBELUNG_MENU MENU DISCARDABLE \n"
          "BEGIN\n"
          "    POPUP \"&Brunhilde\"\n"
          "    BEGIN\n"
          "        MENUITEM SEPARATOR\n"
          "    END\n"
          "END\n"
          "\n"
          "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
          "BEGIN\n"
          "    IDS_SNOW        \"Snow\"\n"
          "    IDS_ANGEL       \"Angel\\n\"\n"
          "    IDS_COMMITTEE   \"Committee\"\n"
          "END\n");
  },

    },;


    TEST_(TestResourceFile, MenuWithOnlySeparator)
    {
  CPPUNIT_ASSERT_EQUAL("Snow"     , m_aResourceFile.getString("IDS_SNOW"     ));
  CPPUNIT_ASSERT_EQUAL("Angel\n"  , m_aResourceFile.getString("IDS_ANGEL"    ));
  CPPUNIT_ASSERT_EQUAL("Committee", m_aResourceFile.getString("IDS_COMMITTEE"));
    },


    TEST_(TestResourceFile, getLine)
    {

    //NIBELUNG_MENU MENU DISCARDABLE nBEGINn    POPUP "&Brunhilde"n    BEGINn        MENUITEM SEPARATORn    ENDnENDnnSTRINGTABLE PRELOAD MOVEABLE DISCARDABLEnBEGINn    IDS_SNOW        "Snow"n    IDS_ANGEL       "Angel\n"n    IDS_COMMITTEE   "Committee"nENDn

  string expect = "    POPUP \"&Brunhilde\"\n";
  CPPUNIT_ASSERT_EQUAL(expect,                         m_aResourceFile.getLine(43));
  CPPUNIT_ASSERT_EQUAL("        MENUITEM SEPARATOR\n", m_aResourceFile.getLine(80));
  CPPUNIT_ASSERT_EQUAL(3, m_aResourceFile.getLineNumber(43));
  CPPUNIT_ASSERT_EQUAL(5, m_aResourceFile.getLineNumber(80));
    },


    ////////////////////////////////////////////////////
    ///   tests for lint system

    //  TODO  &&, & on end
    //  TODO  "spike sample" fixture for the linter - proselytize


  struct
    TestLint:  TestCase
    {
  ResourceFile m_aResourceFile;

  void
    findOneError(string const & rc, string const & expect)
  {
  m_aResourceFile.parseFile(rc, "fileName.rc");
  stringstream complaints;
  m_aResourceFile.callLint(complaints);
  CPPUNIT_ASSERT_EQUAL(expect, complaints.str());
  },

    },;


    TEST_(TestLint, PromptAtEndOfControlList)
    {

  string spike = "    LTEXT           \"&Address:\",IDC_STATIC,7,7,30,8\n";

  string rc = "IDD_CALL DIALOG DISCARDABLE  0, 0, 242, 79\n"
              "BEGIN\n"
              + spike +
              "END\n";

  findOneError( rc, "fileName.rc(3): Prompt at end of control list\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, PromptBeforeControlWithoutTabstop)
    {

  string spike = "    LTEXT           \"&Address:\",IDC_STATIC,7,7,30,8\n";

  string rc = "IDD_CALL DIALOG DISCARDABLE  0, 0, 242, 79\n"
              "BEGIN\n"
              + spike +
              "    EDITTEXT        IDE_ADDR,39,7,125,12,ES_AUTOHSCROLL\n"
              "END\n";

  findOneError( rc, "fileName.rc(3): Prompt before control without tabstop\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, PromptMissingHotkey)
    {

  string spike = "    LTEXT           \"Address:\",IDC_STATIC,7,7,30,8\n";

  string rc = "IDD_CALL DIALOG DISCARDABLE  0, 0, 242, 79\n"
              "BEGIN\n"
              + spike +
              "    EDITTEXT        IDE_ADDR,39,7,125,12,ES_AUTOHSCROLL | WS_TABSTOP\n"
              "    LTEXT           \"&Call Type:\",IDC_STATIC,185,45,32,8\n"
              "    COMBOBOX        IDL_CALL_TYPE,183,58,52,47,CBS_DROPDOWN | WS_VSCROLL | \n"
              "                    WS_TABSTOP\n"
              "    DEFPUSHBUTTON   \"OK\",IDOK,182,7,50,14\n"
              "    PUSHBUTTON      \"Cancel\",IDCANCEL,182,24,50,14\n"
              "END\n";

  findOneError( rc, "fileName.rc(3): Button missing hotkey\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, DoubleAmpersandsAreNotHotkeys)
    {

  string spike = "    CONTROL         \"Application && Sharing\",IDC_NMCH_AS,\"Button\",\n";
  string rc = "IDD_CONFERENCE DIALOG DISCARDABLE  0, 0, 237, 119\n"
              "BEGIN\n"
              "    CONTROL         \"&File Transfer\",IDC_NMCH_FT,\"Button\",BS_AUTOCHECKBOX | \n"
              "                    WS_TABSTOP,71,66,76,10\n"
              + spike +
              "                    BS_AUTOCHECKBOX | WS_TABSTOP,71,81,76,10\n"
              "END\n";

  findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, TrailingAmpersandsAreNotHotkeys)
    {

  string spike = "    CONTROL         \"Application Sharing &\",IDC_NMCH_AS,\"Button\",\n";
  string rc = "IDD_CONFERENCE DIALOG DISCARDABLE  0, 0, 237, 119\n"
              "BEGIN\n"
              "    CONTROL         \"&File Transfer\",IDC_NMCH_FT,\"Button\",BS_AUTOCHECKBOX | \n"
              "                    WS_TABSTOP,71,66,76,10\n"
              + spike +
              "                    BS_AUTOCHECKBOX | WS_TABSTOP,71,81,76,10\n"
              "END\n";

  findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, ButtonMissingHotkey)
    {

  string spike = "    CONTROL         \"Application Sharing\",IDC_NMCH_AS,\"Button\",\n";
  string rc = "IDD_CONFERENCE DIALOG DISCARDABLE  0, 0, 237, 119\n"
              "BEGIN\n"
              "    CONTROL         \"&File Transfer\",IDC_NMCH_FT,\"Button\",BS_AUTOCHECKBOX | \n"
              "                    WS_TABSTOP,71,66,76,10\n"
              + spike +
              "                    BS_AUTOCHECKBOX | WS_TABSTOP,71,81,76,10\n"
              "END\n";

  findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, PushbuttonMissingHotkey)
    {

  string spike = "    PUSHBUTTON      \"Browse\",IDB_BROWSE,120,41,50,14\n";
  string rc = "IDD_SENDFILE DIALOG DISCARDABLE  0, 0, 177, 69\n"
              "BEGIN\n"
              "    DEFPUSHBUTTON   \"OK\",IDOK,119,4,50,14\n"
              "    PUSHBUTTON      \"Cancel\",IDCANCEL,120,24,50,14\n"
              + spike +
              "END\n";

  findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, DefPushbuttonMissingHotkey)
    {

  string spike = "    DEFPUSHBUTTON      \"Browse\",IDB_BROWSE,120,41,50,14\n";
  string rc = "IDD_SENDFILE DIALOG DISCARDABLE  0, 0, 177, 69\n"
              "BEGIN\n"
              "    PUSHBUTTON   \"OK\",IDOK,119,4,50,14\n"
              "    PUSHBUTTON   \"Cancel\",IDCANCEL,120,24,50,14\n"
              + spike +
              "END\n";

  findOneError( rc, "fileName.rc(5): Button missing hotkey\n"
                        + spike +
                        "\n" );

    },


    TEST_(TestLint, DuplicatedControlHotkey)
    {

  string spike1 = "    GROUPBOX        \"&Multisample\",IDC_STATIC,5,101,200,28\n";
  string spike2 = "    LTEXT           \"&Multisample Type:\",IDC_STATIC,22,113,62,10,\n";

  string rc = "IDD_SELECTDEVICE DIALOG DISCARDABLE  0, 0, 267, 138\n"
              "STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU\n"
              "CAPTION \"Select Device\"\n"
              "FONT 8, \"MS Shell Dlg\"\n"
              "BEGIN\n"
              "    GROUPBOX        \"Rendering device\",IDC_STATIC,5,5,200,45\n"
              "    LTEXT           \"&Adapter:\",IDC_STATIC,22,17,65,10,SS_CENTERIMAGE\n"
              "    COMBOBOX        IDC_ADAPTER_COMBO,90,15,105,100,CBS_DROPDOWNLIST | \n"
              "                    WS_VSCROLL | WS_TABSTOP\n"
              "    LTEXT           \"&Device:\",IDC_STATIC,22,32,65,10,SS_CENTERIMAGE\n"
              "    COMBOBOX        IDC_DEVICE_COMBO,90,30,105,100,CBS_DROPDOWNLIST | \n"
              "                    WS_VSCROLL | WS_TABSTOP\n"
              "    GROUPBOX        \"Rendering mode\",IDC_STATIC,5,52,200,45\n"
              "    CONTROL         \"Use desktop &window\",IDC_WINDOW,\"Button\",\n"
              "                    BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,10,62,85,15\n"
              "    CONTROL         \"&Fullscreen mode:\",IDC_FULLSCREEN,\"Button\",\n"
              "                    BS_AUTORADIOBUTTON,10,77,75,15\n"
              "    COMBOBOX        IDC_FULLSCREENMODES_COMBO,90,77,105,204,CBS_DROPDOWNLIST | \n"
              "                    WS_VSCROLL | WS_GROUP | WS_TABSTOP\n"
              + spike1
              + spike2 +
              "                    SS_CENTERIMAGE\n"
              "    COMBOBOX        IDC_MULTISAMPLE_COMBO,90,111,105,100,CBS_DROPDOWNLIST | \n"
              "                    WS_VSCROLL | WS_TABSTOP\n"
              "    DEFPUSHBUTTON   \"OK\",IDOK,210,10,50,14\n"
              "    PUSHBUTTON      \"Cancel\",IDCANCEL,210,30,50,14\n"
              "END\n";

  findOneError( rc, "fileName.rc(21): Duplicated Control hotkey\n"
                        + spike2
                        + spike1+
                        "\n" );

//  TODO  force get(int) to return items in line order

    },


    TEST_(TestLint, DuplicatedMenuItemHotkey)
    {

  string spike1 = "        MENUITEM \"Kriem&hild\\tShift+K\",  ID_KRIEMHILD\n";
  string spike2 = "        MENUITEM \"Gunt&her\\tCtrl+G\",     ID_GUNTHER\n";
  string rc = "IDPLATYPUS MENU DISCARDABLE \n"
              "BEGIN\n"
              "    POPUP \"&Brunhilde\"\n"
              "    BEGIN\n"
              + spike1
              + spike2 +
              "    END\n"
              "END\n"
              "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
              "BEGIN\n"
              "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n"
              "    \"G\", ID_GUNTHER,    VIRTKEY, CONTROL\n"
              "END\n"
              "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    ID_GUNTHER      \"Snow\"\n"
                  "    ID_KRIEMHILD    \"Angel\\n\"\n"
                  "    IDS_COMMITTEE   \"Committee\"\n"
                  "END\n";

  findOneError( rc, "fileName.rc(6): Duplicated MenuItem hotkey\n"
                        + spike2
                        + spike1 +
                        "\n" );

    },


    TEST_(TestLint, MissingAccelerator)
    {

  string spike1 = "    POPUP \"Brunhilde\"\n";
  string spike2 = "        MENUITEM \"Gunther\",       ID_GUNTHER\n";
  string rc = "NIBELUNG_MENU MENU DISCARDABLE \n"
              "BEGIN\n"
              + spike1 + 
              "    BEGIN\n"
              "        MENUITEM \"&Kriemhild\",\n  ID_KRIEMHILD\n"
              + spike2 +
              "    END\n"
              "END\n"
              "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    ID_GUNTHER      \"Snow\"\n"
                  "    ID_KRIEMHILD    \"Angel\\n\"\n"
                  "    IDS_COMMITTEE   \"Committee\"\n"
                  "END\n";

  findOneError( rc, "fileName.rc(3): Missing & in Menu Label\n"
                        + spike1 +
                        "\n"
                        "fileName.rc(7): Missing & in Menu Label\n"
                        + spike2 +
                        "\n" );

    },


    TEST_(TestLint, MenuItemMissesShortcut)
    {

  string spike1 = "        MENUITEM \"Gunth&er\",       ID_GUNTHER\n";
  string spike2 = "    \"G\", ID_GUNTHER,   VIRTKEY, CONTROL\n";
  string rc = "IDPLATYPUS MENU DISCARDABLE \n"
              "BEGIN\n"
              "    POPUP \"&Brunhilde\"\n"
              "    BEGIN\n"
              "        MENUITEM \"&Kriemhild\\tShift+K\",\n  ID_KRIEMHILD\n"
              + spike1 +
              "    END\n"
              "END\n"
              "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
              "BEGIN\n"
              + spike2 +
              "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n"
              "END\n"
              "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    ID_GUNTHER      \"Snow\"\n"
                  "    ID_KRIEMHILD    \"Angel\\n\"\n"
                  "    IDS_COMMITTEE   \"Committee\"\n"
                  "END\n";

  findOneError( rc, "fileName.rc(7): Missing accelerator prompt in Menu Label\n"
                        + spike1
                        + spike2 +
                        "\n" );

    },


    TEST_(TestLint, MenuItemShortcutWrong)
    {

  string spike1 = "        MENUITEM \"&Kriemhild\\tShift+H\",  ID_KRIEMHILD\n";
  string spike2 = "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n";
  string rc = "IDPLATYPUS MENU DISCARDABLE \n"
              "BEGIN\n"
              "    POPUP \"&Brunhilde\"\n"
              "    BEGIN\n"
              + spike1 +
              "        MENUITEM \"Gunth&er\\tCtrl+G\",       ID_GUNTHER\n"
              "    END\n"
              "END\n"
              "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
              "BEGIN\n"
              + spike2 +
              "    \"G\", ID_GUNTHER,   VIRTKEY, CONTROL\n"
              "END\n"
              "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    ID_GUNTHER      \"Snow\"\n"
                  "    ID_KRIEMHILD    \"Angel\\n\"\n"
                  "    IDS_COMMITTEE   \"Committee\"\n"
                  "END\n";

//  TODO  that line number should be 5?

  findOneError( rc, "fileName.rc(5): Wrong accelerator prompt in Menu Label\n"
                        + spike1
                        + spike2 +
                        "\n" );

    },


    TEST_(TestLint, MissingStringTableHelpForMenuItem)
    {

  string spike = "        MENUITEM \"&Kriemhild\\tShift+K\",  ID_KRIEMHILD\n";

  string rc = "IDPLATYPUS MENU DISCARDABLE \n"
              "BEGIN\n"
              "    POPUP \"&Brunhilde\"\n"
              "    BEGIN\n"
              + spike +
              "        MENUITEM \"Gunth&er\\tCtrl+G\",       ID_GUNTHER\n"
              "    END\n"
              "END\n"
              "IDPLATYPUS ACCELERATORS LOADONCALL MOVEABLE DISCARDABLE \n"
              "BEGIN\n"
              "    \"K\", ID_KRIEMHILD,  VIRTKEY, SHIFT\n"
              "    \"G\", ID_GUNTHER,    VIRTKEY, CONTROL\n"
              "END\n"
              "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    ID_GUNTHER      \"Snow\"\n"
                  "    IDS_ANGEL       \"Angel\\n\"\n"
                  "    IDS_COMMITTEE   \"Committee\"\n"
                  "END\n";

  findOneError( rc, "fileName.rc(5): Missing StringTable help for MenuItem\n"
                        + spike +
                        "\n" );

    },

    ////////////////////////////////////////////////////
    ///   tests for miscellany


    TEST_(TestCase, printTree)
    {

  string rc = "NIBELUNG_MENU MENU\n"
              "BEGIN\n"
              "    POPUP \"Brunhilde\", GRAYED\n"
              "    BEGIN\n"
              "        MENUITEM \"&Kriemhild\",\n  ID_KRIEMHILD\n"
              "        MENUITEM \"Gunther\",       ID_GUNTHER\n"
              "    END\n"
              "END\n";

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc, "fileName.rc");
  stringstream tree;

  aResourceFile.printTree(tree, 1);

  string expect = " fileName.rc\n"
                  "      NIBELUNG_MENU\n"
                  "           Brunhilde\n"
                  "                ID_GUNTHER\n"
                  "                ID_KRIEMHILD\n";

  CPPUNIT_ASSERT_EQUAL(expect, tree.str());

    },


    TEST_(TestCase, callLint)
    {

  string rc =
      "IDR_MENU    MENU MOVEABLE DISCARDABLE\n"
      "BEGIN\n"
      "     POPUP \"&File\"\n"
      "      BEGIN\n"
      "\t"   "MENUITEM \"&Close\",    IDM_FILECLOSE\n"
      "       MENUITEM SEPARATOR\n"
      "      END\n"
      "    END\n";

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc, "fileName.rc");
    //    aResourceFile.printTree(cout);
  stringstream out;
  aResourceFile.callLint(out);

    },


    TEST_(TestCase, LeGrandWazoo)
    {

  string rc = "IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 217, 55\n"
                  "STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU\n"
                  "CAPTION \"About HostApp\"\n"
                  "FONT 8, \"MS Sans Serif\"\n"
                  "BEGIN\n"
                  "    ICON            IDR_MAINFRAME,IDC_STATIC,11,17,20,20\n"
                  "    LTEXT           \"Pyramus && Thisby\",IDC_STATIC,40,10,119,8,SS_NOPREFIX\n"
                  "    LTEXT           \"Copyright (C) 2098\",IDC_STATIC,40,25,119,8\n"
                  "    DEFPUSHBUTTON   \"Okay\",IDOK,178,7,32,14,WS_GROUP\n"
                  "END\n"
              "\n"
              "\n"
                  "NIBELUNG_MENU MENU DISCARDABLE \n"
                  "BEGIN\n"
                  "    POPUP \"&Flying\"\n"
                  "    BEGIN\n"
                  "        MENUITEM \"&Valkyrie\",    ID_VALKYRIE, grayed\n"
                  "        MENUITEM \"E&xit\",        ID_FILE_EXIT\n"
                  "    END\n"
                  "    POPUP \"&Rheingold\"\n"
                  "    BEGIN\n"
                  "        MENUITEM \"&Play\",        ID_RHEINGOLD_PLAY\n"
                  "        MENUITEM \"Walhalla\",     ID_RHEINGOLD_WALHALLA\n"
                  "        MENUITEM SEPARATOR\n"
                  "        POPUP \"&Alberich\"\n"
                  "        BEGIN\n"
                  "            MENUITEM \"&Volsung\", ID_VOLSUNG\n"
                  "            MENUITEM \"&Hunland\", ID_HUNLAND\n"
                  "        END\n"
                  "        POPUP \"Wotan\"\n"
                  "        BEGIN\n"
                  "            MENUITEM \"&Rhein\",   ID_RHEIN\n"
                  "        END\n"
                  "        MENUITEM \"&Title Menu\",  ID_RHEINGOLD_TITLEMENU\n"
                  "    END\n"
                  "    POPUP \"&Brunhilde\"\n"
                  "    BEGIN\n"
                  "        POPUP \"&Siegfried\"\n"
                  "        BEGIN\n"
                  "            MENUITEM \"&Kriemhild\",\n  ID_KRIEMHILD\n"
                  "       //     MENUITEM \"&Miscreant\",  ID_MISCREANT\n"
                  "            MENUITEM \"&Gunther\",      ID_GUNTHER\n"
                  "\n"
                  "        END\n"
                  "        MENUITEM SEPARATOR\n"
                  "    END\n"
                  "    POPUP \"&Help\"\n"
                  "    BEGIN\n"
                  "        MENUITEM \"&About Nibelung\",  ID_ABOUT\n"
                  "        MENUITEM \"Gotterdammerung\",  ID_GOTTERDAMMERUNG\n"
                  "    END\n"
                  "END\n"
              "\n"
              "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    IDS_SNOW        \"Snow\"\n"
                  "    IDS_ANGEL       \"Angel\\n\"\n"
                  "    IDS_COMMITTEE   \"Committee\"\n"
                  "END\n"
              "\n"
              "IDPLATYPUS MENU LOADONCALL MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    POPUP \"&File\"\n"
                  "    BEGIN\n"
                  "        MENUITEM \"&New\",        IDM_FILENEW\n"
                  "        MENUITEM \"&Open...\",    IDM_FILEOPEN\n"
                  "        POPUP \"&Menu\"\n"
                  "        BEGIN\n"
                  "            MENUITEM \"&Frog\",   IDM_FROG\n"
                  "            MENUITEM \"&Soup\",   IDM_SOUP\n"
                  "        END\n"
                  "    END\n"
                  "END\n"
              "\n"
              "STRINGTABLE PRELOAD MOVEABLE DISCARDABLE\n"
                  "BEGIN\n"
                  "    IDS_OBVIOUS1     \"I like food\"\n"
                  "    IDS_OBVIOUS2     \"Food is good\"\n"
                  "    IDS_OBVIOUS3     \"\"\"Eat good food\"\"\"\n"  //  <-- escaped strings
                  "END\n";

  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc);

  CPPUNIT_ASSERT_EQUAL("IDD_ABOUTBOX", aResourceFile.get("IDD_ABOUTBOX")->getID());

  Resource & aMenu = *aResourceFile.get("IDPLATYPUS");
  CPPUNIT_ASSERT_EQUAL("IDPLATYPUS", aMenu.getID());

  string     popupID = "&File";
  Resource * pPopup  = aMenu.get(popupID);
  CPPUNIT_ASSERT_EQUAL(popupID, pPopup->getID());

  CPPUNIT_ASSERT_EQUAL("&New",     pPopup->get("IDM_FILENEW" )->getLabel());
  CPPUNIT_ASSERT_EQUAL("&Open...", pPopup->get("IDM_FILEOPEN")->getLabel());

  Resource & bPopup = *pPopup->get("&Menu");

  CPPUNIT_ASSERT_EQUAL("&Frog", bPopup.get("IDM_FROG")->getLabel());    
  CPPUNIT_ASSERT_EQUAL("&Soup", bPopup.get("IDM_SOUP")->getLabel());

  Resource & nibelungMenu = *aResourceFile.get("NIBELUNG_MENU");
  CPPUNIT_ASSERT_EQUAL("NIBELUNG_MENU", nibelungMenu.getID());

  pPopup = nibelungMenu.get("&Flying");
  CPPUNIT_ASSERT_EQUAL("&Valkyrie", pPopup->get("ID_VALKYRIE" )->getLabel());
  CPPUNIT_ASSERT_EQUAL("E&xit",     pPopup->get("ID_FILE_EXIT")->getLabel());

  pPopup = nibelungMenu.get("&Rheingold");
  CPPUNIT_ASSERT_EQUAL("&Play"   , pPopup->get("ID_RHEINGOLD_PLAY"    )->getLabel());
  CPPUNIT_ASSERT_EQUAL("Walhalla", pPopup->get("ID_RHEINGOLD_WALHALLA")->getLabel());

  Resource * zPopup = pPopup->get("&Alberich");
  CPPUNIT_ASSERT_EQUAL("&Volsung", zPopup->get("ID_VOLSUNG")->getLabel());
  CPPUNIT_ASSERT_EQUAL("&Hunland", zPopup->get("ID_HUNLAND")->getLabel());
  zPopup = pPopup->get("Wotan");
  CPPUNIT_ASSERT_EQUAL("&Rhein", zPopup->get("ID_RHEIN")->getLabel());
  CPPUNIT_ASSERT_EQUAL("&Title Menu", pPopup->get("ID_RHEINGOLD_TITLEMENU")->getLabel());

  pPopup = nibelungMenu.get("&Brunhilde");
  zPopup = pPopup->get("&Siegfried");
  CPPUNIT_ASSERT_EQUAL("&Kriemhild", zPopup->get("ID_KRIEMHILD")->getLabel());
  CPPUNIT_ASSERT_EQUAL("&Gunther"  , zPopup->get("ID_GUNTHER"  )->getLabel());

  pPopup = nibelungMenu.get("&Help");
  CPPUNIT_ASSERT_EQUAL("&About Nibelung", pPopup->get("ID_ABOUT")->getLabel());
  CPPUNIT_ASSERT_EQUAL("Gotterdammerung", pPopup->get("ID_GOTTERDAMMERUNG")->getLabel());

  CPPUNIT_ASSERT_EQUAL("Snow"         , aResourceFile.getString("IDS_SNOW"     ));
  CPPUNIT_ASSERT_EQUAL("Angel\n"      , aResourceFile.getString("IDS_ANGEL"    ));
  CPPUNIT_ASSERT_EQUAL("Committee"    , aResourceFile.getString("IDS_COMMITTEE"));
  CPPUNIT_ASSERT_EQUAL("I like food"  , aResourceFile.getString("IDS_OBVIOUS1"));
  CPPUNIT_ASSERT_EQUAL("Food is good" , aResourceFile.getString("IDS_OBVIOUS2"));
  string expect = "\"Eat good food\"";
  CPPUNIT_ASSERT_EQUAL(expect, aResourceFile.getString("IDS_OBVIOUS3"));
  expect = "";
  CPPUNIT_ASSERT_EQUAL(expect, aResourceFile.getString("IDS_NOT_OBVIOUS"));

    },


  std::string
    fileToString(string const & name)
    {
  std::ifstream in(name.c_str());
  std::ostringstream oss;
  oss << in.rdbuf();
  return oss.str();
    }, 


  void
    rcLint(ostream & out, string fileName)
    {
  string rc = fileToString(fileName);
  ResourceFile aResourceFile;
  aResourceFile.parseFile(rc, fileName);
    //    aResourceFile.printTree(cout);
  aResourceFile.callLint(out);
    },


  int
    main(int argc, char **argv)
    {
  if (argc == 2)
      {
      rcLint(cout, argv[1]);
      return 0;
      },
  else
      {
      bool worked = TestCase::runTests();
      cout << (worked?  "All tests passed":  "Test(s) failed") << endl;
      return ! worked;
      },
    },

CategoryLint


Loading...