ZeroBrane studio
#21
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.
Reply
Thanks given by:
#22
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.
Reply
Thanks given by:
#23
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.
Reply
Thanks given by:
#24
(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.
Reply
Thanks given by:
#25
@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 Wink.
Reply
Thanks given by:
#26
@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,
}
Reply
Thanks given by:
#27
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".
Reply
Thanks given by:
#28
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.
Reply
Thanks given by:
#29
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.
Reply
Thanks given by:
#30
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 Smile And Paul is a great person, he helps me when I try to solve the issues with the debugger integration.
Reply
Thanks given by:




Users browsing this thread: 1 Guest(s)