Skip to content

Commit 6a506c3

Browse files
committed
Rework class syntax to make the code base legible for language servers
1 parent 78951a2 commit 6a506c3

202 files changed

Lines changed: 3323 additions & 2285 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ luajit/
1818
spec/test_results.log
1919
spec/test_generation.log
2020
src/luacov.stats.out
21+
runtime/lua/debugger.lua
2122

2223
# Release
2324
manifest-updated.xml
@@ -38,4 +39,8 @@ src/Data/TimelessJewelData/*.bin
3839
runtime/imgui.ini
3940
runtime/SimpleGraphic/SimpleGraphic.log
4041

41-
src/poe_api_response.json
42+
src/poe_api_response.json
43+
runtime/SimpleGraphic/Screenshots
44+
45+
.emmyrc.json
46+
.luarc.json

CONTRIBUTING.md

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ It is recommended to use it over the built-in Lua plugins.
136136
Please note that EmmyLua is not available for other editors based on Visual Studio Code,
137137
such as [VSCodium](https://vscodium.com) or [Eclipse Theia](https://theia-ide.org) but can be built from source if needed.
138138

139+
Another alternative on VSCode is to use [sumneko's Lua language server](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) along with [actboy168's debugger](https://marketplace.visualstudio.com/items?itemName=actboy168.lua-debug). These can potentially offer more features than EmmyLua, such as conditional breakpoints.
140+
141+
## Runtime environment
142+
143+
It is recommended that you configure your IDE to include `src/_SimpleGraphic.def.lua` somehow. Path of Building runs inside a special Lua environment via SimpleGraphic which implements a small API. These are not defined inside the project, which means that the aforementioned meta/hint file is required for the IDE to know which functions exist. As the file is not included in code, it must be explicitly mentioned as a library in whichever language server you are using, or otherwise it will not be read.
144+
145+
This file is normally not executed, but does contain basic implementations for parts of the API, which allows many parts of PoB to work without running inside SimpleGraphic. If you wish to test individual changes, it might be possible to do so through a script using Luajit directly. To do so, see HeadlessWrapper.lua for an example. It should be noted that some parts of the API (such as subscripts) are not implemented, which means some parts of PoB are unusable.
146+
139147
### Visual Studio Code
140148

141149
1. Create a new <kbd>Debug Configuration</kbd> of type <kbd>EmmyLua New Debug</kbd>
@@ -168,20 +176,54 @@ such as [VSCodium](https://vscodium.com) or [Eclipse Theia](https://theia-ide.or
168176
1. In VSCode click <kbd>Start Debugging</kbd> (the green icon) or press <kbd>F5</kbd>
169177
1. The debugger should connect
170178

179+
You might also want to use actboy168 debugger. This is possible by using for example the following launch.json configuration:
180+
181+
```json
182+
{
183+
"version": "0.2.0",
184+
"configurations": [
185+
{
186+
"name": "🍄attach",
187+
"type": "lua",
188+
"request": "attach",
189+
"stopOnEntry": false,
190+
"address": "127.0.0.1:12306",
191+
"luaVersion": "luajit",
192+
},
193+
194+
]
195+
}
196+
```
197+
198+
Then, similarly to the EmmyLua example:
199+
200+
1. Find the sub-folder that looks like `actboy168.lua-debug-x.y.z-win32-x64` in `%USERPROFILE%/.vscode/extensions`. Navigate to it and find the `debugger.lua` script under the script folder. Copy this to `runtime/lua`.
201+
2. Copy-paste the following code snippet into `launch:OnInit()`:
202+
```lua
203+
local debugger = require("debugger"):start("127.0.0.1:12306")
204+
-- debugger:event("wait") -- Uncomment this line if you want PoB to wait until the debugger is attached.
205+
```
206+
171207

172208
#### Excluding directories from EmmyLua
173209

174210
Depending on the amount of system ram you have available and the amount that gets assigned to the jvm running the emmylua language server you might run into issues when trying to debug Path of building.
175-
Files in `/Data` `/Export` and `/TreeData` can be massive and cause the EmmyLua language server to use a significant amount of memory. Sometimes causing the language server to crash. To avoid this and speed up initialization consider adding an `.emmyrc.json` file to the `.vscode` folder in the root of the Path of building folder with the following content:
211+
Files in `/Data` `/Export` and `/TreeData` can be massive and cause the EmmyLua language server to use a significant amount of memory. Sometimes causing the language server to crash. To avoid this and speed up initialization consider adding an `.emmyrc.json` to the root of the Path of building folder with the following content:
176212

177213
```json
178214
{
179215
"$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json",
180216
"runtime": {
181-
"version": "LuaJIT"
217+
"version": "LuaJIT",
218+
// this is not technically correct as LoadModule behaviour can
219+
// differ from require, but it is useful for now
220+
"requireLikeFunction": ["LoadModule"],
182221
},
183222
"workspace": {
184223
"ignoreGlobs": [
224+
"**/*_spec.lua",
225+
"spec/**/*.lua",
226+
"runtime/lua/sha1/lua53_ops.lua",
185227
"**/src/Data/**/*.lua",
186228
"**/src/TreeData/**/*.lua",
187229
"**/src/Modules/ModParser.lua"
@@ -190,6 +232,46 @@ Files in `/Data` `/Export` and `/TreeData` can be massive and cause the EmmyLua
190232
}
191233
```
192234

235+
This file can be customised according to what you want. It is a good idea to ignore test files as these tend to add things to the global namespace, which will look confusing, and they are designed to be run by Busted. `lua53_ops.lua` produces errors and doesn't actually get imported when using LuaJIT. It can be useful to keep the data and mod parser files, but generally this will increase the time the LSP takes to index the project on startup.
236+
237+
### Excluding directories from Sumneko's language server
238+
239+
If you prefer to not use EmmyLua, the following configuration works well for Sumneko's VS Code extension:
240+
241+
```json
242+
{
243+
"Lua.workspace.ignoreDir": [
244+
".vscode",
245+
// these files add things to global that aren't there in normal
246+
// operation
247+
"spec/*",
248+
"src/Export/*",
249+
"src/HeadlessWrapper.lua",
250+
251+
// this has lua 5.3 code which produces errors, but doesn't actually run
252+
"src/runtime/lua/sha1/*",
253+
254+
// avoid overriding the below library setting
255+
"src/_SimpleGraphic.def.lua",
256+
],
257+
"Lua.diagnostics.disable": ["inject-field"],
258+
// disables diagnostics even when you open one of the above
259+
"Lua.diagnostics.ignoredFiles": "Disable",
260+
"Lua.runtime.version": "LuaJIT",
261+
"Lua.workspace.preloadFileSize": 1000,
262+
// this is not technically correct as LoadModule behaviour can
263+
// differ from require, but it is useful for now
264+
"Lua.runtime.special": {
265+
"LoadModule": "require"
266+
},
267+
"Lua.workspace.library": [
268+
"src/_SimpleGraphic.def.lua"
269+
],
270+
}
271+
```
272+
273+
The extension will automatically skip large files from being preloaded (controlled by `Lua.workspace.preloadFileSize`), so they don't have to be excluded. The configuration file can be found by pressing Ctrl-Shift-P and selecting `Preferences: Open Workspace Settings (JSON)`. If you wish to check test files, you can remove the "ignoredFiles" option and install the busted, LuaFileSystem, and luassert LuaLS addons through `Lua: Open Addon Manager`.
274+
193275
### PyCharm Community / IntelliJ Idea Community
194276

195277
1. Create a new "Debug Configuration" of type "Emmy Debugger(NEW)".
@@ -223,6 +305,13 @@ More tests can be added to this folder to test specific functionality, or new te
223305

224306
Please try to include tests for your new features in your pull request. Additionally, if your pr breaks a test that should be passing please update it accordingly.
225307

308+
It can also be a good idea to install `busted` locally as it tends to be faster to execute:
309+
310+
1. Install Luajit (due to PoB accessing the jit library, it non-Luajit versions might not work)
311+
2. Install [Luarocks](https://luarocks.org/) (for example, through Scoop or your Linux package manager)
312+
3. Run `luarocks install busted`
313+
4. Run `busted --lua=luajit` to run the tests. You can also use e.g. `-p TestUtils_spec.lua` to run a specific file.
314+
226315
### Debugging tests
227316
When running tests with a docker container it is possible to use EmmyLua for debugging. Paste in the following right under `function launch:OnInit()` in `./src/Launch.lua`:
228317
```lua

