summaryrefslogtreecommitdiff
path: root/script/core/diagnostics/unused-function.lua
blob: 1145036db3c41b4e196b7fef2034d860e480ba4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
local files   = require 'files'
local guide   = require 'parser.guide'
local vm      = require 'vm'
local define  = require 'proto.define'
local lang    = require 'language'
local await   = require 'await'
local client  = require 'client'
local util    = require 'utility'

local function isToBeClosed(source)
    if not source.attrs then
        return false
    end
    for _, attr in ipairs(source.attrs) do
        if attr[1] == 'close' then
            return true
        end
    end
    return false
end

---@param source parser.object?
---@return boolean
local function isValidFunction(source)
    if not source then
        return false
    end
    if source.type == 'main' then
        return false
    end
    local parent = source.parent
    if not parent then
        return false
    end
    if  parent.type ~= 'local'
    and parent.type ~= 'setlocal' then
        return false
    end
    if isToBeClosed(parent) then
        return false
    end
    return true
end

---@async
local function collect(ast, white, roots, links)
    ---@async
    guide.eachSourceType(ast, 'function', function (src)
        await.delay()
        if not isValidFunction(src) then
            return
        end
        local loc = src.parent
        if loc.type == 'setlocal' then
            loc = loc.node
        end
        for _, ref in ipairs(loc.ref or {}) do
            if ref.type == 'getlocal' then
                local func = guide.getParentFunction(ref)
                if not func or not isValidFunction(func) or roots[func] then
                    roots[src] = true
                    return
                end
                if not links[func] then
                    links[func] = {}
                end
                links[func][#links[func]+1] = src
            end
        end
        white[src] = true
    end)

    return white, roots, links
end

local function turnBlack(source, black, white, links)
    if black[source] then
        return
    end
    black[source] = true
    white[source] = nil
    for _, link in ipairs(links[source] or {}) do
        turnBlack(link, black, white, links)
    end
end

---@async
return function (uri, callback)
    local state = files.getState(uri)
    if not state then
        return
    end

    if vm.isMetaFile(uri) then
        return
    end

    local black = {}
    local white = {}
    local roots = {}
    local links = {}

    collect(state.ast, white, roots, links)

    for source in pairs(roots) do
        turnBlack(source, black, white, links)
    end

    local tagSupports = client.getAbility('textDocument.completion.completionItem.tagSupport.valueSet')
    local supportUnnecessary = tagSupports and util.arrayHas(tagSupports, define.DiagnosticTag.Unnecessary)

    for source in pairs(white) do
        if supportUnnecessary then
            callback {
                start   = source.start,
                finish  = source.finish,
                tags    = { define.DiagnosticTag.Unnecessary },
                message = lang.script.DIAG_UNUSED_FUNCTION,
            }
        else
            callback {
                start   = source.keyword[1],
                finish  = source.keyword[2],
                tags    = { define.DiagnosticTag.Unnecessary },
                message = lang.script.DIAG_UNUSED_FUNCTION,
            }
        end
    end
end