You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
8.8 KiB
314 lines
8.8 KiB
;(function() {
|
|
'use strict'
|
|
/* global define */
|
|
|
|
var esprima
|
|
var exportFn
|
|
var toString = Object.prototype.toString
|
|
|
|
if (typeof module === 'object' && typeof module.exports === 'object' && typeof require === 'function') {
|
|
// server side
|
|
esprima = require('esprima')
|
|
exportFn = function(redeyed) { module.exports = redeyed }
|
|
bootstrap(esprima, exportFn)
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
// client side
|
|
// amd
|
|
define(['esprima'], function(esprima) {
|
|
return bootstrap(esprima)
|
|
})
|
|
} else if (typeof window === 'object') {
|
|
// no amd -> attach to window if it exists
|
|
// Note that this requires 'esprima' to be defined on the window, so that script has to be loaded first
|
|
window.redeyed = bootstrap(window.esprima)
|
|
}
|
|
|
|
function bootstrap(esprima, exportFn) {
|
|
function isFunction(obj) {
|
|
return toString.call(obj) === '[object Function]'
|
|
}
|
|
|
|
function isString(obj) {
|
|
return toString.call(obj) === '[object String]'
|
|
}
|
|
|
|
function isObject(obj) {
|
|
return toString.call(obj) === '[object Object]'
|
|
}
|
|
|
|
function surroundWith(before, after) {
|
|
return function(s) { return before + s + after }
|
|
}
|
|
|
|
function isNonCircular(key) {
|
|
return key !== '_parent'
|
|
}
|
|
|
|
function objectizeString(value) {
|
|
var vals = value.split(':')
|
|
|
|
if (vals.length === 0 || vals.length > 2) {
|
|
throw new Error(
|
|
'illegal string config: ' + value +
|
|
'\nShould be of format "before:after"'
|
|
)
|
|
}
|
|
|
|
if (vals.length === 1 || vals[1].length === 0) {
|
|
return vals.indexOf(':') < 0 ? { _before: vals[0] } : { _after: vals[0] }
|
|
} else {
|
|
return { _before: vals[0], _after: vals[1] }
|
|
}
|
|
}
|
|
|
|
function objectize(node) {
|
|
// Converts 'bef:aft' to { _before: bef, _after: aft }
|
|
// and resolves undefined before/after from parent or root
|
|
|
|
function resolve(value, key) {
|
|
// resolve before/after from root or parent if it isn't present on the current node
|
|
if (!value._parent) return undefined
|
|
|
|
// Immediate parent
|
|
if (value._parent._default && value._parent._default[key]) return value._parent._default[key]
|
|
|
|
// Root
|
|
var root = value._parent._parent
|
|
if (!root) return undefined
|
|
|
|
return root._default ? root._default[key] : undefined
|
|
}
|
|
|
|
function process(key) {
|
|
var value = node[key]
|
|
|
|
if (!value) return
|
|
if (isFunction(value)) return
|
|
|
|
// normalize all strings to objects
|
|
if (isString(value)) {
|
|
node[key] = value = objectizeString(value)
|
|
}
|
|
|
|
value._parent = node
|
|
if (isObject(value)) {
|
|
if (!value._before && !value._after) return objectize(value)
|
|
|
|
// resolve missing _before or _after from parent(s)
|
|
// in case we only have either one on this node
|
|
value._before = value._before || resolve(value, '_before')
|
|
value._after = value._after || resolve(value, '_after')
|
|
|
|
return
|
|
}
|
|
|
|
throw new Error('nodes need to be either {String}, {Object} or {Function}.' + value + ' is neither.')
|
|
}
|
|
|
|
// Process _default ones first so children can resolve missing before/after from them
|
|
if (node._default) process('_default')
|
|
|
|
Object.keys(node)
|
|
.filter(function(key) {
|
|
return isNonCircular(key)
|
|
&& node.hasOwnProperty(key)
|
|
&& key !== '_before'
|
|
&& key !== '_after'
|
|
&& key !== '_default'
|
|
})
|
|
.forEach(process)
|
|
}
|
|
|
|
function functionize(node) {
|
|
Object.keys(node)
|
|
.filter(function(key) {
|
|
return isNonCircular(key) && node.hasOwnProperty(key)
|
|
})
|
|
.forEach(function(key) {
|
|
var value = node[key]
|
|
|
|
if (isFunction(value)) return
|
|
|
|
if (isObject(value)) {
|
|
if (!value._before && !value._after) return functionize(value)
|
|
|
|
// at this point before/after were "inherited" from the parent or root
|
|
// (see objectize)
|
|
var before = value._before || ''
|
|
var after = value._after || ''
|
|
|
|
node[key] = surroundWith(before, after)
|
|
return node[key]
|
|
}
|
|
})
|
|
}
|
|
|
|
function normalize(root) {
|
|
objectize(root)
|
|
functionize(root)
|
|
}
|
|
|
|
function mergeTokensAndComments(tokens, comments) {
|
|
var all = {}
|
|
|
|
function addToAllByRangeStart(t) { all[ t.range[0] ] = t }
|
|
|
|
tokens.forEach(addToAllByRangeStart)
|
|
comments.forEach(addToAllByRangeStart)
|
|
|
|
// keys are sorted automatically
|
|
return Object.keys(all)
|
|
.map(function(k) { return all[k] })
|
|
}
|
|
|
|
function redeyed(code, config, opts) {
|
|
opts = opts || {}
|
|
var parser = opts.parser || esprima
|
|
var jsx = !!opts.jsx
|
|
// tokenizer doesn't support JSX at this point (esprima@4.0.0)
|
|
// therefore we need to generate the AST via the parser not only to
|
|
// avoid the tokenizer from erroring but also to get JSXIdentifier tokens
|
|
var buildAst = jsx || !!opts.buildAst
|
|
|
|
var hashbang = ''
|
|
var ast
|
|
var tokens
|
|
var comments
|
|
var lastSplitEnd = 0
|
|
var splits = []
|
|
var transformedCode
|
|
var all
|
|
var info
|
|
|
|
// Replace hashbang line with empty whitespaces to preserve token locations
|
|
if (code[0] === '#' && code[1] === '!') {
|
|
hashbang = code.substr(0, code.indexOf('\n') + 1)
|
|
code = Array.apply(0, Array(hashbang.length)).join(' ') + '\n' + code.substr(hashbang.length)
|
|
}
|
|
|
|
if (buildAst) {
|
|
ast = parser.parse(code, { tokens: true, comment: true, range: true, loc: true, tolerant: true, jsx: true })
|
|
tokens = ast.tokens
|
|
comments = ast.comments
|
|
} else {
|
|
tokens = []
|
|
comments = []
|
|
parser.tokenize(code, { range: true, loc: true, comment: true }, function(token) {
|
|
if (token.type === 'LineComment') {
|
|
token.type = 'Line'
|
|
comments.push(token)
|
|
} else if (token.type === 'BlockComment') {
|
|
token.type = 'Block'
|
|
comments.push(token)
|
|
} else {
|
|
// Optimistically upgrade 'static' to a keyword
|
|
if (token.type === 'Identifier' && token.value === 'static') token.type = 'Keyword'
|
|
tokens.push(token)
|
|
}
|
|
})
|
|
}
|
|
normalize(config)
|
|
|
|
function tokenIndex(tokens, tkn, start) {
|
|
var current
|
|
var rangeStart = tkn.range[0]
|
|
|
|
for (current = start; current < tokens.length; current++) {
|
|
if (tokens[current].range[0] === rangeStart) return current
|
|
}
|
|
|
|
throw new Error('Token %s not found at or after index: %d', tkn, start)
|
|
}
|
|
|
|
function process(surround) {
|
|
var result
|
|
var currentIndex
|
|
var nextIndex
|
|
var skip = 0
|
|
var splitEnd
|
|
|
|
result = surround(code.slice(start, end), info)
|
|
if (isObject(result)) {
|
|
splits.push(result.replacement)
|
|
|
|
currentIndex = info.tokenIndex
|
|
nextIndex = tokenIndex(info.tokens, result.skipPastToken, currentIndex)
|
|
skip = nextIndex - currentIndex
|
|
splitEnd = skip > 0 ? tokens[nextIndex - 1].range[1] : end
|
|
} else {
|
|
splits.push(result)
|
|
splitEnd = end
|
|
}
|
|
|
|
return { skip: skip, splitEnd: splitEnd }
|
|
}
|
|
|
|
function addSplit(start, end, surround, info) {
|
|
var result
|
|
var skip = 0
|
|
|
|
if (start >= end) return
|
|
if (surround) {
|
|
result = process(surround)
|
|
skip = result.skip
|
|
lastSplitEnd = result.splitEnd
|
|
} else {
|
|
splits.push(code.slice(start, end))
|
|
lastSplitEnd = end
|
|
}
|
|
|
|
return skip
|
|
}
|
|
|
|
all = mergeTokensAndComments(tokens, comments)
|
|
for (var tokenIdx = 0; tokenIdx < all.length; tokenIdx++) {
|
|
var token = all[tokenIdx]
|
|
var surroundForType = config[token.type]
|
|
var surround
|
|
var start
|
|
var end
|
|
|
|
// At least the type (e.g., 'Keyword') needs to be specified for the token to be surrounded
|
|
if (surroundForType) {
|
|
// root defaults are only taken into account while resolving before/after otherwise
|
|
// a root default would apply to everything, even if no type default was specified
|
|
surround = surroundForType
|
|
&& surroundForType.hasOwnProperty(token.value)
|
|
&& surroundForType[token.value]
|
|
&& isFunction(surroundForType[token.value])
|
|
? surroundForType[token.value]
|
|
: surroundForType._default
|
|
|
|
start = token.range[0]
|
|
end = token.range[1]
|
|
|
|
addSplit(lastSplitEnd, start)
|
|
info = { tokenIndex: tokenIdx, tokens: all, ast: ast, code: code }
|
|
tokenIdx += addSplit(start, end, surround, info)
|
|
}
|
|
}
|
|
|
|
if (lastSplitEnd < code.length) {
|
|
addSplit(lastSplitEnd, code.length)
|
|
}
|
|
|
|
if (!opts.nojoin) {
|
|
transformedCode = splits.join('')
|
|
if (hashbang.length > 0) {
|
|
transformedCode = hashbang + transformedCode.substr(hashbang.length)
|
|
}
|
|
}
|
|
|
|
return {
|
|
ast : ast
|
|
, tokens : tokens
|
|
, comments : comments
|
|
, splits : splits
|
|
, code : transformedCode
|
|
}
|
|
}
|
|
|
|
return exportFn ? exportFn(redeyed) : redeyed
|
|
}
|
|
})()
|