Added test to ensure that release notes are parsed properly Updated to latest dependencies to fix security vulnerability Updated GH Actions Workflow to run test before updating tag Updated README.md to reflect proper whatsnew documentation.
262 lines
6.8 KiB
JavaScript
262 lines
6.8 KiB
JavaScript
const util = require('./util')
|
|
|
|
module.exports = function stringify (value, replacer, space) {
|
|
const stack = []
|
|
let indent = ''
|
|
let propertyList
|
|
let replacerFunc
|
|
let gap = ''
|
|
let quote
|
|
|
|
if (
|
|
replacer != null &&
|
|
typeof replacer === 'object' &&
|
|
!Array.isArray(replacer)
|
|
) {
|
|
space = replacer.space
|
|
quote = replacer.quote
|
|
replacer = replacer.replacer
|
|
}
|
|
|
|
if (typeof replacer === 'function') {
|
|
replacerFunc = replacer
|
|
} else if (Array.isArray(replacer)) {
|
|
propertyList = []
|
|
for (const v of replacer) {
|
|
let item
|
|
|
|
if (typeof v === 'string') {
|
|
item = v
|
|
} else if (
|
|
typeof v === 'number' ||
|
|
v instanceof String ||
|
|
v instanceof Number
|
|
) {
|
|
item = String(v)
|
|
}
|
|
|
|
if (item !== undefined && propertyList.indexOf(item) < 0) {
|
|
propertyList.push(item)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (space instanceof Number) {
|
|
space = Number(space)
|
|
} else if (space instanceof String) {
|
|
space = String(space)
|
|
}
|
|
|
|
if (typeof space === 'number') {
|
|
if (space > 0) {
|
|
space = Math.min(10, Math.floor(space))
|
|
gap = ' '.substr(0, space)
|
|
}
|
|
} else if (typeof space === 'string') {
|
|
gap = space.substr(0, 10)
|
|
}
|
|
|
|
return serializeProperty('', {'': value})
|
|
|
|
function serializeProperty (key, holder) {
|
|
let value = holder[key]
|
|
if (value != null) {
|
|
if (typeof value.toJSON5 === 'function') {
|
|
value = value.toJSON5(key)
|
|
} else if (typeof value.toJSON === 'function') {
|
|
value = value.toJSON(key)
|
|
}
|
|
}
|
|
|
|
if (replacerFunc) {
|
|
value = replacerFunc.call(holder, key, value)
|
|
}
|
|
|
|
if (value instanceof Number) {
|
|
value = Number(value)
|
|
} else if (value instanceof String) {
|
|
value = String(value)
|
|
} else if (value instanceof Boolean) {
|
|
value = value.valueOf()
|
|
}
|
|
|
|
switch (value) {
|
|
case null: return 'null'
|
|
case true: return 'true'
|
|
case false: return 'false'
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
return quoteString(value, false)
|
|
}
|
|
|
|
if (typeof value === 'number') {
|
|
return String(value)
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
return Array.isArray(value) ? serializeArray(value) : serializeObject(value)
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
function quoteString (value) {
|
|
const quotes = {
|
|
"'": 0.1,
|
|
'"': 0.2,
|
|
}
|
|
|
|
const replacements = {
|
|
"'": "\\'",
|
|
'"': '\\"',
|
|
'\\': '\\\\',
|
|
'\b': '\\b',
|
|
'\f': '\\f',
|
|
'\n': '\\n',
|
|
'\r': '\\r',
|
|
'\t': '\\t',
|
|
'\v': '\\v',
|
|
'\0': '\\0',
|
|
'\u2028': '\\u2028',
|
|
'\u2029': '\\u2029',
|
|
}
|
|
|
|
let product = ''
|
|
|
|
for (let i = 0; i < value.length; i++) {
|
|
const c = value[i]
|
|
switch (c) {
|
|
case "'":
|
|
case '"':
|
|
quotes[c]++
|
|
product += c
|
|
continue
|
|
|
|
case '\0':
|
|
if (util.isDigit(value[i + 1])) {
|
|
product += '\\x00'
|
|
continue
|
|
}
|
|
}
|
|
|
|
if (replacements[c]) {
|
|
product += replacements[c]
|
|
continue
|
|
}
|
|
|
|
if (c < ' ') {
|
|
let hexString = c.charCodeAt(0).toString(16)
|
|
product += '\\x' + ('00' + hexString).substring(hexString.length)
|
|
continue
|
|
}
|
|
|
|
product += c
|
|
}
|
|
|
|
const quoteChar = quote || Object.keys(quotes).reduce((a, b) => (quotes[a] < quotes[b]) ? a : b)
|
|
|
|
product = product.replace(new RegExp(quoteChar, 'g'), replacements[quoteChar])
|
|
|
|
return quoteChar + product + quoteChar
|
|
}
|
|
|
|
function serializeObject (value) {
|
|
if (stack.indexOf(value) >= 0) {
|
|
throw TypeError('Converting circular structure to JSON5')
|
|
}
|
|
|
|
stack.push(value)
|
|
|
|
let stepback = indent
|
|
indent = indent + gap
|
|
|
|
let keys = propertyList || Object.keys(value)
|
|
let partial = []
|
|
for (const key of keys) {
|
|
const propertyString = serializeProperty(key, value)
|
|
if (propertyString !== undefined) {
|
|
let member = serializeKey(key) + ':'
|
|
if (gap !== '') {
|
|
member += ' '
|
|
}
|
|
member += propertyString
|
|
partial.push(member)
|
|
}
|
|
}
|
|
|
|
let final
|
|
if (partial.length === 0) {
|
|
final = '{}'
|
|
} else {
|
|
let properties
|
|
if (gap === '') {
|
|
properties = partial.join(',')
|
|
final = '{' + properties + '}'
|
|
} else {
|
|
let separator = ',\n' + indent
|
|
properties = partial.join(separator)
|
|
final = '{\n' + indent + properties + ',\n' + stepback + '}'
|
|
}
|
|
}
|
|
|
|
stack.pop()
|
|
indent = stepback
|
|
return final
|
|
}
|
|
|
|
function serializeKey (key) {
|
|
if (key.length === 0) {
|
|
return quoteString(key, true)
|
|
}
|
|
|
|
const firstChar = String.fromCodePoint(key.codePointAt(0))
|
|
if (!util.isIdStartChar(firstChar)) {
|
|
return quoteString(key, true)
|
|
}
|
|
|
|
for (let i = firstChar.length; i < key.length; i++) {
|
|
if (!util.isIdContinueChar(String.fromCodePoint(key.codePointAt(i)))) {
|
|
return quoteString(key, true)
|
|
}
|
|
}
|
|
|
|
return key
|
|
}
|
|
|
|
function serializeArray (value) {
|
|
if (stack.indexOf(value) >= 0) {
|
|
throw TypeError('Converting circular structure to JSON5')
|
|
}
|
|
|
|
stack.push(value)
|
|
|
|
let stepback = indent
|
|
indent = indent + gap
|
|
|
|
let partial = []
|
|
for (let i = 0; i < value.length; i++) {
|
|
const propertyString = serializeProperty(String(i), value)
|
|
partial.push((propertyString !== undefined) ? propertyString : 'null')
|
|
}
|
|
|
|
let final
|
|
if (partial.length === 0) {
|
|
final = '[]'
|
|
} else {
|
|
if (gap === '') {
|
|
let properties = partial.join(',')
|
|
final = '[' + properties + ']'
|
|
} else {
|
|
let separator = ',\n' + indent
|
|
let properties = partial.join(separator)
|
|
final = '[\n' + indent + properties + ',\n' + stepback + ']'
|
|
}
|
|
}
|
|
|
|
stack.pop()
|
|
indent = stepback
|
|
return final
|
|
}
|
|
}
|