Skip to content

Commit 7ccd068

Browse files
committed
Allow to check for mis-typed attributes and accidental AST changes
Setting the `QUARTO_JOG_CHECK` environment variable will run checks to identify element attributes that have the wrong type, and will also find filters that modify the input object, but don't return it. Both of these can cause issues with jog.
1 parent 37bc223 commit 7ccd068

File tree

4 files changed

+515
-2
lines changed

4 files changed

+515
-2
lines changed

src/resources/filters/ast/runemulation.lua

+101-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,97 @@ local function remove_vault(doc)
4747
end
4848
end
4949

50+
--- Create a deep copy of a table.
51+
local function copy_table (tbl, depth, seen)
52+
local tp = type(tbl)
53+
if tp == 'table' then
54+
local copy = {}
55+
-- Iterate 'raw' pairs, i.e., without using metamethods
56+
for key, value in next, tbl, nil do
57+
if depth == 'shallow' then
58+
copy[key] = value
59+
else
60+
copy[copy_table(key)] = copy_table(value)
61+
end
62+
end
63+
return setmetatable(copy, getmetatable(tbl))
64+
elseif tp == 'userdata' then
65+
return tbl:clone()
66+
else -- number, string, boolean, etc
67+
return tbl
68+
end
69+
end
70+
71+
--- Checks if two tables are equal
72+
function equals(o1, o2)
73+
if o1 == o2 then
74+
return true
75+
end
76+
local o1type = type(o1)
77+
local o2type = type(o2)
78+
if o1type ~= o2type or o1type ~= 'table' then
79+
return false
80+
end
81+
82+
local keys = {}
83+
84+
for key1, value1 in pairs(o1) do
85+
local value2 = o2[key1]
86+
if value2 == nil or equals(value1, value2) == false then
87+
return false
88+
end
89+
keys[key1] = true
90+
end
91+
92+
for key2 in pairs(o2) do
93+
if not keys[key2] then return false end
94+
end
95+
return true
96+
end
97+
98+
--- Checks if a filter follows the "nondestructive" property.
99+
-- The nondestructive property is fulfilled if filter functions returns
100+
-- an explicit object, or if it returns `nil` while leaving the passed
101+
-- in object unmodified.
102+
--
103+
-- An error is raised if the property is violated.
104+
--
105+
-- Only filters with this property can use jog safely, without
106+
-- unintended consequences.
107+
local function check_nondestructive_property (namedfilter)
108+
for name, fn in pairs(namedfilter.filter) do
109+
if type(fn) == 'function' then
110+
local copy = function (x)
111+
local tp = type(x)
112+
return tp ~= 'table' and x:clone() or
113+
(pandoc.utils.type(x) == 'Meta' and pandoc.Meta(x) or copy_table(x))
114+
end
115+
namedfilter.filter[name] = function (obj, context)
116+
local orig = copy(obj)
117+
local result, descend = fn(obj, context)
118+
if result == nil then
119+
if type(obj) ~= 'table' and not equals(obj, orig) then
120+
warn(
121+
"\nFunction '" .. name .. "' in filter '" .. namedfilter.name ..
122+
"' returned `nil`, but modified the input."
123+
)
124+
end
125+
-- elseif result.t == obj.t and not rawequal(result, obj) then
126+
-- warn(
127+
-- "\nFunction '" .. name .. "' in filter '" .. namedfilter.name ..
128+
-- "' returned a new object instead of passing the original one through."
129+
-- )
130+
end
131+
return result, descend
132+
end
133+
end
134+
end
135+
return namedfilter
136+
end
137+
50138
local function run_emulated_filter_chain(doc, filters, afterFilterPass, profiling)
51139
init_trace(doc)
140+
local compare_jog_and_walk = os.getenv 'QUARTO_JOG_CHECK'
52141
for i, v in ipairs(filters) do
53142
local function callback()
54143
if v.flags then
@@ -79,7 +168,17 @@ local function run_emulated_filter_chain(doc, filters, afterFilterPass, profilin
79168
print(pandoc.write(doc, "native"))
80169
else
81170
_quarto.ast._current_doc = doc
82-
doc = run_emulated_filter(doc, v.filter)
171+
172+
if compare_jog_and_walk and not v.force_pandoc_walk then
173+
v = check_nondestructive_property(v)
174+
end
175+
doc = run_emulated_filter(doc, v.filter, v.force_pandoc_walk)
176+
177+
if compare_jog_and_walk and not v.force_pandoc_walk then
178+
-- Types of meta values are only check on assignment.
179+
doc.meta = doc.meta
180+
end
181+
83182
ensure_vault(doc)
84183

85184
add_trace(doc, v.name)
@@ -204,4 +303,4 @@ function run_as_extended_ast(specTable)
204303
end
205304

206305
return pandocFilterList
207-
end
306+
end

src/resources/filters/main.lua

+4
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ import("./quarto-init/metainit.lua")
196196

197197
-- [/import]
198198

199+
if os.getenv 'QUARTO_JOG_CHECK' then
200+
_quarto.modules.attribcheck.enable_attribute_checks()
201+
end
202+
199203
initCrossrefIndex()
200204

201205
initShortcodeHandlers()

0 commit comments

Comments
 (0)