300 lines
9.1 KiB
JavaScript
300 lines
9.1 KiB
JavaScript
(function (global, factory) {
|
|
// eslint-disable-next-line no-unused-expressions
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports)
|
|
// eslint-disable-next-line no-undef
|
|
: typeof define === 'function' && define.amd ? define('jsonlint-printer', ['exports'], factory)
|
|
// eslint-disable-next-line no-undef
|
|
: (global = global || self, factory(global.jsonlintPrinter = {}))
|
|
}(this, function (exports) {
|
|
'use strict'
|
|
|
|
function noop () {}
|
|
|
|
function isIdentifierName (value) {
|
|
return /^[a-zA-Z$_][a-zA-Z0-9$_]*$/.test(value)
|
|
}
|
|
|
|
function concatenateTokens (tokens) {
|
|
var outputString = ''
|
|
var tokenCount = tokens.length
|
|
var tokenIndex
|
|
for (tokenIndex = 0; tokenIndex < tokenCount; ++tokenIndex) {
|
|
outputString += tokens[tokenIndex].raw
|
|
}
|
|
return outputString
|
|
}
|
|
|
|
function print (tokens, options) {
|
|
if (!(tokens && tokens.length)) {
|
|
throw new Error('JSON tokens missing.')
|
|
}
|
|
// Whitespace and comments are available only as raw token content.
|
|
if (!(tokens[0] && tokens[0].raw)) {
|
|
throw new Error('JSON tokens lack raw values.')
|
|
}
|
|
|
|
if (!options) {
|
|
// If no options, not even an empty object is passed, just concatenate
|
|
// the raw tokens with neither minification, nor pretty-printing.
|
|
return concatenateTokens(tokens)
|
|
}
|
|
|
|
var indentString = options.indent
|
|
if (typeof indentString === 'number') {
|
|
indentString = new Array(indentString + 1).join(' ')
|
|
}
|
|
// Setting the indent to an empty string enables pretty-printing too.
|
|
// It will just insert line breaks without any indentation.
|
|
var prettyPrint = indentString !== undefined
|
|
var pruneComments = options.pruneComments
|
|
var stripObjectKeys = options.stripObjectKeys
|
|
var enforceDoubleQuotes = options.enforceDoubleQuotes
|
|
var enforceSingleQuotes = options.enforceSingleQuotes
|
|
var trimTrailingCommas = options.trimTrailingCommas
|
|
|
|
var outputString = ''
|
|
var foundLineBreak, addedLineBreak, needsLineBreak
|
|
var addedSpace, needsSpace
|
|
var indentLevel = 0
|
|
var scopes = []
|
|
var scopeType
|
|
var isValue
|
|
var tokenCount = tokens.length
|
|
var tokenIndex, token, tokenType, tokenContent
|
|
|
|
function peekAtNextToken () {
|
|
var nextTokenIndex = tokenIndex
|
|
var nextToken
|
|
do {
|
|
nextToken = tokens[++nextTokenIndex]
|
|
} while (nextToken && (nextToken.type === 'whitespace' ||
|
|
nextToken.type === 'comment'))
|
|
return nextToken
|
|
}
|
|
|
|
var addIndent
|
|
if (prettyPrint && indentString) {
|
|
addIndent = function () {
|
|
for (var i = 0; i < indentLevel; ++i) {
|
|
outputString += indentString
|
|
}
|
|
}
|
|
} else {
|
|
addIndent = noop
|
|
}
|
|
|
|
var addLineBreak, addDelayedSpaceOrLineBreak
|
|
if (prettyPrint) {
|
|
addLineBreak = function () {
|
|
outputString += '\n'
|
|
}
|
|
|
|
addDelayedSpaceOrLineBreak = function () {
|
|
// A line break is more important than a space.
|
|
if (needsLineBreak) {
|
|
addLineBreak()
|
|
addIndent()
|
|
} else if (needsSpace) {
|
|
outputString += ' '
|
|
}
|
|
needsSpace = needsLineBreak = false
|
|
}
|
|
} else {
|
|
addLineBreak = addDelayedSpaceOrLineBreak = noop
|
|
}
|
|
|
|
var addStandaloneComment, tryAddingInlineComment
|
|
if (pruneComments) {
|
|
addStandaloneComment = tryAddingInlineComment = noop
|
|
} else {
|
|
if (prettyPrint) {
|
|
addStandaloneComment = function () {
|
|
// If a comment is not appended to the end of a line, it will start
|
|
// on a new line with the current indentation.
|
|
if (!addedLineBreak && tokenIndex > 0) {
|
|
addLineBreak()
|
|
addIndent()
|
|
}
|
|
outputString += tokenContent
|
|
foundLineBreak = false
|
|
addedLineBreak = false
|
|
// If a comment is not appended to the end of a line, it will take
|
|
// the whole line and has to end by a line break.
|
|
needsLineBreak = true
|
|
}
|
|
|
|
tryAddingInlineComment = function () {
|
|
// This function is called after printing a non-line-break character.
|
|
foundLineBreak = false
|
|
addedLineBreak = false
|
|
addedSpace = false
|
|
|
|
// Start with the character after the just processed one.
|
|
var tryTokenIndex = tokenIndex + 1
|
|
|
|
function skipWhitespace () {
|
|
var token = tokens[tryTokenIndex]
|
|
if (token && token.type === 'whitespace') {
|
|
foundLineBreak = token.raw.indexOf('\n') >= 0
|
|
token = tokens[++tryTokenIndex]
|
|
}
|
|
return token
|
|
}
|
|
|
|
var token = skipWhitespace()
|
|
// If line break followed the previous token, leave the comment
|
|
// to be handled by the next usual token processing.
|
|
if (!foundLineBreak && token && token.type === 'comment') {
|
|
if (needsLineBreak) {
|
|
// If the previous non-whitespace token was ended by a line
|
|
// break, retain it. Print the comment after the line break too.
|
|
if (!addedLineBreak) {
|
|
addLineBreak()
|
|
addIndent()
|
|
}
|
|
} else {
|
|
// If the previous non-whitespace token was not ended by a line
|
|
// break, ensure that the comment is separated from it.
|
|
if (!addedSpace) {
|
|
outputString += ' '
|
|
}
|
|
}
|
|
outputString += token.raw
|
|
// Set the current token to the just processed comment.
|
|
tokenIndex = tryTokenIndex++
|
|
// Check the whitespace after the comment to give a hint
|
|
// about the next whitespace to the further processing.
|
|
skipWhitespace()
|
|
if (foundLineBreak) {
|
|
needsSpace = false
|
|
needsLineBreak = true
|
|
} else {
|
|
needsSpace = true
|
|
needsLineBreak = false
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// If all whitespace is omitted, convert single-line comments
|
|
// to multi-line ones, which include a comment-closing token.
|
|
addStandaloneComment = function () {
|
|
if (tokenContent[1] === '/') {
|
|
outputString += '/*'
|
|
outputString += tokenContent.substr(2, tokenContent.length - 2)
|
|
outputString += ' */'
|
|
} else {
|
|
outputString += tokenContent
|
|
}
|
|
}
|
|
|
|
tryAddingInlineComment = noop
|
|
}
|
|
}
|
|
|
|
function addLiteral () {
|
|
addDelayedSpaceOrLineBreak()
|
|
var tokenValue = token.value
|
|
if (stripObjectKeys && scopeType === '{' && !isValue &&
|
|
isIdentifierName(tokenValue)) {
|
|
outputString += tokenValue
|
|
} else if (typeof tokenValue === 'string') {
|
|
if (enforceDoubleQuotes && tokenContent[0] !== '"') {
|
|
outputString += JSON.stringify(tokenValue)
|
|
} else if (enforceSingleQuotes && tokenContent[0] !== '\'') {
|
|
outputString += '\'' + tokenValue.replace(/'/g, '\\\'') + '\''
|
|
} else {
|
|
outputString += tokenContent
|
|
}
|
|
} else {
|
|
outputString += tokenContent
|
|
}
|
|
tryAddingInlineComment()
|
|
}
|
|
|
|
function openScope () {
|
|
addDelayedSpaceOrLineBreak()
|
|
scopes.push(scopeType)
|
|
scopeType = tokenContent
|
|
isValue = scopeType === '['
|
|
outputString += tokenContent
|
|
tryAddingInlineComment()
|
|
++indentLevel
|
|
needsLineBreak = true
|
|
}
|
|
|
|
function closeScope () {
|
|
scopeType = scopes.pop()
|
|
addLineBreak()
|
|
--indentLevel
|
|
addIndent()
|
|
needsSpace = needsLineBreak = false
|
|
outputString += tokenContent
|
|
tryAddingInlineComment()
|
|
}
|
|
|
|
function addComma () {
|
|
if (trimTrailingCommas) {
|
|
var nextToken = peekAtNextToken()
|
|
if (nextToken && nextToken.type === 'symbol') {
|
|
return tryAddingInlineComment()
|
|
}
|
|
}
|
|
addDelayedSpaceOrLineBreak()
|
|
outputString += ','
|
|
tryAddingInlineComment()
|
|
addLineBreak()
|
|
addIndent()
|
|
addedLineBreak = true
|
|
needsLineBreak = false
|
|
isValue = scopeType === '['
|
|
}
|
|
|
|
function addColon () {
|
|
addDelayedSpaceOrLineBreak()
|
|
outputString += ':'
|
|
needsSpace = true
|
|
tryAddingInlineComment()
|
|
isValue = true
|
|
}
|
|
|
|
for (tokenIndex = 0; tokenIndex < tokenCount; ++tokenIndex) {
|
|
token = tokens[tokenIndex]
|
|
tokenType = token.type
|
|
tokenContent = token.raw
|
|
switch (tokenType) {
|
|
case 'literal':
|
|
addLiteral()
|
|
break
|
|
case 'comment':
|
|
addStandaloneComment()
|
|
break
|
|
case 'symbol':
|
|
switch (tokenContent) {
|
|
case '{':
|
|
case '[':
|
|
openScope()
|
|
break
|
|
case '}':
|
|
case ']':
|
|
closeScope()
|
|
break
|
|
case ',':
|
|
addComma()
|
|
break
|
|
case ':':
|
|
addColon()
|
|
}
|
|
break
|
|
default: // whitespace
|
|
foundLineBreak = tokenContent.indexOf('\n') >= 0
|
|
}
|
|
}
|
|
|
|
return outputString
|
|
}
|
|
|
|
exports.print = print
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true })
|
|
}))
|