weex-templater
Version:
Weex <template> transformer
146 lines (135 loc) • 3.84 kB
JavaScript
var allowedKeywords =
'Math,Date,this,true,false,null,undefined,Infinity,NaN,' +
'isNaN,isFinite,decodeURI,decodeURIComponent,encodeURI,' +
'encodeURIComponent,parseInt,parseFloat'
var allowedKeywordsRE =
new RegExp('^(' + allowedKeywords.replace(/,/g, '\\b|') + '\\b)')
// keywords that don't make sense inside expressions
var improperKeywords =
'break,case,class,catch,const,continue,debugger,default,' +
'delete,do,else,export,extends,finally,for,function,if,' +
'import,in,instanceof,let,return,super,switch,throw,try,' +
'var,while,with,yield,enum,await,implements,package,' +
'proctected,static,interface,private,public'
var improperKeywordsRE =
new RegExp('^(' + improperKeywords.replace(/,/g, '\\b|') + '\\b)')
var wsRE = /\s/g
var newlineRE = /\n/g
var saveRE = /[\{,]\s*[\w\$_]+\s*:|('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*")|new |typeof |void /g
var restoreRE = /"(\d+)"/g
var pathTestRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/
var identRE = /[^\w$\.](?:[A-Za-z_$][\w$]*)/g
var booleanLiteralRE = /^(?:true|false)$/
/**
* Save / Rewrite / Restore
*
* When rewriting paths found in an expression, it is
* possible for the same letter sequences to be found in
* strings and Object literal property keys. Therefore we
* remove and store these parts in a temporary array, and
* restore them after the path rewrite.
*/
var saved = []
/**
* Save replacer
*
* The save regex can match two possible cases:
* 1. An opening object literal
* 2. A string
* If matched as a plain string, we need to escape its
* newlines, since the string needs to be preserved when
* generating the function body.
*
* @param {String} str
* @param {String} isString - str if matched as a string
* @return {String} - placeholder with index
*/
function save (str, isString) {
var i = saved.length
saved[i] = isString
? str.replace(newlineRE, '\\n')
: str
return '"' + i + '"'
}
/**
* Path rewrite replacer
*
* @param {String} raw
* @return {String}
*/
function rewrite (raw) {
var c = raw.charAt(0)
var path = raw.slice(1)
if (allowedKeywordsRE.test(path)) {
return raw
} else {
path = path.indexOf('"') > -1
? path.replace(restoreRE, restore)
: path
return c + 'this.' + path
}
}
/**
* Restore replacer
*
* @param {String} str
* @param {String} i - matched save index
* @return {String}
*/
function restore (str, i) {
return saved[i]
}
/**
* Rewrite an expression, prefixing all path accessors with
* `this.` and generate getter/setter functions.
*
* @param {String} exp
* @return {Function}
*/
function compileGetter (exp) {
/* istanbul ignore if */
if (improperKeywordsRE.test(exp)) {
console.warn('Avoid using reserved keywords in expression: ' + exp)
}
// reset state
saved.length = 0
// save strings and object literal keys
var body = exp
.replace(saveRE, save)
.replace(wsRE, '')
// rewrite all paths
// pad 1 space here becaue the regex matches 1 extra char
body = (' ' + body)
.replace(identRE, rewrite)
.replace(restoreRE, restore)
return body.trim()
}
/**
* Parse an expression into re-written getter.
*
* @param {String} exp
* @return {String}
*/
function parseExpression (exp) {
exp = exp.trim()
var res = isSimplePath(exp) && exp.indexOf('[') < 0
// optimized super simple getter
? 'this.' + exp
// dynamic getter
: compileGetter(exp)
return res
}
/**
* Check if an expression is a simple path.
*
* @param {String} exp
* @return {Boolean}
*/
function isSimplePath (exp) {
return pathTestRE.test(exp) &&
// don't treat true/false as paths
!booleanLiteralRE.test(exp) &&
// Math constants e.g. Math.PI, Math.E etc.
exp.slice(0, 5) !== 'Math.'
}
exports.parseExpression = parseExpression