Faults and refusals
staticjs gives JavaScript's value or stops. It stops in two places:
- When compiled, a program is refused with an
sjs_errorwhose code isSJS_ERROR_SYNTAX(JavaScript refuses it too),SJS_ERROR_UNSUPPORTED(valid JavaScript that staticjs leaves out),SJS_ERROR_NAME(a name bound nowhere, bound twice, not exported, or a module the host does not have) orSJS_ERROR_LIMIT(ansjs_limitsvalue reached). - When it runs, an evaluation ends with
SJS_RUN_FAULTand ansjs_errorwhose code isSJS_ERROR_FAULT, where JavaScript would throw or where it would coerce, compare identities or reach a prototype;SJS_ERROR_LIMITorSJS_ERROR_MEMORYwhen a limit or the allocator stops it.
Each error has a position (at: byte offset, line and column) and a fixed English message; subject names the token or name concerned, when there is one. Embedding shows how to read them.
A fault is never a different value. Most programs that work in JavaScript and avoid the cases below give the same value here: these lists are what to look for when one does not.
Try them: each sample below stops where JavaScript would go on.
[1, 2] === [1, 2]"5" * 2[{ total: 1 }, null].map((order) => order.total)JSON.parse('{"a": 1}', (key, value) => value)Where it faults instead of following JavaScript
These follow from the rule: staticjs stops where JavaScript would coerce, compare identities, or find something on a prototype.
===on two objects (identity);+of a string and an object; arithmetic or<on values that are not both numbers (or, for<, both strings), or BigInts (and, for<, a BigInt and a number).- Converting an object to a number or a string where JavaScript would call its methods:
Number([]),parseInt({}),parseFloat([1]). A string converted to a number on the way to something else:isNaN("x"),isFinite("1"),Math.abs("1"),parseInt("10", "16")'s radix;Number(s),parseInt(s)andparseFloat(s)are the explicit conversions, and read any string as JavaScript does (parseIntin a radix that is not 10 or a power of two as V8 does, as the specification lets it approximate). - An object rest element of a string (
const {...r} = "ab"), as with spreading a string into an object: JavaScript copies UTF-16 code units, which can split a character. An object pattern's key that indexes half of a character outside the BMP (const {0: c} = "😀"), as indexing does. - An object pattern over a number or a boolean (
[1].map(({a = 2}) => a)), whose properties JavaScript finds on its prototype; an object rest element of one gives{}, as in JavaScript. - A computed key, in a literal or a pattern, that is an object or an array: JavaScript turns it into a string. A map's key that is not a primitive (
new Map([[{}, 1]])), which JavaScript keys by its identity; an entry ofnew Mapthat is not an array (new Map([{0: "k", 1: "v"}])), which JavaScript reads as an object. JSON.parsewith a reviver, of a lone surrogate ("\uD800") or of the key__proto__, and of a BigInt or an object;JSON.stringifywith a replacer function or of an object with atoJSONmethod, which JavaScript calls; with an array replacer naming a key JavaScript reads from a prototype; and with aspacebetween 0 and 1, or a string cut inside a pair (JSON).String.rawwith raw strings that are an object or a function, which JavaScript reads by theirlengthand indexes (an array or a string is read; a number, a boolean or a BigInt is none, as in JavaScript), and where JavaScript throws: a template withoutraw.decodeURIanddecodeURIComponentof a malformed escape or of escapes that are not UTF-8 (overlong, a surrogate, past U+10FFFF), where JavaScript throws a URIError. The five read text (a raw string, a substitution, what is encoded or decoded) asStringgives it, so an object faults there, and a BigInt is its digits.- An iterator read where something else holds it too (
[[...it], [...it]], a closure's capture), where JavaScript gives nothing the second time; an iterator'snextorreturnwhere something else holds it too, or through a path (o.it.next()); a callback of an iterator'smap,filterorflatMapthat faults on a value a latertake,some,everyorfindwould not read, as staticjs runs them to the end;takeanddropof a string or an object, which JavaScript converts to a number;Map.groupByof anything but an array, asObject.groupBy. - A property JavaScript finds on
Object.prototype(toString,__proto__); reading a method without calling it, by name, through a pattern or within: any method of an array, a string, a set or a map, those staticjs leaves out included (const {at = 1} = "x"). A namespace (import * as ns) is an object too:ns.toStringand"valueOf" in nsfault where JavaScript finds nothing, and so does turning a namespace into a string. - A method that changes its receiver on a value something else holds (a caller's argument, a global, a module's export, an element of an array, an argument that is the receiver), or on a receiver that is not a variable (
[1].push(2),xs?.push(1)). - An assignment through a container something else holds, or one that would make a cycle (
o.self = o); to an array's element past its end (JavaScript leaves holes), to itslengthor to a key that is not an index; to a property of a string, a set, a number or a function (JavaScript ignores it, adds it or throws); to__proto__or a nameObject.prototypehas, as reading it faults;++of anything but a number, which JavaScript converts. sort()andtoSorted()without a compare function (or withundefined) of two elements or more that are notundefined, one of them an object, an array, a function, a regular expression, a set, a map, an iterator or a Temporal value, whosetoStringJavaScript calls to compare it; of primitives they sort as JavaScript does, by the elements' strings in UTF-16 code units,undefinedlast.toLowerCase()of a string that is not ASCII; cutting a string through the middle of a surrogate pair (slice,substring,substr), padding one (padStart,padEnd) with a pad string cut there,atorcharAtof half of one (charCodeAtandcodePointAtgive its number),replaceAllwith an empty pattern where it puts text between the halves of one,String.fromCharCodeorString.fromCodePointof a half that no other half follows or precedes, andString.rawof a string with a substitution between the halves of one: staticjs strings cannot hold a lone surrogate. SoencodeURIandencodeURIComponentnever meet one, where JavaScript throws a URIError. An object as a string method's argument that JavaScript turns into a string (a pad string, an argument ofconcat, the replacement ofreplaceAll), as+faults on one, and a BigInt there too: staticjs makes a BigInt's digits only forString,toString,+and a template; a string asBigInt.asIntN's orasUintN's value, or a string as any built-in's number (toString("16")), which JavaScript converts; a search argument that is not a string (includes(1),replaceAll(1, "x"),split(1)); a function as the replacement ofreplaceorreplaceAllwith a string pattern.- Of regular expressions (The language):
execortestwithgory, andmatchorreplacewithyand withoutg, which changelastIndexin JavaScript, and staticjs values do not change;matchAllorreplaceAllwithoutg, whatever the other flags, where JavaScript throws a TypeError;match,matchAllorsearchwith anything but a regular expression ("a.b".match(".")), of which JavaScript makes one; a match, a group or a piece ofsplitthat begins or ends between the halves of a surrogate pair, which JavaScript makes into a lone surrogate; a replacement that is an object or a BigInt, or a replacement function that returns one (an array included); a non-string argument (re.test(1)) or asplitlimit that is a string, an object or a BigInt (s.split(re, "2")) that JavaScript converts;Object.keys,Object.values,Object.entries, an object spread or rest, orsortof a match result, as JavaScript would show or keepindex,inputandgroupsamong its keys. A regular expression is an object:===on two,String(re),re.toString(),"" + reand`${re}`(which JavaScript writes as/source/flags),Object.keys(re),inon one,JSON.stringifywith a list of keys that names a property ofRegExp.prototype(source,flags, ...) orlastIndex, an assignment to its properties, and one as a map key, a set member or a computed key fault; so does reading one of its methods (re.exec) or another name ofRegExp.prototype(re.compile) without calling it. A pattern that would need more states thanmax_regex_statesis a limit error when compiled. - Of Temporal values (The language):
===on two, a set member or a map key (identity:equalsandcompareare the way);String(d), a template,Object.keysandin, which JavaScript turns into its string or its prototype's names; reading a method without calling it; a field, amonthCodeor an option given as something JavaScript converts (a string, an array, an object); a calendar other thaniso8601, in an argument, a bag or a string's annotation; a string the polyfill reads past the specification's grammar (+24:00,[foo=-bar]), or that the two polyfills read differently;toLocaleString(locale data) andvalueOf, which throws in JavaScript; a Duration'srelativeTowith a time zone (a ZonedDateTime, which staticjs does not have); an Instant's epoch nanoseconds or milliseconds given as a string (JavaScript converts them),toString'stimeZoneother thanUTCor a fixed offset (time zone data),toZonedDateTimeISO, and the roundings of an Instant before 1970 that temporal-polyfill and the specification give differently (design notes: Instant);Temporal.Nowwhen the host never set the clock (JavaScript reads the machine's), with a time zone's name, or itszonedDateTimeISO. - Of Dates (The language): what reads or writes local time (
new Date(y, m)with two or more arguments,Date()called, a date and time string withoutZor an offset,getFullYearand the other local getters,getTimezoneOffset,toString,toDateString,toTimeString) or the host's locale (toLocaleStringand its kin); a string outside the date time string format, which Node reads by heuristics of its own ("Jan 31 2024","2024/01/31") or gives NaN for;Date.now()andnew Date()when the host never set the clock;===on two, a set member or a map key;-,<, unary+,+with a string, a template,String(d)andNumber(d)(JavaScript callsvalueOfortoString); a string or an object whereDate.UTCor a setter converts one, an object wherenew Datedoes; the local setters (setFullYearand its kin);setTimeand the UTC setters but on a variable of the function that holds the Date alone (owned mutation: a Date copied, stored, passed or captured before, on a path, an element or a temporary). Math.random()when the host never set the seed (sjs_vm_set_seed; JavaScript draws from the machine's entropy).
Refused when compiled
A loop is refused when compiled, even one that would end:
((xs) => {
let sum = 0;
for (const x of xs) sum += x;
return sum;
})([1, 2, 3]) for (const x of xs) sum += x;
^The same sum, with a built-in that walks the array:
((xs) => xs.reduce((sum, x) => sum + x, 0))([1, 2, 3])6JavaScript staticjs leaves out is refused as unsupported, never given a different meaning. Among the module forms: export let, export function, export class and export default function; a string as an exported or imported name (export { a as "x-y" }), as an object cannot keep the order a namespace gives it; import(), import.meta, top-level await and import attributes (with { type: "json" }); two export * that give one name, unless an explicit export settles it, as staticjs cannot tell whether they are one binding, so even two that lead to the same binding are refused; and a name used before its import or declaration, even in an earlier function. A default value that reads a later parameter, its own parameter, or a name its function's body declares is refused too: JavaScript would throw, or would find another binding, as parameters have a scope of their own. An assignment, ++ and -- are refused as unsupported where JavaScript can assign but staticjs does not (anywhere but as a statement, [a] = xs, a target whose root is not a variable the function owns, and the bitwise forms: see Changing a value), and as a syntax error where JavaScript cannot (1 = 2, a + b = 1, 1++). delete is refused as unsupported anywhere but as a statement, and of anything but a property on such a path; of a name it is a syntax error in a module, as in strict JavaScript. In a script, which is sloppy, eval, arguments and the words only strict code reserves (yield, let, ...) are names, which staticjs does not bind; in a module they are syntax errors.
Among the statements: loops, switch, throw, try, var, a let captured by an arrow function or at a module's top level, assigning to a const or a parameter, labels, function and class declarations, and expression statements, which can have no effect, are unsupported, but for a call that changes a variable the function owns and an assignment (and the mutation of a variable the function does not own, or that was captured, copied, passed or stored before it, is unsupported too, but for add on a variable of the function, which is checked when it runs: see Changing a value); break and continue are syntax errors, as staticjs has no loop for them to leave, but for break name inside the label name, which is unsupported with it. A label inside one of the same name is a syntax error, as in JavaScript. A declaration as the statement of an if, an else or a label is a syntax error, as in JavaScript (if (x) const y = 1). An if or a block at a module's top level is unsupported, as a module holds imports, const and exports; the parser reads on through it, so a return in it is the syntax error JavaScript reports. A name read in a block, or in a function in it, and declared later in that block or in one around it, is refused as a use before its declaration, as at a function's top level. A name bound nowhere is refused even in a branch that never runs or after a return, where JavaScript would throw only if it ran.
After JavaScript staticjs leaves out, the parser reads on where it can, sending nothing more: past an assignment, the comma operator, an operator staticjs does not have, this, new.target, a method in an object literal, an expression statement that is not a call, a label, and an if or a block at a module's top level. An expression statement that is a call is sent, and the compiler, refusing it unless it changes a variable, reads on the same way. So a program that is not JavaScript further on, such as ([a.b = 1]) => 0, a = 1 = 2 or x => { a b; }, is a syntax error, not unsupported. It stops at the first refusal where it cannot read on: a loop, switch, try or another statement staticjs does not have, a / in a method's body.
A regular expression literal is read as JavaScript reads it, and its pattern is parsed when compiled: what Node refuses is a syntax error, and what Node accepts but staticjs does not match is unsupported, and the parser reads on. Unsupported are lookahead and lookbehind, backreferences, \p{...} (of a name Node knows; another is a syntax error, as in Node), the v flag, a non-ASCII letter with i, a group name that is not ASCII letters, digits, $ and _ (and then \k<...>), the forms only Annex B allows (a lone ] or {, an identity escape such as \-, \1 with no group 1 as an octal escape, ...), and RegExp and new RegExp.
Errors come in the order JavaScript finds them only so far as a stage that streams can know it. The compiler stops at the first error it meets; what it can only decide at the end of a module (an export { a } the module never declares, a name bound nowhere, an ambiguous export *) comes after everything it met before, in the order syntax, then name, then unsupported. So a module that uses what staticjs leaves out and later exports a name it never declares is refused as unsupported, where JavaScript reports the export.