Ms Windows Resource Lint Source

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


EditText of this page (last edited April 19, 2011) or FindPage with title or text search