Open ruipin opened 3 years ago
Having done some patching in Drag Ruler I'd like to share some insights I gained through that process.
Here are my thoughts about the API:
Here are some pitfalls to be aware of when patching other functions:
toString
for a function is in no way normalized. This includes
LF
as line ending in foundry.js
(Linux, MacOS and some Windows installations do), while some Windows installations use CRLF
. This will apply to the string returned by toString
as well. It's probably a good idea to replace all occurrences of CRLF
with LF
before patching so modules know what they'll have to put up with.Ruler._highlightMeasurement
will look like this natively _highlightMeasurement(ray) {
, but will like this after Drag Ruler patched it function anonymous(ray,startDistance=undefined\n) {
(the \n
is actually a line break)Function.toString()
will be accepted by new Function()
to create a new function. Especially function signatures may need to be transformed (or removed, together with the trailing }
at the end of the function) so new Function()
will consider the coe valid. For example _highlightMeasurement(ray) {
isn't a valid function signature to use in code passed to new Function()
.@manuelVo Thanks for your comments. They are extremely useful.
I think it should be possible for multiple modules to patch the same function [...]
Yeah, the proposal above would allow multiple modules to patch the same function as long as there's no overlap.
A way to insert after or before the regex (in addition to replacing) might be useful A way to add additional parameters to an existing function would be neat [...] As an alternative it would probably neat if a patch-file could be supplied instead of a list of replacements. [...]
Good points. I'm thinking to change the pairs to tuples, where the first entry is the type of operation.
We could have:
replace
/replaceAll
: Calls the corresponding JS String.prototype
functionsprepend
/prependAll
: Inserts text before the first match / all matchesappend
/appendAll
: Inserts text after the first match / all matchesdiff
: Applies a patch, using diff syntax. Probably would use a node module like diffDue to the extra complexities, it's possible 4 would be added later, and not with the first implementation of this API.
The output of toString for a function is in no way normalized [...] Especially function signatures may need to be transformed [...]
I hadn't thought of the line-breaks, you're right this should be normalised. The function signature, I'm aware.
My plan was to extract the parameters and body and then recreate the signature myself. I would also clean up the string (e.g. remove comments, unify line breaks, deindent). The problem here is that this can't be done with regex, as there could be comments before the start of the body that contain {
or (
, or e.g. options = {}
as a parameter. Using something like decomment might be feasible. Alternatively, a full-blown javascript parser might be the only choice here, e.g. esprima. More investigation is necessary.
An API would be provided for people to use to obtain the libWrapper string representation of a method, onto which they'd be applying their patches.
If a function uses symbols that are not available in the global scope the patched function will fail to execute [...]
This is a big problem, I think. You're completely right. I don't think there's any way around this, unfortunately - can't get a function's scope. Fortunately I don't think FVTT relies on scoped variables very often, but it's certainly something to document.
This is especially likely to happen if the function that should be patched has already been overwritten by another module.
Not a big issue with libWrapper, since either we're patching the wrong code (and the patch would fail anyway), or the function is only wrapped by libWrapper in which case we know every single wrapper, and what the original method is.
I know this isn't a super high priority feature given its relatively limited uses, but I want to thank both of you for the documentation of the challenges here as they helped me in my quest to implement such a patching utility myself. I won't say I'm proud of my solution as it's very cobbled together, but it's something that would have been harder still to cobble together without your notes on the subject.
Inspired by some modules like Mess which patch methods using a regex, I have been wondering about standardising an API to do this officially supported by libWrapper, and that attempts to handle possible issues such as conflicts.
This API could be e.g.
and for ease-of-use, also support
Each pair would behave exactly like a call to String.prototype.replace, so would also support replacement methods, etc.
libWrapper would then be responsible for working out which method to patch (including, if necessary, modifying a prior patch), and automatically setting up a corresponding
OVERRIDE
.OVERRIDE
that is higher priority than all of the modules that set up a patch will cause the patch to be discarded, similar to the current behaviour when there are twoOVERRIDE
conflicts.OVERRIDE
) cannot be unregistered.This would mean that two modules patching the same method, as long as their regexes don't overlap, would still be compatible.
Modules would be responsible for catching the exceptions if they require fall-back behaviour. As usual, uncaught exceptions will be raised to the user as errors.
The shim could be given a naive fallback implementation, that just does
toString()
,replace
, and then overwrites the original.