summaryrefslogtreecommitdiff
path: root/love2dToAPK/tools/tools/zbstudio-old-win/src/editor/inspect.lua
diff options
context:
space:
mode:
Diffstat (limited to 'love2dToAPK/tools/tools/zbstudio-old-win/src/editor/inspect.lua')
-rw-r--r--love2dToAPK/tools/tools/zbstudio-old-win/src/editor/inspect.lua243
1 files changed, 243 insertions, 0 deletions
diff --git a/love2dToAPK/tools/tools/zbstudio-old-win/src/editor/inspect.lua b/love2dToAPK/tools/tools/zbstudio-old-win/src/editor/inspect.lua
new file mode 100644
index 0000000..a9eef01
--- /dev/null
+++ b/love2dToAPK/tools/tools/zbstudio-old-win/src/editor/inspect.lua
@@ -0,0 +1,243 @@
+-- Copyright 2012-15 Paul Kulchenko, ZeroBrane LLC
+-- Integration with LuaInspect
+---------------------------------------------------------
+
+local M, LA, LI, T = {}
+
+local function init()
+ if LA then return end
+
+ -- metalua is using 'checks', which noticeably slows the execution
+ -- stab it with out own
+ package.loaded.checks = {}
+ checks = function() end
+
+ LA = require "luainspect.ast"
+ LI = require "luainspect.init"
+ T = require "luainspect.types"
+end
+
+function M.pos2line(pos)
+ return pos and 1 + select(2, M.src:sub(1,pos):gsub(".-\n[^\n]*", ""))
+end
+
+function M.warnings_from_string(src, file)
+ init()
+
+ local ast, err, linenum, colnum = LA.ast_from_string(src, file)
+ if not ast and err then return nil, err, linenum, colnum end
+
+ LI.uninspect(ast)
+ if ide.config.staticanalyzer.infervalue then
+ local tokenlist = LA.ast_to_tokenlist(ast, src)
+ LI.clear_cache()
+ LI.inspect(ast, tokenlist, src)
+ LI.mark_related_keywords(ast, tokenlist, src)
+ else
+ -- stub out LI functions that depend on tokenlist,
+ -- which is not built in the "fast" mode
+ local ec, iv = LI.eval_comments, LI.infer_values
+ LI.eval_comments, LI.infer_values = function() end, function() end
+
+ LI.inspect(ast, nil, src)
+ LA.ensure_parents_marked(ast)
+
+ LI.eval_comments, LI.infer_values = ec, iv
+ end
+
+ local globinit = {arg = true} -- skip `arg` global variable
+ local spec = GetSpec(wx.wxFileName(file):GetExt())
+ for k in pairs(spec and GetApi(spec.apitype or "none").ac.childs or {}) do
+ globinit[k] = true
+ end
+
+ M.src, M.file = src, file
+ return M.show_warnings(ast, globinit)
+end
+
+local function cleanError(err)
+ return err and err:gsub(".-:%d+: file%s+",""):gsub(", line (%d+), char %d+", ":%1")
+end
+
+function AnalyzeFile(file)
+ local src, err = FileRead(file)
+ if not src and err then return nil, TR("Can't open file '%s': %s"):format(file, err) end
+
+ local warn, err, line, pos = M.warnings_from_string(src, file)
+ return warn, cleanError(err), line, pos
+end
+
+function AnalyzeString(src, file)
+ local warn, err, line, pos = M.warnings_from_string(src, file or "<string>")
+ return warn, cleanError(err), line, pos
+end
+
+function M.show_warnings(top_ast, globinit)
+ local warnings = {}
+ local function warn(msg, linenum, path)
+ warnings[#warnings+1] = (path or M.file or "?") .. ":" .. (linenum or M.pos2line(M.ast.pos) or 0) .. ": " .. msg
+ end
+ local function known(o) return not T.istype[o] end
+ local function index(f) -- build abc.def.xyz name recursively
+ if not f or f.tag ~= 'Index' or not f[1] or not f[2] then return end
+ local main = f[1].tag == 'Id' and f[1][1] or index(f[1])
+ return main and type(f[2][1]) == "string" and (main .. '.' .. f[2][1]) or nil
+ end
+ local globseen, isseen, fieldseen = globinit or {}, {}, {}
+ LA.walk(top_ast, function(ast)
+ M.ast = ast
+ local path, line = tostring(ast.lineinfo):gsub('<C|','<'):match('<([^|]+)|L(%d+)')
+ local name = ast[1]
+ -- check if we're masking a variable in the same scope
+ if ast.localmasking and name ~= '_' and
+ ast.level == ast.localmasking.level then
+ local linenum = ast.localmasking.lineinfo
+ and tostring(ast.localmasking.lineinfo.first):match('|L(%d+)')
+ or M.pos2line(ast.localmasking.pos)
+ local parent = ast.parent and ast.parent.parent
+ local func = parent and parent.tag == 'Localrec'
+ warn("local " .. (func and 'function' or 'variable') .. " '" ..
+ name .. "' masks earlier declaration " ..
+ (linenum and "on line " .. linenum or "in the same scope"),
+ line, path)
+ end
+ if ast.localdefinition == ast and not ast.isused and
+ not ast.isignore then
+ local parent = ast.parent and ast.parent.parent
+ local isparam = parent and parent.tag == 'Function'
+ if isparam then
+ if name ~= 'self' then
+ local func = parent.parent and parent.parent.parent
+ local assignment = not func.tag or func.tag == 'Set' or func.tag == 'Localrec'
+ -- anonymous functions can also be defined in expressions,
+ -- for example, 'Op' or 'Return' tags
+ local expression = not assignment and func.tag
+ local func1 = func[1][1]
+ local fname = assignment and func1 and type(func1[1]) == 'string'
+ and func1[1] or (func1 and func1.tag == 'Index' and index(func1))
+ -- "function foo(bar)" => func.tag == 'Set'
+ -- `Set{{`Id{"foo"}},{`Function{{`Id{"bar"}},{}}}}
+ -- "local function foo(bar)" => func.tag == 'Localrec'
+ -- "local _, foo = 1, function(bar)" => func.tag == 'Local'
+ -- "print(function(bar) end)" => func.tag == nil
+ -- "a = a or function(bar) end" => func.tag == nil
+ -- "return(function(bar) end)" => func.tag == 'Return'
+ -- "function tbl:foo(bar)" => func.tag == 'Set'
+ -- `Set{{`Index{`Id{"tbl"},`String{"foo"}}},{`Function{{`Id{"self"},`Id{"bar"}},{}}}}
+ -- "function tbl.abc:foo(bar)" => func.tag == 'Set'
+ -- `Set{{`Index{`Index{`Id{"tbl"},`String{"abc"}},`String{"foo"}}},{`Function{{`Id{"self"},`Id{"bar"}},{}}}},
+ warn("unused parameter '" .. name .. "'" ..
+ (func and (assignment or expression)
+ and (fname and func.tag
+ and (" in function '" .. fname .. "'")
+ or " in anonymous function")
+ or ""),
+ line, path)
+ end
+ else
+ if parent and parent.tag == 'Localrec' then -- local function foo...
+ warn("unused local function '" .. name .. "'", line, path)
+ else
+ warn("unused local variable '" .. name .. "'; "..
+ "consider removing or replacing with '_'", line, path)
+ end
+ end
+ end
+ -- added check for "fast" mode as ast.seevalue relies on value evaluation,
+ -- which is very slow even on simple and short scripts
+ if ide.config.staticanalyzer.infervalue and ast.isfield
+ and not(known(ast.seevalue.value) and ast.seevalue.value ~= nil) then
+ if not fieldseen[name] then
+ fieldseen[name] = true
+ local var = index(ast.parent)
+ local parent = ast.parent and var
+ and (" in '"..var:gsub("%."..name.."$","").."'")
+ or ""
+
+ local tblref = ast.parent and ast.parent[1]
+ local localparam = (tblref and tblref.localdefinition
+ and tblref.localdefinition.isparam)
+ if not localparam then
+ warn("first use of unknown field '" .. name .."'"..parent,
+ ast.lineinfo and tostring(ast.lineinfo.first):match('|L(%d+)'), path)
+ end
+ end
+ elseif ast.tag == 'Id' and not ast.localdefinition and not ast.definedglobal then
+ if not globseen[name] then
+ globseen[name] = true
+ local parent = ast.parent
+ -- if being called and not one of the parameters
+ if parent and parent.tag == 'Call' and parent[1] == ast then
+ warn("first use of unknown global function '" .. name .. "'", line, path)
+ else
+ warn("first use of unknown global variable '" .. name .. "'", line, path)
+ end
+ end
+ elseif ast.tag == 'Id' and not ast.localdefinition and ast.definedglobal then
+ local parent = ast.parent and ast.parent.parent
+ if parent and parent.tag == 'Set' and not globseen[name] -- report assignments to global
+ -- only report if it is on the left side of the assignment
+ -- this is a bit tricky as it can be assigned as part of a, b = c, d
+ -- `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
+ and parent[1] == ast.parent
+ and parent[2][1].tag ~= "Function" then -- but ignore global functions
+ warn("first assignment to global variable '" .. name .. "'", line, path)
+ globseen[name] = true
+ end
+ elseif (ast.tag == 'Set' or ast.tag == 'Local') and #(ast[2]) > #(ast[1]) then
+ warn(("value discarded in multiple assignment: %d values assigned to %d variable%s")
+ :format(#(ast[2]), #(ast[1]), #(ast[1]) > 1 and 's' or ''), line, path)
+ end
+ local vast = ast.seevalue or ast
+ local note = vast.parent
+ and (vast.parent.tag == 'Call' or vast.parent.tag == 'Invoke')
+ and vast.parent.note
+ if note and not isseen[vast.parent] and type(name) == "string" then
+ isseen[vast.parent] = true
+ warn("function '" .. name .. "': " .. note, line, path)
+ end
+ end)
+ return warnings
+end
+
+local frame = ide.frame
+
+-- insert after "Compile" item
+local _, menu, compilepos = ide:FindMenuItem(ID_COMPILE)
+if compilepos then
+ menu:Insert(compilepos+1, ID_ANALYZE, TR("Analyze")..KSC(ID_ANALYZE), TR("Analyze the source code"))
+end
+
+local function analyzeProgram(editor)
+ -- save all files (if requested) for "infervalue" analysis to keep the changes on disk
+ if ide.config.editor.saveallonrun and ide.config.staticanalyzer.infervalue then SaveAll(true) end
+ if ide:GetLaunchedProcess() == nil and not ide:GetDebugger():IsConnected() then ClearOutput() end
+ DisplayOutput("Analyzing the source code")
+ frame:Update()
+
+ local editorText = editor:GetTextDyn()
+ local doc = ide:GetDocument(editor)
+ local filePath = doc:GetFilePath() or doc:GetFileName()
+ local warn, err = M.warnings_from_string(editorText, filePath)
+ if err then -- report compilation error
+ DisplayOutputLn((": not completed.\n%s"):format(cleanError(err)))
+ return false
+ end
+
+ DisplayOutputLn((": %s warning%s.")
+ :format(#warn > 0 and #warn or 'no', #warn == 1 and '' or 's'))
+ DisplayOutputNoMarker(table.concat(warn, "\n") .. (#warn > 0 and "\n" or ""))
+
+ return true -- analyzed ok
+end
+
+frame:Connect(ID_ANALYZE, wx.wxEVT_COMMAND_MENU_SELECTED,
+ function ()
+ ActivateOutput()
+ local editor = GetEditor()
+ if not analyzeProgram(editor) then
+ CompileProgram(editor, { reportstats = false, keepoutput = true })
+ end
+ end)
+frame:Connect(ID_ANALYZE, wx.wxEVT_UPDATE_UI,
+ function (event) event:Enable(GetEditor() ~= nil) end)