diff --git a/changelog.md b/changelog.md index d75397a10..83265b5b1 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,8 @@ ## Unreleased +`2025-3-13` +* `NEW` `unnecessary-assert` diagnostic warns when asserting values that are always truthy * `NEW` locale `es-419`, thanks [Felipe Lema](https://codeberg.org/FelipeLema) ## 3.13.9 diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index ee96900c7..82ac431bc 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS = 'This function expects a maximum of {:d} argument(s) but instead it is receiving {:d}.' DIAG_MISS_ARGS = 'This function requires {:d} argument(s) but instead it is receiving {:d}.' +DIAG_UNNECESSARY_ASSERT = +'Unnecessary assert: this expression is always truthy.' DIAG_OVER_MAX_VALUES = 'Only has {} variables, but you set {} values.' DIAG_AMBIGUITY_1 = diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua index 095673f86..e0ca458b8 100644 --- a/locale/en-us/setting.lua +++ b/locale/en-us/setting.lua @@ -406,6 +406,8 @@ config.diagnostics['missing-return-value'] = 'Enable diagnostics for return statements without values although the containing function declares returns.' config.diagnostics['need-check-nil'] = 'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.' +config.diagnostics['unnecessary-assert'] = +'Enable diagnostics for redundant assertions on truthy values.' config.diagnostics['no-unknown'] = 'Enable diagnostics for cases in which the type cannot be inferred.' config.diagnostics['not-yieldable'] = diff --git a/locale/ja-jp/script.lua b/locale/ja-jp/script.lua index 6e1819d98..12fb50f65 100644 --- a/locale/ja-jp/script.lua +++ b/locale/ja-jp/script.lua @@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS = 'この関数は最大で {:d} 個の引数を受け取りますが、{:d} 個の引数が渡されています。' DIAG_MISS_ARGS = 'この関数は少なくとも {:d} 個の引数を必要としますが、{:d} 個しか渡されていません。' +DIAG_UNNECESSARY_ASSERT = +'不要なアサーション: この式は常に真です。' DIAG_OVER_MAX_VALUES = '変数は {} 個しかありませんが、{} 個の値が設定されています。' DIAG_AMBIGUITY_1 = diff --git a/locale/ja-jp/setting.lua b/locale/ja-jp/setting.lua index a522ff944..a8c307324 100644 --- a/locale/ja-jp/setting.lua +++ b/locale/ja-jp/setting.lua @@ -406,6 +406,8 @@ config.diagnostics['missing-return-value'] = -- TODO: need translate! 'Enable diagnostics for return statements without values although the containing function declares returns.' config.diagnostics['need-check-nil'] = -- TODO: need translate! 'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.' +config.diagnostics['unnecessary-assert'] = -- TODO: need translate! +'Enable diagnostics for redundant assertions on truthy values.' config.diagnostics['no-unknown'] = -- TODO: need translate! 'Enable diagnostics for cases in which the type cannot be inferred.' config.diagnostics['not-yieldable'] = -- TODO: need translate! diff --git a/locale/pt-br/script.lua b/locale/pt-br/script.lua index 73d459d75..dd1ea689d 100644 --- a/locale/pt-br/script.lua +++ b/locale/pt-br/script.lua @@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS = 'A função aceita apenas os parâmetros {:d}, mas você passou {:d}.' DIAG_MISS_ARGS = 'A função recebe pelo menos {:d} argumentos, mas há {:d}.' +DIAG_UNNECESSARY_ASSERT = +'Asserção desnecessária: esta expressão é sempre verdadeira.' DIAG_OVER_MAX_VALUES = 'Apenas há {} variáveis, mas você declarou {} valores.' DIAG_AMBIGUITY_1 = diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua index ca04fdc08..465b45f27 100644 --- a/locale/pt-br/setting.lua +++ b/locale/pt-br/setting.lua @@ -406,6 +406,8 @@ config.diagnostics['missing-return-value'] = -- TODO: need translate! 'Enable diagnostics for return statements without values although the containing function declares returns.' config.diagnostics['need-check-nil'] = -- TODO: need translate! 'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.' +config.diagnostics['unnecessary-assert'] = -- TODO: need translate! +'Enable diagnostics for redundant assertions on truthy values.' config.diagnostics['no-unknown'] = -- TODO: need translate! 'Enable diagnostics for cases in which the type cannot be inferred.' config.diagnostics['not-yieldable'] = -- TODO: need translate! diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index 22f16eaac..2b1221ce3 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS = '函数最多接收 {:d} 个参数,但获得了 {:d} 个。' DIAG_MISS_ARGS = '函数最少接收 {:d} 个参数,但获得了 {:d} 个。' +DIAG_UNNECESSARY_ASSERT = +'不必要的断言:此表达式始终为真值。' DIAG_OVER_MAX_VALUES = '只有 {} 个变量,但你设置了 {} 个值。' DIAG_AMBIGUITY_1 = diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua index b610ac196..ece1e6191 100644 --- a/locale/zh-cn/setting.lua +++ b/locale/zh-cn/setting.lua @@ -404,6 +404,8 @@ config.diagnostics['missing-return-value'] = '函数无值返回但函数使用`@return`标记了返回值' config.diagnostics['need-check-nil'] = '变量之前被赋值为`nil`或可选值(可能为 `nil`)' +config.diagnostics['unnecessary-assert'] = +'启用对冗余断言(针对始终为真值的表达式)的诊断' config.diagnostics['no-unknown'] = '变量的未知类型无法推断' config.diagnostics['not-yieldable'] = diff --git a/locale/zh-tw/script.lua b/locale/zh-tw/script.lua index 090fb7ff0..e2618f374 100644 --- a/locale/zh-tw/script.lua +++ b/locale/zh-tw/script.lua @@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS = '函式最多接收 {:d} 個引數,但獲得了 {:d} 個。' DIAG_MISS_ARGS = '函式最少接收 {:d} 個引數,但獲得了 {:d} 個。' +DIAG_UNNECESSARY_ASSERT = +'不必要的斷言:此表達式始終為真值。' DIAG_OVER_MAX_VALUES = '只有 {} 個變數,但你設定了 {} 個值。' DIAG_AMBIGUITY_1 = diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua index dfb716db8..1bc254aa9 100644 --- a/locale/zh-tw/setting.lua +++ b/locale/zh-tw/setting.lua @@ -404,6 +404,8 @@ config.diagnostics['missing-return-value'] = -- TODO: need translate! 'Enable diagnostics for return statements without values although the containing function declares returns.' config.diagnostics['need-check-nil'] = -- TODO: need translate! 'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.' +config.diagnostics['unnecessary-assert'] = -- TODO: need translate! +'Enable diagnostics for redundant assertions on truthy values.' config.diagnostics['no-unknown'] = -- TODO: need translate! 'Enable diagnostics for cases in which the type cannot be inferred.' config.diagnostics['not-yieldable'] = -- TODO: need translate! diff --git a/script/core/diagnostics/unnecessary-assert.lua b/script/core/diagnostics/unnecessary-assert.lua new file mode 100644 index 000000000..a72087193 --- /dev/null +++ b/script/core/diagnostics/unnecessary-assert.lua @@ -0,0 +1,29 @@ +local files = require 'files' +local guide = require 'parser.guide' +local vm = require 'vm' +local lang = require 'language' +local await = require 'await' + +---@async +return function (uri, callback) + local state = files.getState(uri) + if not state then + return + end + + ---@async + guide.eachSourceType(state.ast, 'call', function (source) + await.delay() + local currentFunc = guide.getParentFunction(source) + if currentFunc and source.node.special == 'assert' and source.args[1] then + local argNode = vm.compileNode(source.args[1]) + if argNode:alwaysTruthy() then + callback { + start = source.node.start, + finish = source.node.finish, + message = lang.script('DIAG_UNNECESSARY_ASSERT'), + } + end + end + end) +end diff --git a/script/proto/diagnostic.lua b/script/proto/diagnostic.lua index 61b8ff4bd..c17b185c3 100644 --- a/script/proto/diagnostic.lua +++ b/script/proto/diagnostic.lua @@ -78,6 +78,7 @@ m.register { 'cast-type-mismatch', 'return-type-mismatch', 'inject-field', + 'unnecessary-assert', } { group = 'type-check', severity = 'Warning', diff --git a/script/vm/node.lua b/script/vm/node.lua index f1b77513b..599a4ba99 100644 --- a/script/vm/node.lua +++ b/script/vm/node.lua @@ -119,6 +119,32 @@ function mt:hasFalsy() return false end +---Almost an inverse of hasFalsy, but stricter about "any" and "unknown" types. +---@return boolean +function mt:alwaysTruthy() + if self.optional then + return false + end + if #self == 0 then + return false + end + for _, c in ipairs(self) do + if c.type == 'nil' + or (c.type == 'global' and c.cate == 'type' and c.name == 'nil') + or (c.type == 'global' and c.cate == 'type' and c.name == 'false') + or (c.type == 'global' and c.cate == 'type' and c.name == 'any') + or (c.type == 'global' and c.cate == 'type' and c.name == 'boolean') + or (c.type == 'global' and c.cate == 'type' and c.name == 'doc.type.boolean') + or (c.type == 'global' and c.cate == 'type' and c.name == 'unknown') + or not self:hasKnownType() + or (c.type == 'boolean' and c[1] == false) + or (c.type == 'doc.type.boolean' and c[1] == false) then + return false + end + end + return true +end + ---@return boolean function mt:hasKnownType() for _, c in ipairs(self) do diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua index 99a5dc248..e2961d8fb 100644 --- a/test/diagnostics/init.lua +++ b/test/diagnostics/init.lua @@ -100,6 +100,7 @@ check 'missing-parameter' check 'missing-return-value' check 'missing-return' check 'need-check-nil' +check 'unnecessary-assert' check 'newfield-call' check 'newline-call' check 'not-yieldable' diff --git a/test/diagnostics/unnecessary-assert.lua b/test/diagnostics/unnecessary-assert.lua new file mode 100644 index 000000000..6e97c6460 --- /dev/null +++ b/test/diagnostics/unnecessary-assert.lua @@ -0,0 +1,56 @@ +TEST [[ +local a +assert(a) + +---@type boolean +local b +assert(b) + +---@type any +local c +assert(c) + +---@type unknown +local d +assert(d) + +---@type boolean +local e +assert(e) + +---@type number? +local f +assert(f) + +assert(false) + +assert(nil and 5) + +---@return string?, string? +local function f() end + +assert(f()) +]] + +TEST [[ +(true) +]] + +TEST [[ +---@return integer +local function hi() + return 1 +end +(hi(1)) +]] + +TEST [[ +({}, 'hi') +]] + +TEST [[ +---@return string, string? +local function f() end + +(f()) +]]