summaryrefslogtreecommitdiff
path: root/script/core/diagnostics/inject-field.lua
blob: 2866eef8f8bdaa3d0f0ee36af3a0677b7c3e9937 (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
local files   = require 'files'
local vm      = require 'vm'
local lang    = require 'language'
local guide   = require 'parser.guide'
local await   = require 'await'
local hname   = require 'core.hover.name'

local skipCheckClass = {
    ['unknown']       = true,
    ['any']           = true,
    ['table']         = true,
}

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

    ---@async
    local function checkInjectField(src)
        await.delay()

        local node = src.node
        if not node then
            return
        end
        local ok
        for view in vm.getInfer(node):eachView(uri) do
            if skipCheckClass[view] then
                return
            end
            ok = true
        end
        if not ok then
            return
        end

        local isExact
        local class = vm.getDefinedClass(uri, node)
        if class then
            for _, doc in ipairs(class:getSets(uri)) do
                if vm.docHasAttr(doc, 'exact') then
                    isExact = true
                    break
                end
            end
            if not isExact then
                return
            end
            if  src.type == 'setmethod'
            and not guide.getSelfNode(node) then
                return
            end
        end

        for _, def in ipairs(vm.getDefs(src)) do
            local dnode = def.node
            if dnode
            and not isExact
            and vm.getDefinedClass(uri, dnode) then
                return
            end
            if def.type == 'doc.type.field' then
                return
            end
            if def.type == 'doc.field' then
                return
            end
        end

        local howToFix = ''
        if not isExact then
            howToFix = lang.script('DIAG_INJECT_FIELD_FIX_CLASS', {
                node = hname(node),
                fix  = '---@class',
            })
            for _, ndef in ipairs(vm.getDefs(node)) do
                if ndef.type == 'doc.type.table' then
                    howToFix = lang.script('DIAG_INJECT_FIELD_FIX_TABLE', {
                        fix   = '[any]: any',
                    })
                    break
                end
            end
        end

        local message = lang.script('DIAG_INJECT_FIELD', {
            class = vm.getInfer(node):view(uri),
            field = guide.getKeyName(src),
            fix   = howToFix,
        })
        if     src.type == 'setfield' and src.field then
            callback {
                start   = src.field.start,
                finish  = src.field.finish,
                message = message,
            }
        elseif src.type == 'setmethod' and src.method then
            callback {
                start   = src.method.start,
                finish  = src.method.finish,
                message = message,
            }
        end
    end
    guide.eachSourceType(ast.ast, 'setfield',  checkInjectField)
    guide.eachSourceType(ast.ast, 'setmethod', checkInjectField)

    ---@async
    local function checkExtraTableField(src)
        await.delay()

        if not src.bindSource then
            return
        end
        if not vm.docHasAttr(src, 'exact') then
            return
        end
        local value = src.bindSource.value
        if not value or value.type ~= 'table' then
            return
        end
        for _, field in ipairs(value) do
            local defs = vm.getDefs(field)
            for _, def in ipairs(defs) do
                if def.type == 'doc.field' then
                    goto nextField
                end
            end
            local message = lang.script('DIAG_INJECT_FIELD', {
                class = vm.getInfer(src):view(uri),
                field = guide.getKeyName(src),
                fix   = '',
            })
            callback {
                start   = field.start,
                finish  = field.finish,
                message = message,
            }
            ::nextField::
        end
    end

    guide.eachSourceType(ast.ast, 'doc.class', checkExtraTableField)
end