xref: /netbsd-src/usr.bin/xlint/lint1/check-msgs.lua (revision 9e211f359920a7b95ff05505ca6ee27fa088acb5)
12e28c523Srillig#! /usr/bin/lua
2*9e211f35Srillig-- $NetBSD: check-msgs.lua,v 1.22 2024/03/01 17:22:55 rillig Exp $
32e28c523Srillig
42e28c523Srillig--[[
52e28c523Srillig
639a12fe1Srilligusage: lua ./check-msgs.lua *.c *.y
72e28c523Srillig
8cf4174e4SrilligCheck that the message text in the comments of the C source code matches the
9cf4174e4Srilligactual user-visible message text in err.c.
102e28c523Srillig
112e28c523Srillig]]
122e28c523Srillig
132e28c523Srillig
1468e4158dSrilliglocal function load_messages()
1568e4158dSrillig  local msgs = {} ---@type table<string>string
162e28c523Srillig
1768e4158dSrillig  local f = assert(io.open("err.c"))
182e28c523Srillig  for line in f:lines() do
19bc8fe445Srillig    local msg, id = line:match("%s*\"(.+)\",%s*// (Q?%d+)$")
202e28c523Srillig    if msg ~= nil then
2168e4158dSrillig      msgs[id] = msg
222e28c523Srillig    end
232e28c523Srillig  end
242e28c523Srillig
252e28c523Srillig  f:close()
262e28c523Srillig
272e28c523Srillig  return msgs
282e28c523Srilligend
292e28c523Srillig
302e28c523Srillig
314d5c51ccSrilliglocal had_errors = false
324d5c51ccSrillig---@param fmt string
334d5c51ccSrilligfunction print_error(fmt, ...)
344d5c51ccSrillig  print(fmt:format(...))
354d5c51ccSrillig  had_errors = true
364d5c51ccSrilligend
374d5c51ccSrillig
384d5c51ccSrillig
394d5c51ccSrilliglocal function check_message(fname, lineno, id, comment, msgs)
402e28c523Srillig  local msg = msgs[id]
412e28c523Srillig
422e28c523Srillig  if msg == nil then
4368e4158dSrillig    print_error("%s:%d: id=%s not found", fname, lineno, id)
442e28c523Srillig    return
452e28c523Srillig  end
462e28c523Srillig
4790c29d84Srillig  msg = msg:gsub("/%*", "**")
4890c29d84Srillig  msg = msg:gsub("%*/", "**")
4990c29d84Srillig  msg = msg:gsub("\\(.)", "%1")
50cf4174e4Srillig
512e28c523Srillig  if comment == msg then
522e28c523Srillig    return
532e28c523Srillig  end
542e28c523Srillig
55d4b0f2feSrillig  local prefix = comment:match("^(.-)%s*%.%.%.$")
56d4b0f2feSrillig  if prefix ~= nil and msg:find(prefix, 1, 1) == 1 then
572e28c523Srillig    return
582e28c523Srillig  end
592e28c523Srillig
6068e4158dSrillig  print_error("%s:%d:   id=%-3s   msg=%-40s   comment=%s",
612e28c523Srillig    fname, lineno, id, msg, comment)
622e28c523Srilligend
632e28c523Srillig
64715e58e7Srilliglocal message_prefix = {
65715e58e7Srillig  error = "",
66715e58e7Srillig  error_at = "",
67715e58e7Srillig  warning = "",
68715e58e7Srillig  warning_at = "",
69715e58e7Srillig  query_message = "Q",
70715e58e7Srillig  c99ism = "",
71715e58e7Srillig  c11ism = "",
7229b0195fSrillig  c23ism = "",
73715e58e7Srillig  gnuism = "",
7441c1949fSrillig}
752e28c523Srillig
764d5c51ccSrilliglocal function check_file(fname, msgs)
772e28c523Srillig  local f = assert(io.open(fname, "r"))
782e28c523Srillig  local lineno = 0
792e28c523Srillig  local prev = ""
802e28c523Srillig  for line in f:lines() do
812e28c523Srillig    lineno = lineno + 1
822e28c523Srillig
8341c1949fSrillig    local func, id = line:match("^%s+([%w_]+)%((%d+)[),]")
84715e58e7Srillig    local prefix = message_prefix[func]
85715e58e7Srillig    if prefix then
86715e58e7Srillig      id = prefix .. id
87d4b0f2feSrillig      local comment = prev:match("^%s+/%* (.+) %*/$")
882e28c523Srillig      if comment ~= nil then
894d5c51ccSrillig        check_message(fname, lineno, id, comment, msgs)
90f78ee5c0Srillig      else
9168e4158dSrillig        print_error("%s:%d: missing comment for %s: /* %s */",
92f78ee5c0Srillig          fname, lineno, id, msgs[id])
932e28c523Srillig      end
942e28c523Srillig    end
952e28c523Srillig
962e28c523Srillig    prev = line
972e28c523Srillig  end
982e28c523Srillig
992e28c523Srillig  f:close()
1002e28c523Srilligend
1012e28c523Srillig
1022e28c523Srillig
103f963c8e4Srilliglocal function file_contains(filename, text)
104f963c8e4Srillig  local f = assert(io.open(filename, "r"))
105a75e3514Srillig  local found = f:read("a"):find(text, 1, true)
106f963c8e4Srillig  f:close()
107a75e3514Srillig  return found
108f963c8e4Srilligend
109f963c8e4Srillig
1104d5c51ccSrillig
111a8a62f37Srillig-- Ensure that each test file for a particular message mentions the full text
112a8a62f37Srillig-- of that message and the message ID.
113f963c8e4Srilliglocal function check_test_files(msgs)
114f963c8e4Srillig  local testdir = "../../../tests/usr.bin/xlint/lint1"
115a8a62f37Srillig  local cmd = ("cd '%s' && printf '%%s\\n' msg_[0-9][0-9][0-9]*.c"):format(testdir)
116a8a62f37Srillig  local filenames = assert(io.popen(cmd))
117a8a62f37Srillig  for filename in filenames:lines() do
11868e4158dSrillig    local msgid = filename:match("^msg_(%d%d%d)")
119a8a62f37Srillig    if msgs[msgid] then
120a8a62f37Srillig      local unescaped_msg = msgs[msgid]:gsub("\\(.)", "%1")
121*9e211f35Srillig      local expected_text = ("Test for message: %s [%s]"):format(unescaped_msg, msgid)
122a8a62f37Srillig      local fullname = ("%s/%s"):format(testdir, filename)
123a8a62f37Srillig      if not file_contains(fullname, expected_text) then
124*9e211f35Srillig        print_error("%s must contain: // %s", fullname, expected_text)
125f963c8e4Srillig      end
126f963c8e4Srillig    end
127f963c8e4Srillig  end
128a8a62f37Srillig  filenames:close()
129a8a62f37Srilligend
1302e28c523Srillig
131e442b1ffSrilliglocal function check_yacc_file(filename)
132e442b1ffSrillig  local decl = {}
1334a0dff37Srillig  local decl_list = {}
1344a0dff37Srillig  local decl_list_index = 1
135e442b1ffSrillig  local f = assert(io.open(filename, "r"))
136e442b1ffSrillig  local lineno = 0
137e442b1ffSrillig  for line in f:lines() do
138e442b1ffSrillig    lineno = lineno + 1
139e442b1ffSrillig    local type = line:match("^%%type%s+<[%w_]+>%s+(%S+)$") or
140e442b1ffSrillig      line:match("^/%* No type for ([%w_]+)%. %*/$")
141e442b1ffSrillig    if type then
1424a0dff37Srillig      if decl[type] then
1434a0dff37Srillig        print_error("%s:%d: duplicate type declaration for rule %q",
1444a0dff37Srillig          filename, lineno, type)
1454a0dff37Srillig      end
146e442b1ffSrillig      decl[type] = lineno
1474a0dff37Srillig      table.insert(decl_list, { lineno = lineno, rule = type })
148e442b1ffSrillig    end
149e442b1ffSrillig    local rule = line:match("^([%w_]+):")
150e442b1ffSrillig    if rule then
151e442b1ffSrillig      if decl[rule] then
152e442b1ffSrillig        decl[rule] = nil
153e442b1ffSrillig      else
154e442b1ffSrillig        print_error("%s:%d: missing type declaration for rule %q",
155e442b1ffSrillig          filename, lineno, rule)
156e442b1ffSrillig      end
1574a0dff37Srillig      if decl_list_index > 0 then
1584a0dff37Srillig        local expected = decl_list[decl_list_index]
1594a0dff37Srillig        if expected.rule == rule then
1604a0dff37Srillig          decl_list_index = decl_list_index + 1
1614a0dff37Srillig        else
1624a0dff37Srillig          print_error("%s:%d: expecting rule %q (from line %d), got %q",
1634a0dff37Srillig              filename, lineno, expected.rule, expected.lineno, rule)
1644a0dff37Srillig          decl_list_index = 0
1654a0dff37Srillig        end
1664a0dff37Srillig      end
167e442b1ffSrillig    end
168e442b1ffSrillig  end
169e442b1ffSrillig  for rule, decl_lineno in pairs(decl) do
170e442b1ffSrillig    print_error("%s:%d: missing rule %q", filename, decl_lineno, rule)
171e442b1ffSrillig  end
172e442b1ffSrillig  f:close()
173e442b1ffSrilligend
174e442b1ffSrillig
1752e28c523Srilliglocal function main(arg)
17668e4158dSrillig  local msgs = load_messages()
1772e28c523Srillig  for _, fname in ipairs(arg) do
1784d5c51ccSrillig    check_file(fname, msgs)
179e442b1ffSrillig    if fname:match("%.y$") then
180e442b1ffSrillig      check_yacc_file(fname)
181e442b1ffSrillig    end
1822e28c523Srillig  end
1834d5c51ccSrillig  check_test_files(msgs)
1842e28c523Srilligend
1852e28c523Srillig
1864d5c51ccSrilligmain(arg)
1874d5c51ccSrilligos.exit(not had_errors)
188