jscc
Version:
Tiny and powerful preprocessor for conditional comments and replacement of compile-time variables in text files
145 lines • 4.76 kB
JavaScript
;
/*
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