summaryrefslogtreecommitdiffstats
path: root/src/CheckBasicStyle.lua
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/CheckBasicStyle.lua215
1 files changed, 215 insertions, 0 deletions
diff --git a/src/CheckBasicStyle.lua b/src/CheckBasicStyle.lua
new file mode 100644
index 000000000..6aad4125d
--- /dev/null
+++ b/src/CheckBasicStyle.lua
@@ -0,0 +1,215 @@
+
+-- 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 ("//")
+ - Spaces after comma, not before (except in #define argument list)
+ - (TODO) Spaces before *, /, &
+ - (TODO) Hex numbers with even digit length
+ - (TODO) Hex numbers in lowercase
+ - (TODO) Braces not on the end of line
+ - (TODO) Line dividers (////...) exactly 80 slashes
+ - (TODO) Not using "* "-style doxy comments continuation lines
+
+Violations that cannot be checked easily:
+ - Spaces around "+" (there are things like "a++", "++a", "a += 1", "X+", "stack +1" and ascii-drawn tables)
+
+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
+
+This script requires LuaFileSystem to be available in the current Lua interpreter.
+--]]
+
+
+
+
+
+-- Check that LFS is installed:
+local hasLfs = pcall(require, "lfs")
+if not(hasLfs) then
+ print("This script requires LuaFileSystem to be installed")
+ os.exit(1)
+end
+local lfs = require("lfs")
+assert(lfs ~= nil)
+
+
+
+
+
+-- 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 =
+{
+ "./Bindings/Bindings.cpp",
+ "./Bindings/DeprecatedBindings.cpp",
+ "./LeakFinder.cpp",
+ "./LeakFinder.h",
+ "./MersenneTwister.h",
+ "./StackWalker.cpp",
+ "./StackWalker.h",
+}
+
+--- 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
+local function ReportViolation(a_FileName, a_LineNumber, a_Message)
+ print(a_FileName .. "(" .. a_LineNumber .. "): " .. a_Message)
+ g_NumViolations = g_NumViolations + 1
+end
+
+
+
+
+
+--- 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
+ ReportViolation(a_FileName, a_LineNum, a_Message .. "(" .. patStart .. " .. " .. patEnd .. ")")
+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 \",\""},
+ {"^\t*[^#].*,[^%s]", "Needs a space after a \",\""}, -- Anywhere except lines starting with "#" - avoid #define params
+}
+
+
+
+
+
+--- 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
+ ReportViolation(a_FileName, numLines, "Missing empty line at file end")
+ 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
+ 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)
+ -- Check against each violation pattern:
+ for _, pat in ipairs(g_ViolationPatterns) do
+ ReportViolationIfFound(a_Line, a_FileName, lineCounter, pat[1], pat[2])
+ end
+
+ 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
+
+ -- If the item is a folder, recurse:
+ local attrs = lfs.attributes(a_ItemName)
+ if (attrs and (attrs.mode == "directory")) then
+ for fnam in lfs.dir(a_ItemName) do
+ if ((fnam ~= ".") and (fnam ~= "..")) then
+ ProcessItem(a_ItemName .. "/" .. fnam)
+ end
+ end
+ return
+ end
+
+ local ext = a_ItemName:match("%.([^/%.]-)$")
+ if (g_ShouldProcessExt[ext]) then
+ ProcessFile(a_ItemName)
+ end
+end
+
+
+
+
+
+-- Process the entire current folder:
+ProcessItem(".")
+
+-- Report final verdict:
+print("Number of violations found: " .. g_NumViolations)
+if (g_NumViolations > 0) then
+ os.exit(2)
+else
+ os.exit(0)
+end
+
+
+
+