2014-07-21 11:57:50 -04:00
#!/usr/bin/env lua
2014-07-17 16:19:52 -04:00
-- CheckBasicStyle.lua
--[[
Checks that all source files ( * . cpp , * . h ) use the basic style requirements of the project :
- Tabs for indentation , spaces for alignment
- Trailing whitespace on non - empty lines
- Two spaces between code and line - end comment ( " // " )
2014-07-19 09:08:49 -04:00
- Spaces after comma , not before
2014-07-19 09:25:28 -04:00
- Opening braces not at the end of a code line
2014-07-21 09:20:27 -04:00
- Spaces after if , for , while
2014-08-04 06:03:37 -04:00
- Line dividers ( //// ... ) exactly 80 slashes
2014-08-04 07:20:16 -04:00
- Multi - level indent change
2014-07-17 16:19:52 -04:00
- ( TODO ) Spaces before * , / , &
- ( TODO ) Hex numbers with even digit length
- ( TODO ) Hex numbers in lowercase
2014-07-19 09:08:49 -04:00
- ( TODO ) Not using " * " - style doxy comment continuation lines
2014-07-18 03:58:29 -04:00
Violations that cannot be checked easily :
- Spaces around " + " ( there are things like " a++ " , " ++a " , " a += 1 " , " X+ " , " stack +1 " and ascii - drawn tables )
2014-07-17 16:19:52 -04:00
Reports all violations on stdout in a form that is readable by Visual Studio ' s parser, so that dblclicking
the line brings the editor directly to the violation .
Returns 0 on success , 1 on internal failure , 2 if any violations found
--]]
-- The list of file extensions that are processed:
local g_ShouldProcessExt =
{
[ " h " ] = true ,
[ " cpp " ] = true ,
}
--- The list of files not to be processed:
local g_IgnoredFiles =
{
2014-07-21 11:38:36 -04:00
" Bindings/Bindings.cpp " ,
" LeakFinder.cpp " ,
" LeakFinder.h " ,
" MersenneTwister.h " ,
" StackWalker.cpp " ,
" StackWalker.h " ,
2014-07-17 16:19:52 -04:00
}
--- The list of files not to be processed, as a dictionary (filename => true), built from g_IgnoredFiles
local g_ShouldIgnoreFile = { }
-- Initialize the g_ShouldIgnoreFile map:
for _ , fnam in ipairs ( g_IgnoredFiles ) do
g_ShouldIgnoreFile [ fnam ] = true
end
--- Keeps track of the number of violations for this folder
local g_NumViolations = 0
--- Reports one violation
-- Pretty-prints the message
-- Also increments g_NumViolations
2014-07-19 09:08:49 -04:00
local function ReportViolation ( a_FileName , a_LineNumber , a_PatStart , a_PatEnd , a_Message )
print ( a_FileName .. " ( " .. a_LineNumber .. " ): " .. a_PatStart .. " .. " .. a_PatEnd .. " : " .. a_Message )
2014-07-17 16:19:52 -04:00
g_NumViolations = g_NumViolations + 1
end
2014-07-18 03:58:29 -04:00
--- Searches for the specified pattern, if found, reports it as a violation with the given message
local function ReportViolationIfFound ( a_Line , a_FileName , a_LineNum , a_Pattern , a_Message )
local patStart , patEnd = a_Line : find ( a_Pattern )
if not ( patStart ) then
return
end
2014-07-19 09:08:49 -04:00
ReportViolation ( a_FileName , a_LineNum , patStart , patEnd , a_Message )
2014-07-18 03:58:29 -04:00
end
local g_ViolationPatterns =
{
-- Check against indenting using spaces:
{ " ^ \t * + " , " Indenting with a space " } ,
-- Check against alignment using tabs:
{ " [^%s] \t +[^%s] " , " Aligning with a tab " } ,
-- Check against trailing whitespace:
{ " [^%s]%s+ \n " , " Trailing whitespace " } ,
-- Check that all "//"-style comments have at least two spaces in front (unless alone on line):
{ " [^%s] // " , " Needs at least two spaces in front of a \" // \" -style comment " } ,
-- Check that all "//"-style comments have at least one spaces after:
{ " %s//[^%s/*<] " , " Needs a space after a \" // \" -style comment " } ,
-- Check that all commas have spaces after them and not in front of them:
{ " , " , " Extra space before a \" , \" " } ,
2014-08-28 09:53:26 -04:00
{ " ,[^%s \" %% \' ] " , " Needs a space after a \" , \" " } , -- Report all except >> "," << needed for splitting and >>,%s<< needed for formatting
2014-07-19 09:25:28 -04:00
-- Check that opening braces are not at the end of a code line:
{ " [^%s].-{ \n ?$ " , " Brace should be on a separate line " } ,
2014-07-21 09:20:27 -04:00
-- Space after keywords:
{ " [^_]if%( " , " Needs a space after \" if \" " } ,
{ " for%( " , " Needs a space after \" for \" " } ,
{ " while%( " , " Needs a space after \" while \" " } ,
{ " switch%( " , " Needs a space after \" switch \" " } ,
2014-07-21 09:21:54 -04:00
{ " catch%( " , " Needs a space after \" catch \" " } ,
2014-08-28 09:53:26 -04:00
{ " template< " , " Needs a space after \" template \" " } ,
2014-07-21 09:20:27 -04:00
-- No space after keyword's parenthesis:
{ " [^%a#]if %( " , " Remove the space after \" ( \" " } ,
{ " for %( " , " Remove the space after \" ( \" " } ,
{ " while %( " , " Remove the space after \" ( \" " } ,
2014-07-21 09:21:54 -04:00
{ " catch %( " , " Remove the space after \" ( \" " } ,
2014-07-21 09:20:27 -04:00
-- No space before a closing parenthesis:
{ " %) " , " Remove the space before \" ) \" " } ,
2014-07-18 03:58:29 -04:00
}
2014-07-17 16:19:52 -04:00
--- Processes one file
local function ProcessFile ( a_FileName )
assert ( type ( a_FileName ) == " string " )
-- Read the whole file:
local f , err = io.open ( a_FileName , " r " )
if ( f == nil ) then
print ( " Cannot open file \" " .. a_FileName .. " \" : " .. err )
print ( " Aborting " )
os.exit ( 1 )
end
local all = f : read ( " *all " )
-- Check that the last line is empty - otherwise processing won't work properly:
local lastChar = string.byte ( all , string.len ( all ) )
if ( ( lastChar ~= 13 ) and ( lastChar ~= 10 ) ) then
local numLines = 1
string.gsub ( all , " \n " , function ( ) numLines = numLines + 1 end ) -- Count the number of line-ends
2014-07-19 09:25:28 -04:00
ReportViolation ( a_FileName , numLines , 1 , 1 , " Missing empty line at file end " )
2014-07-17 16:19:52 -04:00
return
end
-- Process each line separately:
-- Ref.: http://stackoverflow.com/questions/10416869/iterate-over-possibly-empty-lines-in-a-way-that-matches-the-expectations-of-exis
local lineCounter = 1
2014-08-04 07:20:16 -04:00
local lastIndentLevel = 0
2014-07-17 16:19:52 -04:00
all : gsub ( " \r \n " , " \n " ) -- normalize CRLF into LF-only
string.gsub ( all .. " \n " , " [^ \n ]* \n " , -- Iterate over each line, while preserving empty lines
function ( a_Line )
2014-07-18 03:58:29 -04:00
-- Check against each violation pattern:
for _ , pat in ipairs ( g_ViolationPatterns ) do
ReportViolationIfFound ( a_Line , a_FileName , lineCounter , pat [ 1 ] , pat [ 2 ] )
2014-07-17 16:19:52 -04:00
end
2014-08-04 06:03:37 -04:00
2014-08-04 07:20:16 -04:00
-- Check that divider comments are well formed - 80 slashes plus optional indent:
2014-08-04 06:03:37 -04:00
local dividerStart , dividerEnd = a_Line : find ( " /////* " )
if ( dividerStart ) then
if ( dividerEnd ~= dividerStart + 79 ) then
ReportViolation ( a_FileName , lineCounter , 1 , 80 , " Divider comment should have exactly 80 slashes " )
end
if ( dividerStart > 1 ) then
if (
( a_Line : sub ( 1 , dividerStart - 1 ) ~= string.rep ( " \t " , dividerStart - 1 ) ) or -- The divider should have only indent in front of it
( a_Line : len ( ) > dividerEnd + 1 ) -- The divider should have no other text following it
) then
ReportViolation ( a_FileName , lineCounter , 1 , 80 , " Divider comment shouldn't have any extra text around it " )
end
end
end
2014-08-04 07:20:16 -04:00
-- Check the indent level change from the last line, if it's too much, report:
local indentStart , indentEnd = a_Line : find ( " \t + " )
local indentLevel = 0
if ( indentStart ) then
indentLevel = indentEnd - indentStart
end
if ( indentLevel > 0 ) then
if ( ( lastIndentLevel - indentLevel >= 2 ) or ( lastIndentLevel - indentLevel <= - 2 ) ) then
ReportViolation ( a_FileName , lineCounter , 1 , indentStart or 1 , " Indent changed more than a single level between the previous line and this one: from " .. lastIndentLevel .. " to " .. indentLevel )
end
lastIndentLevel = indentLevel
end
2014-07-18 03:58:29 -04:00
2014-07-17 16:19:52 -04:00
lineCounter = lineCounter + 1
end
)
end
--- Processes one item - a file or a folder
local function ProcessItem ( a_ItemName )
assert ( type ( a_ItemName ) == " string " )
-- Skip files / folders that should be ignored
if ( g_ShouldIgnoreFile [ a_ItemName ] ) then
return
end
local ext = a_ItemName : match ( " %.([^/%.]-)$ " )
if ( g_ShouldProcessExt [ ext ] ) then
ProcessFile ( a_ItemName )
end
end
2014-07-21 11:38:36 -04:00
-- Process all files in the AllFiles.lst file (generated by cmake):
for fnam in io.lines ( " AllFiles.lst " ) do
ProcessItem ( fnam )
end
2014-07-17 16:19:52 -04:00
-- Report final verdict:
print ( " Number of violations found: " .. g_NumViolations )
if ( g_NumViolations > 0 ) then
os.exit ( 2 )
else
os.exit ( 0 )
end