(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 }) }))