Inintal commit

This commit is contained in:
2026-02-14 15:05:12 +01:00
commit 625297106e
21 changed files with 75105 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
-- SCL Diagnostics - Linter functionality for the LSP server
local M = {}
local parser = require("scl_lsp.parser")
local DiagnosticSeverity = {
Error = 1,
Warning = 2,
Information = 3,
Hint = 4,
}
local function position_to_lsp(line, character)
return {
line = line,
character = character,
}
end
local function range_to_lsp(start_line, start_char, end_line, end_char)
return {
start = position_to_lsp(start_line, start_char),
end = position_to_lsp(end_line, end_char),
}
end
function M.validate_diagnostics(content, workspace_types, root_dir)
local diagnostics = {}
local lines = {}
for line in content:gmatch("([^\n]*)\n") do
table.insert(lines, line)
end
local variables, _ = parser.extract_variables(content)
local types = parser.extract_types(content)
local functions = parser.extract_functions(content)
local all_types = {}
for k, v in pairs(types) do all_types[k] = v end
for k, v in pairs(workspace_types or {}) do all_types[k] = v end
local in_var_section = false
local var_section_start = nil
for line_idx, line in ipairs(lines) do
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
local line_num = line_idx - 1
if trimmed:match("^VAR[_A-Z]*%s*$") or
trimmed:match("^VAR_INPUT%s*$") or
trimmed:match("^VAR_OUTPUT%s*$") or
trimmed:match("^VAR_IN_OUT%s*$") or
trimmed:match("^VAR_TEMP%s*$") or
trimmed:match("^VAR[_ ]*CONSTANT%s*$") or
trimmed:match("^VAR[_ ]*RETAIN%s*$") or
trimmed:match("^VAR[_ ]*NON_RETAIN%s*$") then
in_var_section = true
var_section_start = line_num
elseif trimmed:match("^END_VAR%s*$") then
in_var_section = false
var_section_start = nil
end
if not in_var_section then
local hash_vars = {}
for var in line:gmatch("#[%w_]+") do
local var_name = var:sub(2)
hash_vars[var_name] = true
end
for var_name, _ in pairs(hash_vars) do
if not variables[var_name] and not all_types[var_name] then
local var_start = line:find("#" .. var_name, 1, true)
if var_start then
table.insert(diagnostics, {
range = range_to_lsp(line_num, var_start - 1, line_num, var_start + #var_name - 1),
severity = DiagnosticSeverity.Error,
message = string.format("Undefined variable: %s", var_name),
code = "SCL001",
source = "scl_lsp",
})
end
end
end
end
if in_var_section and trimmed:match("^[%w_]+%s*:") then
local var_name = trimmed:match("^([%w_]+)%s*:")
local var_start = line:find(var_name, 1, true)
if var_name and not var_name:match("^%d") then
local has_hash = line:match("#" .. var_name)
if has_hash then
table.insert(diagnostics, {
range = range_to_lsp(line_num, 0, line_num, #line),
severity = DiagnosticSeverity.Error,
message = "Variable declarations should NOT have '#' prefix in VAR section",
code = "SCL002",
source = "scl_lsp",
})
end
local type_part = line:match(":%s*([^;]+)")
if type_part then
type_part = type_part:gsub("%s*:=.*$", "")
type_part = type_part:gsub("%s*$", "")
type_part = type_part:gsub("^%s+", "")
local base_type = type_part:match("^(%w+)")
if base_type and not all_types[base_type] and
base_type ~= "BOOL" and base_type ~= "INT" and base_type ~= "DINT" and
base_type ~= "REAL" and base_type ~= "LREAL" and base_type ~= "BYTE" and
base_type ~= "WORD" and base_type ~= "DWORD" and base_type ~= "LWORD" and
base_type ~= "CHAR" and base_type ~= "STRING" and base_type ~= "WCHAR" and
base_type ~= "WSTRING" and base_type ~= "TIME" and base_type ~= "DATE" and
base_type ~= "TIME_OF_DAY" and base_type ~= "DATE_AND_TIME" and
base_type ~= "S5TIME" and base_type ~= "LTIME" then
table.insert(diagnostics, {
range = range_to_lsp(line_num, 0, line_num, #line),
severity = DiagnosticSeverity.Warning,
message = string.format("Unknown data type: %s", base_type),
code = "SCL003",
source = "scl_lsp",
})
end
end
end
end
if trimmed:match("^%s*//") or trimmed:match("^%s*%(%*") then
-- Skip comments
else
for assignment in line:gmatch("#[%w_]+%s*:=") do
local var_name = assignment:match("#([%w_]+)")
if var_name and in_var_section then
local assign_start = line:find("#" .. var_name, 1, true)
table.insert(diagnostics, {
range = range_to_lsp(line_num, assign_start - 1, line_num, assign_start + #var_name),
severity = DiagnosticSeverity.Error,
message = "Cannot use '#' prefix for variables in VAR section",
code = "SCL004",
source = "scl_lsp",
})
end
end
end
end
for func_idx, func in ipairs(functions) do
local func_content = ""
for i = func.start + 1, func.final - 1 do
if lines[i + 1] then
func_content = func_content .. lines[i + 1] .. "\n"
end
end
local func_vars, _ = parser.extract_variables(func_content)
local all_vars = {}
for k, v in pairs(variables) do all_vars[k] = v end
for k, v in pairs(func_vars) do
all_vars[k] = v
end
end
return diagnostics
end
return M