Skip to content

Commit

Permalink
feat: sort and quote keys according to Yarn's rules when writing yarn…
Browse files Browse the repository at this point in the history
….lock
  • Loading branch information
shalvah committed Oct 9, 2022
1 parent 37bf6d3 commit ee4fb78
Show file tree
Hide file tree
Showing 3 changed files with 2,829 additions and 2,800 deletions.
55 changes: 42 additions & 13 deletions workspaces/arborist/lib/yarn-lock.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
// <key> <value>
//
// Assume that any key or value might be quoted, though that's only done
// in practice if certain chars are in the string. Quoting unnecessarily
// does not cause problems for yarn, so that's what we do when we write
// it back.
// in practice if certain chars are in the string. When writing back, we follow
// Yarn's rules for quoting, to cause minimal friction.
//
// The data format would support nested objects, but at this time, it
// appears that yarn does not use that for anything, so in the interest
Expand All @@ -33,10 +32,45 @@ const consistentResolve = require('./consistent-resolve.js')
const { dirname } = require('path')
const { breadth } = require('treeverse')

// Sort Yarn entries respecting the yarn.lock sort order
const yarnEntryPriorities = {
name: 1,
version: 2,
uid: 3,
resolved: 4,
integrity: 5,
registry: 6,
dependencies: 7,
}

const priorityThenLocaleCompare = (a, b) => {
if (yarnEntryPriorities[a] || yarnEntryPriorities[b]) {
return (yarnEntryPriorities[a] || 100) > (yarnEntryPriorities[b] || 100) ? 1 : -1
}

return localeCompare(a, b)
}

const shouldQuoteString = str => {
return str.indexOf('true') === 0 ||
str.indexOf('false') === 0 ||
/[:\s\n\\",[\]]/g.test(str) ||
/^[0-9]/g.test(str) ||
!/^[a-zA-Z]/g.test(str)
}

const quoteIfNeeded = val => {
if (typeof val === 'boolean' || typeof val === 'number' || shouldQuoteString(val)) {
return JSON.stringify(val)
}

return val
}

// sort a key/value object into a string of JSON stringified keys and vals
const sortKV = obj => Object.keys(obj)
.sort(localeCompare)
.map(k => ` ${JSON.stringify(k)} ${JSON.stringify(obj[k])}`)
.map(k => ` ${quoteIfNeeded(k)} ${quoteIfNeeded(obj[k])}`)
.join('\n')

// for checking against previous entries
Expand Down Expand Up @@ -171,7 +205,7 @@ class YarnLock {
toString () {
return prefix + [...new Set([...this.entries.values()])]
.map(e => e.toString())
.sort(localeCompare).join('\n\n') + '\n'
.sort((a, b) => localeCompare(a.replaceAll('"', ''), b.replaceAll('"', ''))).join('\n\n') + '\n'
}

fromTree (tree) {
Expand Down Expand Up @@ -323,19 +357,14 @@ class YarnLockEntry {
// sort objects to the bottom, then alphabetical
return ([...this[_specs]]
.sort(localeCompare)
.map(JSON.stringify).join(', ') +
.map(quoteIfNeeded).join(', ') +
':\n' +
Object.getOwnPropertyNames(this)
.filter(prop => this[prop] !== null)
.sort(
(a, b) =>
/* istanbul ignore next - sort call order is unpredictable */
(typeof this[a] === 'object') === (typeof this[b] === 'object')
? localeCompare(a, b)
: typeof this[a] === 'object' ? 1 : -1)
.sort(priorityThenLocaleCompare)
.map(prop =>
typeof this[prop] !== 'object'
? ` ${JSON.stringify(prop)} ${JSON.stringify(this[prop])}\n`
? ` ${prop} ${prop === 'integrity' ? this[prop] : JSON.stringify(this[prop])}\n`
: Object.keys(this[prop]).length === 0 ? ''
: ` ${prop}:\n` + sortKV(this[prop]) + '\n')
.join('')).trim()
Expand Down
Loading

0 comments on commit ee4fb78

Please sign in to comment.