2017-04-03 09:55:49 +00:00
local SUITE = require ( ' TestSuite ' ) . new ( ' function ' )
SUITE : setEnviroment {
format = require ( ' pretty ' )
}
--------------------------------------------------------------------------------
2017-06-25 10:18:24 +00:00
-- Compat
if not loadstring then loadstring = load end -- Lua 5.3 compat
local HAS_ADV_GETLOCAL = not not debug.getinfo ( 1 , ' u ' ) . nparams -- Lua 5.1 compat
local HAS_UNICODE_IDEN = not not loadstring ' local ϕ = 1; return ϕ ' -- Lua 5.1 compat
local HAS_JIT_LIBRARY = type ( rawget ( _G , ' jit ' ) ) == ' table ' -- Non-LuaJIT compat
--
2017-04-03 09:55:49 +00:00
local function format_test ( t )
if t.adv_getlocal and not HAS_ADV_GETLOCAL then return end
2017-06-24 16:53:59 +00:00
if t.single then
return SUITE : addTest ( t.name or t.expect , function ( )
local format = format ( { t.input } , t.options ) : gsub ( ' ^{%s+(.-)%s+}$ ' , function ( a ) return ' { ' .. a .. ' } ' end )
assert_equal ( ' { ' .. t.expect .. ' } ' , format )
end , { line = debug.getinfo ( 2 ) . currentline } )
end
2017-04-03 11:49:18 +00:00
SUITE : addTest ( t.name or t.expect , function ( )
2017-06-24 16:53:59 +00:00
assert_equal ( t.expect , format ( t.input , t.options ) )
2017-04-12 13:15:03 +00:00
end , { line = debug.getinfo ( 2 ) . currentline } )
2017-04-03 09:55:49 +00:00
end
2017-06-24 16:53:59 +00:00
local function curline ( delta ) return debug.getinfo ( 2 ) . currentline + ( delta or 0 ) end
local TEST_UPVALUE = 42
2017-04-03 09:55:49 +00:00
--------------------------------------------------------------------------------
2017-06-24 16:53:59 +00:00
-- Basic inline functions
2017-04-03 09:55:49 +00:00
format_test {
2017-07-15 18:43:25 +00:00
name = ' Function basic formatting ' ,
2017-04-03 09:55:49 +00:00
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
single = true ,
2017-04-03 09:55:49 +00:00
input = function ( ) end ,
expect = ' function () ... end ' ,
}
format_test {
2017-06-24 16:53:59 +00:00
name = ' Function with a single argument ' ,
2017-04-03 09:55:49 +00:00
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
single = true ,
2017-04-03 09:55:49 +00:00
input = function ( a ) end ,
expect = ' function (a) ... end ' ,
}
format_test {
2017-06-24 16:53:59 +00:00
name = ' Function with multiple arguments ' ,
2017-04-03 09:55:49 +00:00
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
single = true ,
2017-04-03 09:55:49 +00:00
input = function ( a , b ) end ,
expect = ' function (a, b) ... end ' ,
}
format_test {
2017-06-24 16:53:59 +00:00
name = ' Function with vararg ' ,
2017-04-03 09:55:49 +00:00
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
single = true ,
2017-04-03 09:55:49 +00:00
input = function ( ... ) end ,
expect = ' function (...) ... end ' ,
}
format_test {
2017-06-24 16:53:59 +00:00
name = ' Function with vararg and multiple arguments ' ,
2017-04-03 09:55:49 +00:00
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
single = true ,
2017-04-03 09:55:49 +00:00
input = function ( a , b , ... ) end ,
expect = ' function (a, b, ...) ... end ' ,
}
format_test {
2017-07-15 18:43:25 +00:00
name = ' Function with upvalues don \' t affected by them in single mode 1 ' ,
2017-04-03 09:55:49 +00:00
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
single = true ,
input = function ( ) l = TEST_UPVALUE end ,
expect = ' function () ... end ' ,
2017-04-03 09:55:49 +00:00
}
format_test {
2017-07-15 18:43:25 +00:00
name = ' Function with upvalues don \' t affected by them in single mode 2 ' ,
2017-04-03 09:55:49 +00:00
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
single = true ,
input = function ( ) TEST_UPVALUE = true end ,
expect = ' function () ... end ' ,
2017-04-03 09:55:49 +00:00
}
2017-06-24 16:53:59 +00:00
--------------------------------------------------------------------------------
-- Basic elaborate functions
local func_line = curline ( 3 )
2017-04-03 09:55:49 +00:00
format_test {
2017-06-24 16:53:59 +00:00
name = ' Singleline elaborate function ' ,
input = function ( ) end ,
2017-07-14 14:51:19 +00:00
expect = ' function () \n -- Source file: \' ./test/test_function.lua \' [Line: ' .. func_line .. ' ] \n \n ... \n end ' ,
2017-04-03 09:55:49 +00:00
}
2017-06-24 16:53:59 +00:00
local func_line = curline ( 3 )
2017-04-03 09:55:49 +00:00
format_test {
2017-06-24 16:53:59 +00:00
name = ' Multiline elaborate function ' ,
input = function ( )
end ,
2017-07-14 14:51:19 +00:00
expect = ' function () \n -- Source file: \' ./test/test_function.lua \' [Lines: ' .. func_line .. ' - ' .. ( func_line + 1 ) .. ' ] \n \n ... \n end ' ,
2017-04-03 09:55:49 +00:00
}
2017-06-24 16:53:59 +00:00
local func_line = curline ( 3 ) -- Must be exactly 3 lines above function
2017-04-03 09:55:49 +00:00
format_test {
2017-06-24 16:53:59 +00:00
name = ' Elaborate function with upvalue included 1 ' ,
input = function ( ) l = TEST_UPVALUE end ,
adv_getlocal = true ,
2017-07-14 14:51:19 +00:00
expect = ' function () \n -- Source file: \' ./test/test_function.lua \' [Line: ' .. func_line .. ' ] \n -- Up values: { TEST_UPVALUE = 42 } \n \n ... \n end '
2017-04-03 09:55:49 +00:00
}
2017-06-24 16:53:59 +00:00
local func_line = curline ( 3 ) -- Must be exactly 3 lines above function
2017-04-03 09:55:49 +00:00
format_test {
2017-06-24 16:53:59 +00:00
name = ' Elaborate function with upvalue included 2 ' ,
input = function ( ) TEST_UPVALUE = true end ,
adv_getlocal = true ,
2017-07-14 14:51:19 +00:00
expect = ' function () \n -- Source file: \' ./test/test_function.lua \' [Line: ' .. func_line .. ' ] \n -- Up values: { TEST_UPVALUE = 42 } \n \n ... \n end '
2017-04-03 09:55:49 +00:00
}
2017-06-24 18:06:36 +00:00
--------------------------------------------------------------------------------
-- Elaborate functions with documentation
format_test {
name = ' Basic Func with docs ' ,
input = function ( )
-- This is docs
return true
end ,
expect = ' function () \n -- This is docs \n \n ... \n end ' ,
}
2017-06-24 16:53:59 +00:00
format_test {
2017-06-24 18:06:36 +00:00
name = ' Comments after other code won \' t be included as docs ' ,
2017-06-24 16:53:59 +00:00
input = function ( )
2017-06-24 18:06:36 +00:00
-- This is also docs
2017-06-24 16:53:59 +00:00
if true then return false end
2017-06-24 18:06:36 +00:00
-- Some other comment, that won't appear as docs.
return true
end ,
expect = ' function () \n -- This is also docs \n \n ... \n end ' ,
}
format_test {
name = ' We can leave a space between doc lines ' ,
input = function ( )
-- This is docs
-- This is also docs
return true
end ,
expect = ' function () \n -- This is docs \n -- This is also docs \n \n ... \n end ' ,
}
format_test {
name = ' We can also leave a line with an empty comment ' ,
input = function ( )
-- This is docs
--
-- This is also docs
2017-06-24 16:53:59 +00:00
return true
end ,
2017-06-24 18:06:36 +00:00
expect = ' function () \n -- This is docs \n -- \n -- This is also docs \n \n ... \n end ' ,
}
format_test {
name = ' No opvalues when docs are there ' ,
input = function ( )
-- This is docs
return TEST_UPVALUE
end ,
expect = ' function () \n -- This is docs \n \n ... \n end ' ,
}
format_test {
name = ' Can find docs when body contains the word "function" ' ,
input = function ( )
-- Hi
_function ( )
end ,
expect = ' function () \n -- Hi \n \n ... \n end ' ,
2017-06-24 16:53:59 +00:00
}
2017-04-03 09:55:49 +00:00
2017-07-14 14:51:19 +00:00
format_test {
name = ' Can find docs from string-loaded function ' ,
input = loadstring ' return function () \n --Hello \n end ' ( ) ,
expect = ' function () \n -- Hello \n \n ... \n end ' ,
}
format_test {
name = ' String-loaded functions without docs, won \' t display Source file comment ' ,
input = loadstring ' return function () end ' ( ) ,
expect = ' function () \n -- Loaded from string \n \n ... \n end ' ,
}
2017-06-24 16:53:59 +00:00
--------------------------------------------------------------------------------
-- Builtins
2017-04-03 09:55:49 +00:00
format_test {
2017-06-24 16:53:59 +00:00
single = true ,
2017-04-03 09:55:49 +00:00
input = math.abs ,
expect = ' builtin function (x) ... end ' ,
}
format_test {
input = math.abs ,
2017-06-09 15:24:05 +00:00
expect = ' builtin function (x) \n -- math.abs \n -- Returns the absolute value of x. \n \n ... \n end ' ,
2017-04-03 09:55:49 +00:00
}
format_test {
2017-06-24 16:53:59 +00:00
single = true ,
2017-04-03 09:55:49 +00:00
input = math.random ,
expect = ' builtin function ([m [, n]) ... end ' ,
}
format_test {
input = math.random ,
2017-06-24 18:37:43 +00:00
expect = ' builtin function ([m [, n]) \n -- math.random \n -- When called without arguments, returns a uniform pseudo-random real \n -- number in the range [0,1). When called with an integer number m, \n -- math.random returns a uniform pseudo-random integer in the range [1, m]. \n -- When called with two integer numbers m and n, math.random returns a \n -- uniform pseudo-random integer in the range [m, n]. \n \n ... \n end ' ,
2017-04-03 09:55:49 +00:00
}
format_test {
input = string.byte ,
2017-06-24 18:37:43 +00:00
expect = ' builtin function (s [, i [, j]]) \n -- string.byte \n -- Returns the internal numerical codes of the characters s[i], s[i+1], \n -- ..., s[j]. The default value for i is 1; the default value for j is \n -- i. \n -- Note that numerical codes are not necessarily portable across \n -- platforms. \n \n ... \n end ' ,
2017-04-03 09:55:49 +00:00
}
2017-04-03 11:49:18 +00:00
-- short_builtins option: If an builtin is expected to be available by some name
-- in a standard enviroment, return that name, instead of other complex info.
format_test {
2017-06-24 16:53:59 +00:00
-- NOTE: These tests may be counter to intention, soon.
2017-04-03 11:49:18 +00:00
input = math.random ,
options = { short_builtins = true } ,
expect = ' math.random ' ,
}
format_test {
2017-06-24 16:53:59 +00:00
-- NOTE: These tests may be counter to intention, soon.
2017-04-03 11:49:18 +00:00
input = { math.cos , math.sin , math.abs } ,
options = { short_builtins = true } ,
2017-04-14 10:19:23 +00:00
expect = ' { math.cos, math.sin, math.abs } ' ,
2017-04-03 11:49:18 +00:00
}
2017-06-24 16:53:59 +00:00
format_test {
name = ' Replace function with short version, if key ' ,
input = { [ math.random ] = 365 } ,
expect = ' { [math.random] = 365 } ' ,
}
--------------------------------------------------------------------------------
-- Embedding functions loaded with loadstring
format_test {
2017-07-15 18:43:25 +00:00
name = ' Embed functions loaded with loadstring ' ,
2017-06-24 16:53:59 +00:00
single = true ,
input = loadstring ( ' return function (a, b) return a + b end ' ) ( ) ,
expect = ' function (a, b) return a + b end ' ,
}
format_test {
2017-07-15 18:43:25 +00:00
name = ' Embed functions with extra whitespace stripped ' ,
2017-06-24 16:53:59 +00:00
single = true ,
input = loadstring ( ' return function (a, b) \n return a + b \n end ' ) ( ) ,
expect = ' function (a, b) return a + b end ' ,
}
format_test {
2017-07-15 19:25:32 +00:00
name = ' Embedding nested function, when on same line is too hard 1 ' ,
2017-06-24 16:53:59 +00:00
single = true ,
adv_getlocal = true ,
2017-07-15 19:25:32 +00:00
input = loadstring ( ' return function () return function () end end ' ) ( ) ,
expect = ' function () ... end ' ,
2017-06-24 16:53:59 +00:00
}
format_test {
2017-07-15 19:25:32 +00:00
name = ' Embedding nested function, when on same line is too hard 2 ' ,
2017-06-24 16:53:59 +00:00
single = true ,
adv_getlocal = true ,
2017-07-15 19:25:32 +00:00
input = loadstring ( ' return function () return function () end end ' ) ( ) ( ) ,
2017-06-24 16:53:59 +00:00
expect = ' function () ... end ' ,
}
format_test {
2017-07-15 19:25:32 +00:00
name = ' Embedding nested function, when they end on different lines is too hard ' ,
2017-06-24 16:53:59 +00:00
single = true ,
adv_getlocal = true ,
2017-07-15 19:25:32 +00:00
input = loadstring ( ' return function () return function () end \n end ' ) ( ) ( ) ,
2017-06-24 16:53:59 +00:00
expect = ' function () ... end ' ,
}
2017-06-24 18:06:36 +00:00
format_test {
2017-07-15 18:43:25 +00:00
name = ' Embed functions which contains the word "function" ' ,
2017-06-24 18:06:36 +00:00
single = true ,
adv_getlocal = true ,
input = loadstring ( ' return function () function_body() end ' ) ( ) ,
expect = ' function () function_body() end ' ,
}
2017-07-15 18:10:49 +00:00
format_test {
2017-07-15 18:43:25 +00:00
name = ' Embed functions defined with OOP style ' ,
2017-07-15 18:10:49 +00:00
single = true ,
adv_getlocal = true ,
input = loadstring ( ' local obj = {} \n function obj:a () return self end \n return obj.a ' ) ( ) ,
expect = ' function (self) return self end ' ,
}
2017-07-15 18:59:55 +00:00
--[[ NOTE: This turned out to be hard. Requires traversal of AST.
2017-07-15 18:10:49 +00:00
format_test {
2017-07-15 18:43:25 +00:00
name = ' Embed correct function, when two are on the same line, but with different arguments 1 ' ,
2017-07-15 18:10:49 +00:00
single = true ,
adv_getlocal = true ,
input = loadstring ' return { a = function (a) return a end, b = function (b) return b end } ' ( ) [ ' a ' ] ,
expect = ' function (a) return a end ' ,
}
format_test {
2017-07-15 18:43:25 +00:00
name = ' Embed correct function, when two are on the same line, but with different arguments 2 ' ,
single = true ,
adv_getlocal = true ,
input = loadstring ' return { a = function (a) return a end, b = function (b) return b end } ' ( ) [ ' b ' ] ,
expect = ' function (b) return b end ' ,
}
2017-07-15 18:59:55 +00:00
--]]
2017-07-15 18:43:25 +00:00
format_test {
name = ' If embedding a function would be too long, ignore ' ,
2017-07-15 18:10:49 +00:00
single = true ,
adv_getlocal = true ,
input = loadstring ( ' return function () return 1234578901235789012357890 end ' ) ( ) ,
expect = ' function () ... end ' ,
}
format_test {
2017-07-15 18:43:25 +00:00
name = ' If embedding a function would include newline, ignore ' ,
2017-07-15 18:10:49 +00:00
single = true ,
adv_getlocal = true ,
input = loadstring ( ' return function () -- Hi \n return 1234 end ' ) ( ) ,
expect = ' function () ... end ' ,
}
2017-07-14 14:51:19 +00:00
2017-06-24 16:53:59 +00:00
--------------------------------------------------------------------------------
-- Indent functions nicely
format_test {
2017-07-15 18:10:49 +00:00
name = ' Align the tail part, letting people focus on the important aspects. ' ,
2017-06-24 16:53:59 +00:00
input = { random = math.random , abs = math.abs } ,
expect = ' { \n abs = builtin function (x) ... end, \n random = builtin function ([m [, n]) ... end \n } ' ,
}
format_test {
2017-07-15 18:10:49 +00:00
name = ' Align the function part, even if some are builtin and some are not. ' ,
2017-06-24 16:53:59 +00:00
adv_getlocal = true ,
input = { random = math.random , abs = function ( x ) return x < 0 and - x or x end } ,
expect = ' { \n abs = function (x) ... end, \n random = builtin function ([m [, n]) ... end \n } ' ,
}
format_test {
2017-07-15 18:10:49 +00:00
name = ' Align the function part, even if one is loaded from a string. ' ,
2017-06-24 16:53:59 +00:00
adv_getlocal = true ,
input = { random = math.random , abs = loadstring ( ' return function () return 1 end ' ) ( ) } ,
expect = ' { \n abs = function () return 1 end, \n random = builtin function ([m [, n]) ... end \n } ' ,
}
format_test {
2017-07-15 18:10:49 +00:00
name = ' Align the end part when both are loaded from strings. ' ,
2017-06-24 16:53:59 +00:00
adv_getlocal = true ,
input = { a = loadstring ' return function(a) return a end ' ( ) , b = loadstring ' return function (...) return ... end ' ( ) } ,
expect = ' { \n a = function (a) return a end, \n b = function (...) return ... end \n } ' ,
}
format_test {
2017-07-15 18:10:49 +00:00
name = ' Align without special indent if there is function modifier. ' ,
2017-06-24 16:53:59 +00:00
adv_getlocal = true ,
input = { max = function ( a , b ) return a > b and a or b end ,
abs = function ( x ) return x < 0 and - x or x end
} ,
expect = ' { \n abs = function (x) ... end, \n max = function (a, b) ... end \n } ' ,
}
2017-06-25 10:18:24 +00:00
if HAS_UNICODE_IDEN then
format_test {
2017-07-15 18:10:49 +00:00
name = ' Align functions with unicode-named parameters nicely ' ,
2017-06-25 10:18:24 +00:00
adv_getlocal = true ,
2017-07-15 18:43:25 +00:00
input = loadstring ' return { \n function (ψ) return ψ end, \n function (b) return b end \n } ' ( ) ,
expect = ' { \n function (ψ) return ψ end \n function (b) return b end \n } ' ,
2017-06-25 10:18:24 +00:00
}
end
2017-06-24 16:53:59 +00:00
2017-04-03 11:49:18 +00:00
--------------------------------------------------------------------------------
-- Closure creation
2017-06-24 16:53:59 +00:00
-- _include_closure option: If a function uses upvalues, return code that creates
2017-04-03 11:49:18 +00:00
-- a closure for that function, including the function code.
-- NOTE: Without AST traversal, it's impossible to garentee that values refered
-- by multiple recursive closures are the same, because they can actually refer
-- to different variables with the same name, as the following example shows:
--
-- local val_a = 1
-- local func_a = function () return val_a end
-- local val_a = 2
-- local func_c = function () return func_a() + val_a end
2017-06-24 16:53:59 +00:00
-- NOTE: The following tests are all EXPERIMENTAL! All of the tests use features
-- which may change at any time.
format_test {
name = ' Closures do not affect non-upvalue function ' ,
adv_getlocal = true ,
options = { _include_closure = true } ,
input = function ( a , b ) return ( a > b ) and a or b end ,
expect = ' function (a, b) return (a > b) and a or b end ' ,
}
format_test {
name = ' Closures do not affect builtins ' ,
input = math.abs ,
options = { _include_closure = true } ,
expect = ' builtin function (x) \n -- math.abs \n -- Returns the absolute value of x. \n \n ... \n end ' ,
}
2017-04-03 11:49:18 +00:00
do
local input_func = ( function ( )
local i = 0
return function ( a ) i = i + a ; return i end
end ) ( )
for i = 1 , 10 do input_func ( 1 ) end
format_test {
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
options = { _include_closure = true } ,
2017-04-03 11:49:18 +00:00
input = input_func ,
expect = ' (function () local i = 10; return function (a) i = i + a; return i end end)() ' ,
}
end
do
local input_func = loadstring ( [ [
return function ( )
local i = 0
return function ( a ) i = i + a ; return i end
end ] ] ) ( ) ( )
for i = 1 , 10 do input_func ( 1 ) end
format_test {
name = ' Can include variables in closure ' ,
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
options = { _include_closure = true } ,
2017-04-03 11:49:18 +00:00
input = input_func ,
expect = ' (function () local i = 10; return function (a) i = i + a; return i end end)() ' ,
}
end
format_test {
name = ' Can include functions in closure ' ,
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
options = { _include_closure = true } ,
2017-04-03 11:49:18 +00:00
input = ( function ( )
local custom_max = function ( a , b ) return ( a > b ) and a or b end
return function ( a , b ) return custom_max ( a , b ) + 2 end
end ) ( ) ,
expect = ' (function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)() ' ,
}
format_test {
name = ' Can include functions defined with `function <name> () ...` style ' ,
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
options = { _include_closure = true } ,
2017-04-03 11:49:18 +00:00
input = ( function ( )
local function custom_max ( a , b ) return ( a > b ) and a or b end
return function ( a , b ) return custom_max ( a , b ) + 2 end
end ) ( ) ,
expect = ' (function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)() ' ,
}
do
local custom_max = function ( a , b ) return ( a > b ) and a or b end
format_test {
name = ' Can include functions from outside scope into closure ' ,
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
options = { _include_closure = true } ,
2017-04-03 11:49:18 +00:00
input = ( function ( )
return function ( a , b ) return custom_max ( a , b ) + 2 end
end ) ( ) ,
expect = ' (function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)() ' ,
}
end
2017-07-15 18:10:49 +00:00
2017-04-03 11:49:18 +00:00
do
local a_func = function ( x ) return x + 2 end
local b_func = function ( x ) return a_func ( x ) * a_func ( x ) end
local c_func = function ( x ) return b_func ( a_func ( x ) ) end
format_test {
name = ' Can support recursive closures ' ,
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
options = { _include_closure = true } ,
2017-04-03 11:49:18 +00:00
input = c_func ,
expect = ' (function () local b_func = (function () local a_func = function (x) return x + 2 end; return function (x) return a_func(x) * a_func(x) end end)(); local a_func = function (x) return x + 2 end; return function (x) return b_func(a_func(x)) end end)() ' ,
}
end
2017-04-03 09:55:49 +00:00
--------------------------------------------------------------------------------
2017-04-03 11:49:18 +00:00
--[[
format_test {
adv_getlocal = true ,
2017-06-24 16:53:59 +00:00
options = { _all_function_info = true , max_depth = 2 } ,
2017-04-03 11:49:18 +00:00
input = function ( ) end ,
expect = ' ' ,
}
--]]
2017-04-03 09:55:49 +00:00
return SUITE