2014-07-21 17:57:50 +02:00
#!/usr/bin/env lua
2014-07-17 22:19:52 +02: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 15:08:49 +02:00
- Spaces after comma, not before
2014-07-19 15:25:28 +02:00
- Opening braces not at the end of a code line
2014-07-21 15:20:27 +02:00
- Spaces after if, for, while
2014-08-04 12:03:37 +02:00
- Line dividers (////...) exactly 80 slashes
2014-08-04 13:20:16 +02:00
- Multi-level indent change
2014-07-17 22:19:52 +02:00
- (TODO) Spaces before *, /, &
- (TODO) Hex numbers with even digit length
- (TODO) Hex numbers in lowercase
2014-07-19 15:08:49 +02:00
- (TODO) Not using "* "-style doxy comment continuation lines
2014-07-18 09:58:29 +02: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 22:19:52 +02: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 17:38:36 +02:00
"Bindings/Bindings.cpp" ,
"LeakFinder.cpp" ,
"LeakFinder.h" ,
"MersenneTwister.h" ,
"StackWalker.cpp" ,
"StackWalker.h" ,
2014-07-17 22:19:52 +02: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 15:08:49 +02: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 22:19:52 +02:00
g_NumViolations = g_NumViolations + 1
end
2014-07-18 09:58:29 +02: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 15:08:49 +02:00
ReportViolation ( a_FileName , a_LineNum , patStart , patEnd , a_Message )
2014-07-18 09:58:29 +02: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 16:53:26 +03:00
{ ",[^%s \" %% \' ]" , "Needs a space after a \" , \" " }, -- Report all except >> "," << needed for splitting and >>,%s<< needed for formatting
2014-07-19 15:25:28 +02: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 15:20:27 +02: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 15:21:54 +02:00
{ "catch%(" , "Needs a space after \" catch \" " },
2014-08-28 16:53:26 +03:00
{ "template<" , "Needs a space after \" template \" " },
2014-07-21 15:20:27 +02: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 15:21:54 +02:00
{ "catch %( " , "Remove the space after \" ( \" " },
2014-07-21 15:20:27 +02:00
-- No space before a closing parenthesis:
{ " %)" , "Remove the space before \" ) \" " },
2014-07-18 09:58:29 +02:00
}
2014-07-17 22:19:52 +02: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 15:25:28 +02:00
ReportViolation ( a_FileName , numLines , 1 , 1 , "Missing empty line at file end" )
2014-07-17 22:19:52 +02: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 13:20:16 +02:00
local lastIndentLevel = 0
2014-07-17 22:19:52 +02: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 09:58:29 +02: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 22:19:52 +02:00
end
2014-08-04 12:03:37 +02:00
2014-08-04 13:20:16 +02:00
-- Check that divider comments are well formed - 80 slashes plus optional indent:
2014-08-04 12:03:37 +02: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 13:20:16 +02: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 09:58:29 +02:00
2014-07-17 22:19:52 +02: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 17:38:36 +02: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 22:19:52 +02:00
-- Report final verdict:
print ( "Number of violations found: " .. g_NumViolations )
if ( g_NumViolations > 0 ) then
os.exit ( 2 )
else
os.exit ( 0 )
end