UNPKG

9.03 kBJavaScriptView Raw
1;(function() {
2'use strict'
3/* global define */
4
5var esprima
6var exportFn
7var toString = Object.prototype.toString
8
9if (typeof module === 'object' && typeof module.exports === 'object' && typeof require === 'function') {
10 // server side
11 esprima = require('esprima')
12 exportFn = function(redeyed) { module.exports = redeyed }
13 bootstrap(esprima, exportFn)
14} else if (typeof define === 'function' && define.amd) {
15 // client side
16 // amd
17 define(['esprima'], function(esprima) {
18 return bootstrap(esprima)
19 })
20} else if (typeof window === 'object') {
21 // no amd -> attach to window if it exists
22 // Note that this requires 'esprima' to be defined on the window, so that script has to be loaded first
23 window.redeyed = bootstrap(window.esprima)
24}
25
26function bootstrap(esprima, exportFn) {
27 function isFunction(obj) {
28 return toString.call(obj) === '[object Function]'
29 }
30
31 function isString(obj) {
32 return toString.call(obj) === '[object String]'
33 }
34
35 function isObject(obj) {
36 return toString.call(obj) === '[object Object]'
37 }
38
39 function surroundWith(before, after) {
40 return function(s) { return before + s + after }
41 }
42
43 function isNonCircular(key) {
44 return key !== '_parent'
45 }
46
47 function objectizeString(value) {
48 var vals = value.split(':')
49
50 if (vals.length === 0 || vals.length > 2) {
51 throw new Error(
52 'illegal string config: ' + value +
53 '\nShould be of format "before:after"'
54 )
55}
56
57 if (vals.length === 1 || vals[1].length === 0) {
58 return vals.indexOf(':') < 0 ? { _before: vals[0] } : { _after: vals[0] }
59 } else {
60 return { _before: vals[0], _after: vals[1] }
61 }
62 }
63
64 function objectize(node) {
65 // Converts 'bef:aft' to { _before: bef, _after: aft }
66 // and resolves undefined before/after from parent or root
67
68 function resolve(value, key) {
69 // resolve before/after from root or parent if it isn't present on the current node
70 if (!value._parent) return undefined
71
72 // Immediate parent
73 if (value._parent._default && value._parent._default[key]) return value._parent._default[key]
74
75 // Root
76 var root = value._parent._parent
77 if (!root) return undefined
78
79 return root._default ? root._default[key] : undefined
80 }
81
82 function process(key) {
83 var value = node[key]
84
85 if (!value) return
86 if (isFunction(value)) return
87
88 // normalize all strings to objects
89 if (isString(value)) {
90 node[key] = value = objectizeString(value)
91 }
92
93 value._parent = node
94 if (isObject(value)) {
95 if (!value._before && !value._after) return objectize(value)
96
97 // resolve missing _before or _after from parent(s)
98 // in case we only have either one on this node
99 value._before = value._before || resolve(value, '_before')
100 value._after = value._after || resolve(value, '_after')
101
102 return
103 }
104
105 throw new Error('nodes need to be either {String}, {Object} or {Function}.' + value + ' is neither.')
106 }
107
108 // Process _default ones first so children can resolve missing before/after from them
109 if (node._default) process('_default')
110
111 Object.keys(node)
112 .filter(function(key) {
113 return isNonCircular(key)
114 && node.hasOwnProperty(key)
115 && key !== '_before'
116 && key !== '_after'
117 && key !== '_default'
118 })
119 .forEach(process)
120 }
121
122 function functionize(node) {
123 Object.keys(node)
124 .filter(function(key) {
125 return isNonCircular(key) && node.hasOwnProperty(key)
126 })
127 .forEach(function(key) {
128 var value = node[key]
129
130 if (isFunction(value)) return
131
132 if (isObject(value)) {
133 if (!value._before && !value._after) return functionize(value)
134
135 // at this point before/after were "inherited" from the parent or root
136 // (see objectize)
137 var before = value._before || ''
138 var after = value._after || ''
139
140 node[key] = surroundWith(before, after)
141 return node[key]
142 }
143 })
144 }
145
146 function normalize(root) {
147 objectize(root)
148 functionize(root)
149 }
150
151 function mergeTokensAndComments(tokens, comments) {
152 var all = {}
153
154 function addToAllByRangeStart(t) { all[ t.range[0] ] = t }
155
156 tokens.forEach(addToAllByRangeStart)
157 comments.forEach(addToAllByRangeStart)
158
159 // keys are sorted automatically
160 return Object.keys(all)
161 .map(function(k) { return all[k] })
162 }
163
164 function redeyed(code, config, opts) {
165 opts = opts || {}
166 var parser = opts.parser || esprima
167 var jsx = !!opts.jsx
168 // tokenizer doesn't support JSX at this point (esprima@4.0.0)
169 // therefore we need to generate the AST via the parser not only to
170 // avoid the tokenizer from erroring but also to get JSXIdentifier tokens
171 var buildAst = jsx || !!opts.buildAst
172
173 var hashbang = ''
174 var ast
175 var tokens
176 var comments
177 var lastSplitEnd = 0
178 var splits = []
179 var transformedCode
180 var all
181 var info
182
183 // Replace hashbang line with empty whitespaces to preserve token locations
184 if (code[0] === '#' && code[1] === '!') {
185 hashbang = code.substr(0, code.indexOf('\n') + 1)
186 code = Array.apply(0, Array(hashbang.length)).join(' ') + '\n' + code.substr(hashbang.length)
187 }
188
189 if (buildAst) {
190 ast = parser.parse(code, { tokens: true, comment: true, range: true, loc: true, tolerant: true, jsx: true })
191 tokens = ast.tokens
192 comments = ast.comments
193 } else {
194 tokens = []
195 comments = []
196 parser.tokenize(code, { range: true, loc: true, comment: true }, function(token) {
197 if (token.type === 'LineComment') {
198 token.type = 'Line'
199 comments.push(token)
200 } else if (token.type === 'BlockComment') {
201 token.type = 'Block'
202 comments.push(token)
203 } else {
204 // Optimistically upgrade 'static' to a keyword
205 if (token.type === 'Identifier' && token.value === 'static') token.type = 'Keyword'
206 tokens.push(token)
207 }
208 })
209 }
210 normalize(config)
211
212 function tokenIndex(tokens, tkn, start) {
213 var current
214 var rangeStart = tkn.range[0]
215
216 for (current = start; current < tokens.length; current++) {
217 if (tokens[current].range[0] === rangeStart) return current
218 }
219
220 throw new Error('Token %s not found at or after index: %d', tkn, start)
221 }
222
223 function process(surround) {
224 var result
225 var currentIndex
226 var nextIndex
227 var skip = 0
228 var splitEnd
229
230 result = surround(code.slice(start, end), info)
231 if (isObject(result)) {
232 splits.push(result.replacement)
233
234 currentIndex = info.tokenIndex
235 nextIndex = tokenIndex(info.tokens, result.skipPastToken, currentIndex)
236 skip = nextIndex - currentIndex
237 splitEnd = skip > 0 ? tokens[nextIndex - 1].range[1] : end
238 } else {
239 splits.push(result)
240 splitEnd = end
241 }
242
243 return { skip: skip, splitEnd: splitEnd }
244 }
245
246 function addSplit(start, end, surround, info) {
247 var result
248 var skip = 0
249
250 if (start >= end) return
251 if (surround) {
252 result = process(surround)
253 skip = result.skip
254 lastSplitEnd = result.splitEnd
255 } else {
256 splits.push(code.slice(start, end))
257 lastSplitEnd = end
258 }
259
260 return skip
261 }
262
263 all = mergeTokensAndComments(tokens, comments)
264 for (var tokenIdx = 0; tokenIdx < all.length; tokenIdx++) {
265 var token = all[tokenIdx]
266 var surroundForType = config[token.type]
267 var surround
268 var start
269 var end
270
271 // At least the type (e.g., 'Keyword') needs to be specified for the token to be surrounded
272 if (surroundForType) {
273 // root defaults are only taken into account while resolving before/after otherwise
274 // a root default would apply to everything, even if no type default was specified
275 surround = surroundForType
276 && surroundForType.hasOwnProperty(token.value)
277 && surroundForType[token.value]
278 && isFunction(surroundForType[token.value])
279 ? surroundForType[token.value]
280 : surroundForType._default
281
282 start = token.range[0]
283 end = token.range[1]
284
285 addSplit(lastSplitEnd, start)
286 info = { tokenIndex: tokenIdx, tokens: all, ast: ast, code: code }
287 tokenIdx += addSplit(start, end, surround, info)
288 }
289 }
290
291 if (lastSplitEnd < code.length) {
292 addSplit(lastSplitEnd, code.length)
293 }
294
295 if (!opts.nojoin) {
296 transformedCode = splits.join('')
297 if (hashbang.length > 0) {
298 transformedCode = hashbang + transformedCode.substr(hashbang.length)
299 }
300 }
301
302 return {
303 ast : ast
304 , tokens : tokens
305 , comments : comments
306 , splits : splits
307 , code : transformedCode
308 }
309 }
310
311 return exportFn ? exportFn(redeyed) : redeyed
312}
313})()