summaryrefslogtreecommitdiff
path: root/script/core/diagnostics/unused-function.lua
blob: 81c7e457ed6ddcd60f8ac7ce14d64f9b098187c4 (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
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 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
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

local function collect(ast, white, roots, links)
    guide.eachSourceType(ast, 'function', function (src)
        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 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
    collect(state.ast, white, roots, links)

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

    for source in pairs(white) do
        if client.isVSCode() 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