UNPKG

jscc

Version:

Tiny and powerful preprocessor for conditional comments and replacement of compile-time variables in text files

145 lines 4.76 kB
"use strict"; /* Variable replacement inside the code. */ const R = require("./regexes"); /** * Helper for call `JSON.stringify` in `stringifyObject`. * * It outputs a valid number for non-finite numbers and the source string of * regexes because stringify converts `Infinity` and `-Infinity` to `null` * and regexes to `{}` (an empty object). */ const stringifyFn = (_, value) => { if (value && value.constructor.name === 'Number') { /* It is necessary to coerce the value to a primitive number because `Infinity !== new Number(Infinity)`, and the like for `-Infinity`. */ if (+value === Infinity) { return Number.MAX_VALUE; } if (+value === -Infinity) { return Number.MIN_VALUE; } } return value instanceof RegExp ? value.source : value; }; /** * Stringify a non-null object `obj` using the following rules: * * - NaN -> 'NaN' (a Number or Date with a NaN value) * - RegExp -> Regex source * - Date -> Date string in JSON format * - other -> JSON.stringify(obj) * * @param obj Trueish object * @returns String representation of the object */ const stringObject = (obj) => { let str; // toISOString throw with NaN dates, toJSON returns `null` if (obj instanceof Date) { str = isNaN(+obj) ? 'NaN' : obj.toJSON(); } else if (obj instanceof RegExp) { str = obj.source; } else if (obj instanceof String) { str = obj.valueOf(); } else if (obj instanceof Number) { str = String(obj); } else { str = JSON.stringify(obj, stringifyFn); } return str; }; /** * Stringify the given value using this rules: * * - undefined -> 'undefined' * - null -> 'null' * - NaN -> 'NaN' * - Infinity -> 'Infinity' * - RegExp -> JSON.stringify(value.source) * - objects -> JSON.stringify(value) (Date as ISO string or `null`) * - primitives -> String(value) * * @param value any value, including undefined */ const stringValue = (value, escapeQuotes) => { // Trap falsy values, including `NaN` and empty strings. if (!value) { return String(value); } // stringObject accepts `NaN` objects. if (typeof value === 'object') { return stringObject(value); } // Other non-falsy primitive values. let str = String(value); if (escapeQuotes & 1 /* Single */) { str = str.replace(/(?=')/g, '\\'); } if (escapeQuotes & 2 /* Double */) { str = str.replace(/(?=")/g, '\\'); } return str; }; /** * Returns the value pointed by match[2] and adjust match[1] to cover * the length of the replacement. * * @param value Source value or object * @param match Contain the property path in $2 * @throws TypeError if you try to read the prop on a null object. */ const getValueInfo = (value, match) => { // Get the property path without the first dot const propPath = match[2] && match[2].slice(1); let length = match[1].length; // Try to get the property value only if this is an object if (propPath) { const props = propPath.split('.'); let prop = props.shift(); while (prop && typeof value === 'object') { // the next assignment will raise a TypeError if the current value // is null, undefined, or a primitive value... it is ok. value = value[prop]; // include this prop in the replaced length and pick the next length += prop.length + 1; prop = props.shift(); } } return { value, length }; }; /** * Replaces memvars in a source fragment with its current values. * * @param props Jscc properties with the MagicString instance and the values * @param fragment Fragment of code to replace and add to `magicStr` * @param start Offset where the replacement starts in magicStr. */ const remapVars = function _remapVars(props, fragment, start) { // node.js is async, make local copy of the regex const re = new RegExp(R.VARS_TO_REPL); let changes = false; let match; // $1: varname including the prefix '$' // $2: optional property name(s) // tslint:disable-next-line:no-conditional-assignment while (match = re.exec(fragment)) { const vname = match[1].slice(1); // strip the prefix '$' if (vname in props.values) { const index = start + match.index; const vinfo = getValueInfo(props.values[vname], match); props.magicStr.overwrite(index, index + vinfo.length, stringValue(vinfo.value, props.escapeQuotes)); changes = true; } } return changes; }; module.exports = remapVars; //# sourceMappingURL=remap-vars.js.map