-
Notifications
You must be signed in to change notification settings - Fork 13
/
helpers.js
149 lines (124 loc) · 5.55 KB
/
helpers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Checks if the string is a number literal
var NUMBER_LITERAL = /^\d+([.]\d+)?$/;
// Checks if there are unescaped single quotes (the string needs to be reversed first)
var UNESCAPED_QUOTE = /'(?!\\)/;
// Checks if there are unescaped double quotes (the string needs to be reversed first)
var UNESCAPED_DQUOTE = /"(?!\\)/;
// Valid Javascript name: 'name' or 'property.accessor.chain'
var VALID_NAME = /^([$A-Za-z_]+[$A-Za-z_0-9]*)(\.?([$A-Za-z_]+[$A-Za-z_0-9]*))*$/;
// Valid Javascript name: 'name'
var VALID_SHORT_NAME = /^[$A-Za-z_]+[$A-Za-z_0-9]*$/;
// Javascript keywords can't be a name: 'for.is_invalid' as well as 'for' but not 'for_' or '_for'
var KEYWORDS = /^(Array|RegExpt|Object|String|Number|Math|Error|break|continue|do|for|new|case|default|else|function|in|return|typeof|while|delete|if|switch|var|with)(?=(\.|$))/;
// Valid block name
var VALID_BLOCK_NAME = /^[A-Za-z]+[A-Za-z_0-9]*$/;
// Returns TRUE if the passed string is a valid javascript number or string literal
function isLiteral(string) {
var literal = false,
teststr;
// Check if it's a number literal
if (NUMBER_LITERAL.test(string)) {
literal = true;
} else if ((string[0] === string[string.length - 1]) && (string[0] === "'" || string[0] === '"')) {
// Check if it's a valid string literal (throw exception otherwise)
teststr = string.substr(1, string.length - 2).split("").reverse().join("");
if (string[0] === "'" && UNESCAPED_QUOTE.test(teststr) || string[1] === '"' && UNESCAPED_DQUOTE.test(teststr)) {
throw new Error("Invalid string literal. Unescaped quote (" + string[0] + ") found.");
}
literal = true;
}
return literal;
}
// Returns TRUE if the passed string is a valid javascript string literal
function isStringLiteral(string) {
// Check if it's a valid string literal (throw exception otherwise)
if ((string[0] === string[string.length - 1]) && (string[0] === "'" || string[0] === '"')) {
var teststr = string.substr(1, string.length - 2).split("").reverse().join("");
if (string[0] === "'" && UNESCAPED_QUOTE.test(teststr) || string[1] === '"' && UNESCAPED_DQUOTE.test(teststr)) {
throw new Error("Invalid string literal. Unescaped quote (" + string[0] + ") found.");
}
return true;
}
return false;
}
// Variable names starting with __ are reserved.
function isValidName(string) {
return VALID_NAME.test(string) && !KEYWORDS.test(string) && string.substr(0, 2) !== "__";
}
// Variable names starting with __ are reserved.
function isValidShortName(string) {
return VALID_SHORT_NAME.test(string) && !KEYWORDS.test(string) && string.substr(0, 2) !== "__";
}
// Checks if a name is a vlaid block name
function isValidBlockName(string) {
return VALID_BLOCK_NAME.test(string);
}
/**
* Returns a valid javascript code that will
* check if a variable (or property chain) exists
* in the evaled context. For example:
* check("foo.bar.baz")
* will return the following string:
* "typeof foo !== 'undefined' && typeof foo.bar !== 'undefined' && typeof foo.bar.baz !== 'undefined'"
*/
exports.check = function (variable, context) {
/* 'this' inside of the render function is bound to the tag closure which is meaningless, so we can't use it.
* '__this' is bound to the original template whose render function we called.
* Using 'this' in the HTML templates will result in '__this.__currentContext'. This is an additional context
* for binding data to a specific template - e.g. binding widget data.
*/
variable = variable.replace(/^this/, '__this.__currentContext');
if (isLiteral(variable)) {
return "(true)";
}
var props = variable.split("."), chain = "", output = [];
if (typeof context === 'string' && context.length) {
props.unshift(context);
}
props.forEach(function (prop) {
chain += (chain ? (isNaN(prop) ? "." + prop : "[" + prop + "]") : prop);
output.push("typeof " + chain + " !== 'undefined'");
});
return "(" + output.join(" && ") + ")";
};
/**
* Returns an escaped string (safe for evaling). If context is passed
* then returns a concatenation of context and the escaped variable name.
*/
exports.escape = function (variable, context) {
/* 'this' inside of the render function is bound to the tag closure which is meaningless, so we can't use it.
* '__this' is bound to the original template whose render function we called.
* Using 'this' in the HTML templates will result in '__this.__currentContext'. This is an additional context
* for binding data to a specific template - e.g. binding widget data.
*/
variable = variable.replace(/^this/, '__this.__currentContext');
if (isLiteral(variable)) {
variable = "(" + variable + ")";
} else if (typeof context === 'string' && context.length) {
variable = context + '.' + variable;
}
var chain = "", props = variable.split(".");
props.forEach(function (prop) {
chain += (chain ? (isNaN(prop) ? "." + prop : "[" + prop + "]") : prop);
});
return chain.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
};
/**
* Merges b into a and returns a
*/
exports.merge = function (a, b) {
var key;
if (a && b) {
for (key in b) {
if (b.hasOwnProperty(key)) {
a[key] = b[key];
}
}
}
return a;
};
exports.isLiteral = isLiteral;
exports.isValidName = isValidName;
exports.isValidShortName = isValidShortName;
exports.isValidBlockName = isValidBlockName;
exports.isStringLiteral = isStringLiteral;