When writing Lua code, I felt that the built-in methods for string were a bit lacking. So I added this function to the string class.
---@param self string
function string:firstToUpper() -- setting undefined field 'firstToUpper' of global 'string'string
return (self:gsub("^%l", string.upper))
end
However, when running luacheck, it reported warning: (W142) setting undefined field firstToUpper of global string
. I don't want to add an ignore rule in my .luacheckrc
file. What changes should I make to my code to prevent this error?
I have try to write in this way
local mt = getmetatable("")
mt.__index.firstToUpper = function(self)
return (self:gsub("^%l", string.upper))
end
it does not report this warning, but I have not seen this kind of writing before, my question is:
is ""
a table when handle it in Lua interpreter?
and how can I improve my code
is "" a table when handle it in Lua interpreter?
No, it's a string, just like you would expect. Every value in Lua can have a metatable. This includes strings, hence why getmetatable()
will work on them. Types other than tables and full userdata have one metatable per type. This also includes strings, that's why you can modify metatable of ""
and it propagates to all strings.
See 2.4 - Metatables and Metamethods for more detailed explanation.
and how can I improve my code
Do you need to? Linters are just tools. You don't need to listen to it. Of course, modifying built-in modules is usually considered bad practice in any language, but does it matter in your case?
If you want to listen to it, you could create another module that "inherits" from built-in string
module and then use debug.setmetatable()
to modify metatable of strings:
local ext = {
firstToUpper = function (str)
return (str:gsub("^%l", string.upper))
end,
}
setmetatable(ext, {__index=string})
debug.setmetatable("", {__index=ext})
print(("hello"):firstToUpper()) --> Hello
This way you end up using debug
module and you still modify part of built-in state. Is it any better in your case? If not you can use ext.firstToUpper("hello")
but this way you lose convenience.
Alternatively, you can define read_globals
in configuration file:
read_globals = {
string = {
fields = {
firstToUpper = {
read_only=false,
},
},
},
}
But at this point I'd consider it being a very targeted ignore which you are trying to avoid. Remember that this would disable detection of any unintentional changes to firstToUpper
(or anything else defined similarly).
For me, currently, the best option would be to modify and define all needed globals in a file(s) that wouldn't be checked or would be checked with loose rules and then define strict luacheck configuration file using globals
and read_globals
for all other files that would use the modified global configuration.