spec/System/TestCommon_spec.lua

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
describe("Common", function()
2+
describe("Class creation and use", function()
3+
it("produces error when parent constructors are not called", function()
4+
local ParentClass = newClass("ConstructorTestParentClass")
5+
function ParentClass:ConstructorTestParentClass()
6+
return self
7+
end
8+
local ChildClass = newClass("ConstructorTestProblemChildClass", "ConstructorTestParentClass")
9+
function ChildClass:ConstructorTestProblemChild()
10+
-- Intentionally does not call self:ConstructorTestParentClass()
11+
return self
12+
end
13+
common.classes.ConstructorTestParent = ParentClass
14+
common.classes.ConstructorTestProblemChild = ChildClass
15+
16+
assert.has_error(function()
17+
new("ConstructorTestProblemChild"):ConstructorTestProblemChild()
18+
end, "Parent class 'ConstructorTestParentClass' of class 'ConstructorTestProblemChild' must be initialised")
19+
common.classes.ConstructorTestParent = nil
20+
common.classes.ConstructorTestProblemChild = nil
21+
end)
22+
it("produces an error if additional arguments are passed", function()
23+
local StupidClass = newClass("NewAbuse")
24+
function StupidClass:NewAbuse(someParam)
25+
return self
26+
end
27+
28+
common.classes.NewAbuse = StupidClass
29+
30+
assert.has_no.errors(function()
31+
local newObj = new("NewAbuse"):NewAbuse("fish")
32+
end)
33+
assert.has_error(function()
34+
local newObj = new("NewAbuse", "look I'm using the old syntax")
35+
end)
36+
end)
37+
it("produces an error if it calls a parent class without giving it self", function()
38+
local ParentClass = newClass("ConstructorTestParentClass")
39+
function ParentClass:ConstructorTestParentClass()
40+
return self
41+
end
42+
43+
local ChildClass = newClass("ConstructorTestProblemChildClass", "ConstructorTestParentClass")
44+
function ChildClass:ConstructorTestProblemChild()
45+
self.ConstructorTestParentClass()
46+
return self
47+
end
48+
49+
common.classes.ConstructorTestParent = ParentClass
50+
common.classes.ConstructorTestProblemChild = ChildClass
51+
52+
assert.has_error(function()
53+
new("ConstructorTestProblemChild"):ConstructorTestProblemChild()
54+
end)
55+
common.classes.ConstructorTestParent = nil
56+
common.classes.ConstructorTestProblemChild = nil
57+
end)
58+
it("produces an error if its constructor doesn't return the object", function()
59+
local StupidClass = newClass("StupidClass")
60+
function StupidClass:StupidClass()
61+
end
62+
63+
common.classes.StupidClass = StupidClass
64+
65+
assert.has_error(function()
66+
new("StupidClass"):StupidClass()
67+
end, "Class StupidClass constructor did not return a value")
68+
end)
69+
end)
70+
end)

