And a third example of a much less obvious but ultimately crippling defect that is essentially unfixable within the current JXA design:
I do professional Illustrator automation myself, sometimes using AppleScript for quick scripts but mostly using Python+appscript as it’s far more capable for heavy-duty work. Needless to say, when everyone’s livelihoods are relying on my automation working correctly, I have very little tolerance for failures in either my own code or anyone else’s. So you can imagine my reaction to JXA when converting the following simple save
command, which works perfectly in AS:
set theFile to "/Users/has/AS-save-test.pdf"
tell application "Adobe Illustrator"
save document 1 in theFile as pdf ¬
with options {class:PDF save options, trim marks:true}
end tell
and Python appscript:
theFile = "/Users/has/PY-save-test.pdf"
app('Adobe Illustrator').documents[1].save(in=theFile, as=k.pdf,
with_options={k.class: k.PDF_save_options, k.trim_marks: True})
to JXA:
theFile = "/Users/has/JS-save-test.pdf";
AI = Application('Adobe Illustrator');
AI.documents[0].save({in:theFile, as:"pdf",
withOptions:{class:"PDFSaveOptions", trimMarks:true}});
only to get Error: Expected fileSpecification but found string.
This problem occurs because the type information provided by dictionaries is never used by AppleScript: it’s purely there for documentation purposes, so is not guaranteed to be be sufficient or correct, or even meaningful to anything but a human reader. Unfortunately, JXA uses this information to do its own fancy casting of JS values to AE types before sending the command, so whenever that dictionary-supplied information is incomplete or incorrect, or simply not one that JXA knows about, it falls over.
In this particular case, AI describes the in
parameter’s type as file specification
(aka typeFSS), which if you remember pre-OS X was the value type used to specify files that may or may not currently exist, but was deprecated around 10.5 as the Carbon FSSpec datatype didn’t support Unicode. Obviously, Adobe’s hacked in some updates since then, so nowadays it accepts POSIX or HFS path strings, although annoyingly it still doesn’t take POSIX file
(aka typeFileURL) values, which is what it really ought to do.
Still, JXA does provide a strictParameterType
option for disabling this fancy casting behavior in situations where it causes such problems:
theFile = "/Users/has/save-js.pdf";
AI = Application('Adobe Illustrator');
AI.strictParameterType = false;
AI.documents[0].save({in:theFile, as:"pdf",
withOptions:{class:"PDFSaveOptions", trimMarks:true}});
Now the file saves… yay!!!
But wait… A new problem appears, and this is the real deal breaker: the saved file doesn’t contain trim marks or any of the other settings specified by the withOptions
parameter. This is because Illustrator silently ignores the entire ‘save options’ record if isn’t in the correct format. (It really ought to report a bad parameter error, but as experienced AppleScripters you know what these old Carbon warhorses are like, and we forgive them their little quirks because without them AppleScript would be worthless.)
In this case, the record’s class
property needs to contain a type name, but contains a string instead. Of course, JXA’s fancy auto-casting system was intended to convert strings to the appropriate typeType/typeEnumerator type, and in trivial use-cases - e.g. doc.close({saving:“no”}) - this mechanism works as intended. However, here the relevant string lies deep within a record, with no obvious way for JXA to determine what its type should be. Even if the strictParameterType
option could be left on, the record’s values still wouldn’t be cast correctly since the AI dictionary only describes thewithOptions
parameter’s type as any
. And it’s tricky for the dictionary to be more specific than that, since each saved/exported document format - ai, eps, pdf, etc. - uses a different type of record.
Even if JXA could be “educated” on how to pack such complex parameters correctly, there’s zero guarantee that another application won’t do something very similar but in a slightly different way that causes JXA to puke all over again. So not only does JXA not work properly with Illustrator, the way it’s designed to work makes it incapable of ever working right, because it’ll always choke on some app somewhere.
The correct solution? Do what AppleScript does: allow the user to choose what types of objects they wish to supply, and leave the application to use/coerce/refuse those values as appropriate. They have a far better understand of what’s appropriate than the AE bridge ever will.
Of course, not every language has built-in classes suitable for representing all of the available AE types, so it may be necessary for the bridge to implement one or more custom classes. For example, Python and JavaScript don’t currently include a Symbol class suitable for representing AE type and enumerator names, so it’s necessary for the bridge to provide a suitable substitute. The JavaScriptOSA component I sent to the AS team does this: it implements a Keyword
‘class’ for representing type/enumerator/property/etc. names, plus a convenient mechanism (the k
object) for creating new Keyword objects within your code. Thus, the ‘with options’ record in the original AppleScript command:
{class: PDF save options, trim marks: true}
translates unambiguously to this in JavaScriptOSA:
{class: k.PDFSaveOptions, trimMarks: true}
When that object is passed to the AE bridge, it doesn’t require any dictionary-driven guesswork to figure out what type each value should be as those values already contain all the type information required. Simple, reliable, proven.
Alas, the road to hell is paved with ingeniously clever “solutions” to hellishly convoluted problems that would never would’ve existed in the first place, had corporate hellspawn programmers not invented those problems themselves just to have something exciting to do.