Projekt

Obecné

Profil

Stáhnout (6.57 KB) Statistiky
| Větev: | Revize:
1
/*
2
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
3
Copyrights licensed under the New BSD License.
4
See the accompanying LICENSE file for terms.
5
*/
6

    
7
'use strict';
8

    
9
// Generate an internal UID to make the regexp pattern harder to guess.
10
var UID                 = Math.floor(Math.random() * 0x10000000000).toString(16);
11
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R|D|M|S|U)-' + UID + '-(\\d+)__@"', 'g');
12

    
13
var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
14
var IS_PURE_FUNCTION = /function.*?\(/;
15
var IS_ARROW_FUNCTION = /.*?=>.*?/;
16
var UNSAFE_CHARS_REGEXP   = /[<>\/\u2028\u2029]/g;
17

    
18
var RESERVED_SYMBOLS = ['*', 'async'];
19

    
20
// Mapping of unsafe HTML and invalid JavaScript line terminator chars to their
21
// Unicode char counterparts which are safe to use in JavaScript strings.
22
var ESCAPED_CHARS = {
23
    '<'     : '\\u003C',
24
    '>'     : '\\u003E',
25
    '/'     : '\\u002F',
26
    '\u2028': '\\u2028',
27
    '\u2029': '\\u2029'
28
};
29

    
30
function escapeUnsafeChars(unsafeChar) {
31
    return ESCAPED_CHARS[unsafeChar];
32
}
33

    
34
function deleteFunctions(obj){
35
    var functionKeys = [];
36
    for (var key in obj) {
37
        if (typeof obj[key] === "function") {
38
            functionKeys.push(key);
39
        }
40
    }
41
    for (var i = 0; i < functionKeys.length; i++) {
42
        delete obj[functionKeys[i]];
43
    }
44
}
45

    
46
module.exports = function serialize(obj, options) {
47
    options || (options = {});
48

    
49
    // Backwards-compatibility for `space` as the second argument.
50
    if (typeof options === 'number' || typeof options === 'string') {
51
        options = {space: options};
52
    }
53

    
54
    var functions = [];
55
    var regexps   = [];
56
    var dates     = [];
57
    var maps      = [];
58
    var sets      = [];
59
    var undefs    = [];
60

    
61
    // Returns placeholders for functions and regexps (identified by index)
62
    // which are later replaced by their string representation.
63
    function replacer(key, value) {
64

    
65
        // For nested function
66
        if(options.ignoreFunction){
67
            deleteFunctions(value);
68
        }
69

    
70
        if (!value && value !== undefined) {
71
            return value;
72
        }
73

    
74
        // If the value is an object w/ a toJSON method, toJSON is called before
75
        // the replacer runs, so we use this[key] to get the non-toJSONed value.
76
        var origValue = this[key];
77
        var type = typeof origValue;
78

    
79
        if (type === 'object') {
80
            if(origValue instanceof RegExp) {
81
                return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';
82
            }
83

    
84
            if(origValue instanceof Date) {
85
                return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
86
            }
87

    
88
            if(origValue instanceof Map) {
89
                return '@__M-' + UID + '-' + (maps.push(origValue) - 1) + '__@';
90
            }
91

    
92
            if(origValue instanceof Set) {
93
                return '@__S-' + UID + '-' + (sets.push(origValue) - 1) + '__@';
94
            }
95
        }
96

    
97
        if (type === 'function') {
98
            return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@';
99
        }
100

    
101
        if (type === 'undefined') {
102
            return '@__U-' + UID + '-' + (undefs.push(origValue) - 1) + '__@';
103
        }
104

    
105
        return value;
106
    }
107

    
108
    function serializeFunc(fn) {
109
      var serializedFn = fn.toString();
110
      if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {
111
          throw new TypeError('Serializing native function: ' + fn.name);
112
      }
113

    
114
      // pure functions, example: {key: function() {}}
115
      if(IS_PURE_FUNCTION.test(serializedFn)) {
116
          return serializedFn;
117
      }
118

    
119
      // arrow functions, example: arg1 => arg1+5
120
      if(IS_ARROW_FUNCTION.test(serializedFn)) {
121
          return serializedFn;
122
      }
123

    
124
      var argsStartsAt = serializedFn.indexOf('(');
125
      var def = serializedFn.substr(0, argsStartsAt)
126
        .trim()
127
        .split(' ')
128
        .filter(function(val) { return val.length > 0 });
129

    
130
      var nonReservedSymbols = def.filter(function(val) {
131
        return RESERVED_SYMBOLS.indexOf(val) === -1
132
      });
133

    
134
      // enhanced literal objects, example: {key() {}}
135
      if(nonReservedSymbols.length > 0) {
136
          return (def.indexOf('async') > -1 ? 'async ' : '') + 'function'
137
            + (def.join('').indexOf('*') > -1 ? '*' : '')
138
            + serializedFn.substr(argsStartsAt);
139
      }
140

    
141
      // arrow functions
142
      return serializedFn;
143
    }
144

    
145
    // Check if the parameter is function
146
    if (options.ignoreFunction && typeof obj === "function") {
147
        obj = undefined;
148
    }
149
    // Protects against `JSON.stringify()` returning `undefined`, by serializing
150
    // to the literal string: "undefined".
151
    if (obj === undefined) {
152
        return String(obj);
153
    }
154

    
155
    var str;
156

    
157
    // Creates a JSON string representation of the value.
158
    // NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args.
159
    if (options.isJSON && !options.space) {
160
        str = JSON.stringify(obj);
161
    } else {
162
        str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space);
163
    }
164

    
165
    // Protects against `JSON.stringify()` returning `undefined`, by serializing
166
    // to the literal string: "undefined".
167
    if (typeof str !== 'string') {
168
        return String(str);
169
    }
170

    
171
    // Replace unsafe HTML and invalid JavaScript line terminator chars with
172
    // their safe Unicode char counterpart. This _must_ happen before the
173
    // regexps and functions are serialized and added back to the string.
174
    if (options.unsafe !== true) {
175
        str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);
176
    }
177

    
178
    if (functions.length === 0 && regexps.length === 0 && dates.length === 0 && maps.length === 0 && sets.length === 0 && undefs.length === 0) {
179
        return str;
180
    }
181

    
182
    // Replaces all occurrences of function, regexp, date, map and set placeholders in the
183
    // JSON string with their string representations. If the original value can
184
    // not be found, then `undefined` is used.
185
    return str.replace(PLACE_HOLDER_REGEXP, function (match, type, valueIndex) {
186
        if (type === 'D') {
187
            return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
188
        }
189

    
190
        if (type === 'R') {
191
            return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";
192
        }
193

    
194
        if (type === 'M') {
195
            return "new Map(" + serialize(Array.from(maps[valueIndex].entries()), options) + ")";
196
        }
197

    
198
        if (type === 'S') {
199
            return "new Set(" + serialize(Array.from(sets[valueIndex].values()), options) + ")";
200
        }
201

    
202
        if (type === 'U') {
203
            return 'undefined'
204
        }
205

    
206
        var fn = functions[valueIndex];
207

    
208
        return serializeFunc(fn);
209
    });
210
}
(3-3/4)