Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
Not really, because files may reference one another's globals.
For example, our Info.lua file references all the command handlers, that are defined in all the other files. That's why we load Info.lua as the last one - the functions wouldn't be defined otherwise.
Simple example:
-- a.lua:
function Test()
end
-- b.lua
Test()
If a.lua is loaded first, b.lua second, the entire code works and I wouldn't expect any message from the analyzer.
However, if b.lua is loaded first and a.lua second, then the Test() function is undefined when b.lua is executed, and the code won't work. I'd expect a warning from the analyzer that b.lua is using an unknown global.
Posts: 1,450
Threads: 53
Joined: Feb 2011
Thanks: 15
Given 120 thank(s) in 91 post(s)
That should not ever happen though. There is a reason there is an Initialize function, it should be the first place where code is executed. If you are executing code simply by adding code to the Lua state without initializing you're doing it wrong.
Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
Actually it's been done this way ever since we updated the hooks to support multiple registrations with arbitrary handler functions. It allows us to enclose the full functionality of a part of the plugin into one file, without the need to call any special function.
Consider the WorldEdit plugin, for example. It now uses a cPlayerState Lua class that tracks (almost) all of the state for a single player, including the selection. So it's only natural that it registers the hooks that it needs for keeping track of the selection; the hooks are registered in code outside of any functions, so that it executes as soon as MCS loads the plugin.
The Info.lua file is another example - it lists the commands, permissions, documentation and also the handlers; as such it must be a global structure (so that it can be read by external tools such as InfoDump.lua), yet it has to reference functions that are defined in other files.
Posts: 1,450
Threads: 53
Joined: Feb 2011
Thanks: 15
Given 120 thank(s) in 91 post(s)
(03-01-2014, 11:15 PM)xoft Wrote: Actually it's been done this way ever since we updated the hooks to support multiple registrations with arbitrary handler functions. It allows us to enclose the full functionality of a part of the plugin into one file, without the need to call any special function.
Consider the WorldEdit plugin, for example. It now uses a cPlayerState Lua class that tracks (almost) all of the state for a single player, including the selection. So it's only natural that it registers the hooks that it needs for keeping track of the selection; the hooks are registered in code outside of any functions, so that it executes as soon as MCS loads the plugin. In this case there isn't really a problem anyway, the order of initialization is decided in a single file and is not dependent on other files.
(03-01-2014, 11:15 PM)xoft Wrote: The Info.lua file is another example - it lists the commands, permissions, documentation and also the handlers; as such it must be a global structure (so that it can be read by external tools such as InfoDump.lua), yet it has to reference functions that are defined in other files. You could consider wrapping it in a function instead of storing it in a variable. Then it is only evaluated when the function is called eliminating the problem of order of initialization.
Posts: 14
Threads: 0
Joined: Feb 2014
Thanks: 0
Given 0 thank(s) in 0 post(s)
@ xoft, with the current processing, the order won't make any difference as the information about globals is not passed between processed files. There is a "slower" analysis mode that also handles "require" calls, but it's disabled because it's slower, generates false positives, and only looks into "require", but not something like "dofile" or take load order into account.
> yet it has to reference functions that are defined in other files.
Yes, those will get reported as "first use of unknown global function '....'", but those will be reported only once per file. I tried to find a sweet spot between usefulness of the report and all the corner cases that need to be handled for full support.
I think "Analyze All" is still useful in its current form, just not as powerful as it could (theoretically) be .
Posts: 14
Threads: 0
Joined: Feb 2014
Thanks: 0
Given 0 thank(s) in 0 post(s)
03-03-2014, 10:43 AM
(This post was last modified: 03-03-2014, 11:01 AM by FakeTruth.)
@ xoft, you can run this plugin that implements Analyze All for a particular project. Just save it as packages/analyzeall.lua and you should see a new menu item under Analyze that will run the analysis through all lua files in your project. You'll need to get the latest ZBS from github as there were some API changes to make this work. Let me know how it works for you. Thanks.
local G = ...
local id = G.ID("analyzeall.analyzeall")
local menuid
local function analyzeProject()
local frame = ide:GetMainFrame()
local menubar = ide:GetMenuBar()
if menubar:IsChecked(ID_CLEAROUTPUT) then ClearOutput() end
DisplayOutputLn("Analyzing the project code.")
frame:Update()
local errors, warnings = 0, 0
local projectPath = ide:GetProject()
if projectPath then
for _, filePath in ipairs(FileSysGetRecursive(projectPath, true, "*.lua")) do
local warn, err, line = AnalyzeFile(filePath)
if err then
DisplayOutputNoMarker(filePath..'('..line..'): '..err.."\n")
errors = errors + 1
elseif #warn > 0 then
DisplayOutputNoMarker(table.concat(warn, "\n") .. "\n")
warnings = warnings + #warn
end
frame:Update() -- refresh the output with new results
end
end
DisplayOutputLn(("%s error%s and %s warning%s."):format(
errors > 0 and errors or 'no', errors == 1 and '' or 's',
warnings > 0 and warnings or 'no', warnings == 1 and '' or 's'
))
end
return {
name = "Analyze all files",
description = "Analyzes all files in a project.",
author = "Paul Kulchenko",
version = 0.1,
onRegister = function(self)
local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Project")))
local _, analyzepos = ide:FindMenuItem(menu, ID_ANALYZE)
if analyzepos then
menu:Insert(analyzepos+1, id, TR("Analyze All")..KSC(id), TR("Analyze the project source code"))
end
ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, analyzeProject)
end,
onUnRegister = function(self)
local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Project")))
ide:GetMainFrame():Disconnect(id, wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED)
if menuid then menu:Destroy(menuid) end
end,
}
Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
This plugin doesn't do what I expect it to do - when a function is defined in one file and then called from another file, it still reports the call as "unknown global function".
Posts: 14
Threads: 0
Joined: Feb 2014
Thanks: 0
Given 0 thank(s) in 0 post(s)
It's not a limitation of the plugin; it's a limitation of the current implementation of the analysis. As I wrote earlier, all files are processed one by one without any knowledge (for now) of globals are defined in what other files.
You may try to change FAST to `false` in src/editor/inspect.lua as it produces slower, but more thorough analysis; as far as I remember, it checks "required" files, but not those loaded with "dofile". It also looks at table fields, but seems to generate too many messages and enough false positives to keep it turned off by default.
Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
03-05-2014, 03:37 AM
(This post was last modified: 03-05-2014, 04:06 AM by xoft.)
I was more playing with the thought of concatenating all files into a single string and passing that to the analyzer - that should, theoretically, allow it to do a better job with those functions. I might give it a try later on, for now I'm content with what ZBS does and will recommend it to anyone doing plugin development for MCS, especially when on Linux / MacOS. For Windows, Decoda is still my favorite IDE because it allows me to see stack traces in both the Lua code as well as C++ code.
Hmm, I just couldn't keep off of it. I've tried the following snippet and it seems to do what I want, now only it needs converting from the big blob linenumbers back to original files' names and line numbers
-- packages/analyzeall.lua:
local G = ...
local id = G.ID("analyzeall.analyzeall")
local menuid
local function analyzeProject()
local frame = ide:GetMainFrame()
local menubar = ide:GetMenuBar()
if menubar:IsChecked(ID_CLEAROUTPUT) then ClearOutput() end
DisplayOutputLn("Analyzing the project code.")
frame:Update()
local projectPath = ide:GetProject()
local src = {}
local files = {}
if projectPath then
for _, filePath in ipairs(FileSysGetRecursive(projectPath, true, "*.lua")) do
files[#files + 1] = filePath
end
table.sort(files)
for _, fileName in ipairs(files) do
src[#src + 1] = FileRead(fileName)
end
local warn, err, line = AnalyzeString(table.concat(src, "\n"))
if err then
DisplayOutputNoMarker("Error: " .. err .. "\n")
elseif (#warn > 0) then
DisplayOutputNoMarker(table.concat(warn, "\n") .. "\n")
end
frame:Update() -- refresh the output with new results
end
end
return {
name = "Analyze all files as a single blob",
description = "Analyzes all files in a project.",
author = "Paul Kulchenko",
version = 0.1,
onRegister = function(self)
local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Project")))
local _, analyzepos = ide:FindMenuItem(menu, ID_ANALYZE)
if analyzepos then
menu:Insert(analyzepos+1, id, TR("Analyze All")..KSC(id), TR("Analyze the project source code"))
end
ide:GetMainFrame():Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, analyzeProject)
end,
onUnRegister = function(self)
local menu = ide:GetMenuBar():GetMenu(ide:GetMenuBar():FindMenu(TR("&Project")))
ide:GetMainFrame():Disconnect(id, wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED)
if menuid then menu:Destroy(menuid) end
end,
}
-- Add this to src/editor/inspect.lua:
function AnalyzeString(src)
local warn, err, line, pos = M.warnings_from_string(src, "src")
if err then
err = err:gsub("line %d+, char %d+", "syntax error")
end
return warn, err, line, pos
end
Of course I'm just messing around, the code will need proper massaging if we want it production-grade.
Posts: 6,485
Threads: 176
Joined: Jan 2012
Thanks: 131
Given 1075 thank(s) in 852 post(s)
I've tried switching my plugin development to ZBS. I have to say the workflow is a bit different, but I can see some benefits already. One of the most used features for me is that the IDE won't even let me start the debugger if there's a hard syntax error in the file, that alone has saved me so much time already And Paul is a great person, he helps me when I try to solve the issues with the debugger integration.
|