spec/System/TestCompareBuySimilar_spec.lua

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ describe("Buy similar mod stat matching", function()
44

55
describe("addModEntries mod matching", function()
66
it("matches impossible escape mods as options", function()
7-
local fromNothing = new("Item", [[
7+
local fromNothing = new("Item"):Item([[
88
Impossible Escape
99
Viridian Jewel
1010
LevelReq: 0
@@ -32,7 +32,7 @@ Corrupted]])
3232
end)
3333

3434
it("matches thread of hope radius as an option", function()
35-
local thread = new("Item", [[
35+
local thread = new("Item"):Item([[
3636
Rarity: UNIQUE
3737
Thread of Hope
3838
Crimson Jewel
@@ -52,7 +52,7 @@ Passage]])
5252
end)
5353

5454
it("combines mods that are the same stat", function()
55-
local lifeDiamond = new("Item", [[
55+
local lifeDiamond = new("Item"):Item([[
5656
Test Subject
5757
Diamond
5858
Implicits: 0
@@ -67,7 +67,7 @@ Implicits: 0
6767
assert.equal("+50 to Maximum Life", StripEscapes(entries[1].formattedLines[2]))
6868
assert.equal(150, entries[1].value)
6969

70-
local lifelessDiamond = new("Item", [[
70+
local lifelessDiamond = new("Item"):Item([[
7171
Test Subject
7272
Diamond
7373
Implicits: 0
@@ -82,7 +82,7 @@ Implicits: 0
8282
end)
8383

8484
it("is not case-sensitive", function ()
85-
local funnyItem = new("Item", [[
85+
local funnyItem = new("Item"):Item([[
8686
Test Subject
8787
Diamond
8888
Implicits: 1
@@ -93,7 +93,7 @@ Implicits: 1
9393
end)
9494

9595
it("does not combine implicit and explicit mods", function()
96-
local lifelessDiamond = new("Item", [[
96+
local lifelessDiamond = new("Item"):Item([[
9797
Test Subject
9898
Diamond
9999
Implicits: 1
@@ -140,7 +140,7 @@ Implicits: 1
140140
end)
141141

142142
local function openPopup(item, slotName)
143-
item = item or new("Item", "Rarity: Rare\nTest Ring\nRuby Ring\nImplicits: 0\n+50 to maximum Life")
143+
item = item or new("Item"):Item("Rarity: Rare\nTest Ring\nRuby Ring\nImplicits: 0\n+50 to maximum Life")
144144
bs.openPopup(item, slotName or "Ring", build)
145145
local controls = main.popups[1].controls
146146
_G.Copy = function(url) copiedUrl = url end
@@ -194,7 +194,7 @@ Implicits: 1
194194
end)
195195

196196
it("encodes option values in the generated query", function()
197-
local item = new("Item", [[
197+
local item = new("Item"):Item([[
198198
Rarity: UNIQUE
199199
Impossible Escape
200200
Viridian Jewel
@@ -214,7 +214,7 @@ Corrupted]])
214214
end)
215215

216216
it("inverts reduced stat bounds in the generated query", function()
217-
local item = new("Item", [[
217+
local item = new("Item"):Item([[
218218
Rarity: Rare
219219
Test Ring
220220
Ruby Ring
@@ -230,7 +230,7 @@ Implicits: 0
230230
end)
231231

232232
it("uses a count group for ambiguous trade stats", function()
233-
local item = new("Item", [[
233+
local item = new("Item"):Item([[
234234
Rarity: Rare
235235
Test Ring
236236
Ruby Ring

spec/System/TestImport_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe("TestImport", function()
7171
end)
7272

7373
function importAndReimportWithOldJewel(shouldDelete)
74-
local oldJewel = new("Item", [[Rarity: RARE
74+
local oldJewel = new("Item"):Item([[Rarity: RARE
7575
TEST JEWEL
7676
Crimson Jewel
7777
Crafted: true

spec/System/TestItemDBControl_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe("ItemDBControl", function()
3333
return item ~= invalidItem
3434
end,
3535
}
36-
local control = new("ItemDBControl", nil, { 0, 0, 100, 100 }, itemsTab, {
36+
local control = new("ItemDBControl"):ItemDBControl(nil, { 0, 0, 100, 100 }, itemsTab, {
3737
list = { invalidItem, betterItem, worseItem },
3838
}, "RARE")
3939
control.sortDetail = {

spec/System/TestItemMods_spec.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,12 +614,12 @@ describe("TetsItemMods", function()
614614
end)
615615

616616
it("shows a fallback tooltip when an item's base is no longer supported", function()
617-
local item = new("Item", [[
617+
local item = new("Item"):Item([[
618618
Rarity: Unique
619619
Legacy Item
620620
Removed Base
621621
]])
622-
local tooltip = new("Tooltip")
622+
local tooltip = new("Tooltip"):Tooltip()
623623

624624
assert.has_no.errors(function()
625625
build.itemsTab:AddItemTooltip(tooltip, item)

0 commit comments

Comments
 (0)