summaryrefslogtreecommitdiff
path: root/script/cli/check_worker.lua
blob: 23c34b2c58b4839e84ad3cc424fa45a0359d0dba (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
local lclient   = require 'lclient'()
local furi      = require 'file-uri'
local ws        = require 'workspace'
local files     = require 'files'
local diag      = require 'provider.diagnostic'
local util      = require 'utility'
local jsonb     = require 'json-beautify'
local lang      = require 'language'
local define    = require 'proto.define'
local protoDiag = require 'proto.diagnostic'
local config    = require 'config.config'
local fs        = require 'bee.filesystem'
local provider  = require 'provider'
local await     = require 'await'
require 'plugin'
require 'vm'

lang(LOCALE)

local numThreads = tonumber(NUM_THREADS or 1)
local threadId = tonumber(THREAD_ID or 1)

if type(CHECK_WORKER) ~= 'string' then
    print(lang.script('CLI_CHECK_ERROR_TYPE', type(CHECK_WORKER)))
    return
end

local rootPath = fs.absolute(fs.path(CHECK_WORKER)):string()
local rootUri = furi.encode(rootPath)
if not rootUri then
    print(lang.script('CLI_CHECK_ERROR_URI', rootPath))
    return
end
rootUri = rootUri:gsub("/$", "")

if CHECKLEVEL then
    if not define.DiagnosticSeverity[CHECKLEVEL] then
        print(lang.script('CLI_CHECK_ERROR_LEVEL', 'Error, Warning, Information, Hint'))
        return
    end
end
local checkLevel = define.DiagnosticSeverity[CHECKLEVEL] or define.DiagnosticSeverity.Warning

util.enableCloseFunction()

-- Hash function used to distribute work.
local function hashString(str)
    local hash = 0
    for i = 1, #str do
        hash = (hash * 37 & 0xFFFFFFFF) + str:byte(i, i)
    end
    return hash
end

local lastClock = os.clock()
local results = {}

local function errorhandler(err)
    print(err)
    print(debug.traceback())
end

---@async
xpcall(lclient.start, errorhandler, lclient, function (client)
    await.disable()
    client:registerFakers()

    client:initialize {
        rootUri = rootUri,
    }

    client:register('textDocument/publishDiagnostics', function (params)
        results[params.uri] = params.diagnostics
    end)

    if not QUIET then
        io.write(lang.script('CLI_CHECK_INITING'))
    end

    provider.updateConfig(rootUri)

    ws.awaitReady(rootUri)

    local disables = util.arrayToHash(config.get(rootUri, 'Lua.diagnostics.disable'))
    for name, serverity in pairs(define.DiagnosticDefaultSeverity) do
        serverity = config.get(rootUri, 'Lua.diagnostics.severity')[name] or serverity
        if serverity:sub(-1) == '!' then
            serverity = serverity:sub(1, -2)
        end
        if define.DiagnosticSeverity[serverity] > checkLevel then
            disables[name] = true
        end
    end
    config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true))

    -- Downgrade file opened status to Opened for everything to avoid reporting during compilation on files that do not belong to this thread
    local diagStatus = config.get(rootUri, 'Lua.diagnostics.neededFileStatus')
    for diag, status in pairs(diagStatus) do
        if status == 'Any' or status == 'Any!' then
            diagStatus[diag] = 'Opened!'
        end
    end
    for diag, status in pairs(protoDiag.getDefaultStatus()) do
        if status == 'Any' or status == 'Any!' then
            diagStatus[diag] = 'Opened!'
        end
    end
    config.set(rootUri, 'Lua.diagnostics.neededFileStatus', diagStatus)

    local uris = files.getChildFiles(rootUri)
    local max  = #uris
    for i, uri in ipairs(uris) do
        local hash = hashString(uri) % numThreads + 1
        if hash == threadId then
            files.open(uri)
            diag.doDiagnostic(uri, true)
            -- Print regularly but always print the last entry to ensure that logs written to files don't look incomplete.
            if (os.clock() - lastClock > 0.2 or i == #uris) and not QUIET then
                lastClock = os.clock()
                client:update()
                local output = '\x0D'
                            .. ('>'):rep(math.ceil(i / max * 20))
                            .. ('='):rep(20 - math.ceil(i / max * 20))
                            .. ' '
                            .. ('0'):rep(#tostring(max) - #tostring(i))
                            .. tostring(i) .. '/' .. tostring(max)
                io.write(output)
                local filesWithErrors = 0
                local errors = 0
                for _, diags in pairs(results) do
                    filesWithErrors = filesWithErrors + 1
                    errors = errors + #diags
                end
                if errors > 0 then
                    local errorDetails = ' [' .. lang.script('CLI_CHECK_PROGRESS', errors, filesWithErrors) .. ']'
                    io.write(errorDetails)
                end
                io.flush()
            end
        end
    end
    if not QUIET then
        io.write('\x0D')
    end
end)

local count = 0
for uri, result in pairs(results) do
    count = count + #result
    if #result == 0 then
        results[uri] = nil
    end
end

local outpath = CHECK_OUT_PATH
if outpath == nil then
    outpath = LOGPATH .. '/check.json'
end
-- Always write result, even if it's empty to make sure no one accidentally looks at an old output after a successful run.
util.saveFile(outpath, jsonb.beautify(results))

if not QUIET then
    if count == 0 then
        print(lang.script('CLI_CHECK_SUCCESS'))
    else
        print(lang.script('CLI_CHECK_RESULTS', count, outpath))
    end
end