Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/03_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Used by: `parse()`, `parseDocument()`, `parseAllDocuments()`, `new Composer()`,
| prettyErrors | `boolean` | `true` | Include line/col position in errors, along with an extract of the source string. |
| strict | `boolean` | `true` | When parsing, do not ignore errors [required](#silencing-errors-and-warnings) by the YAML 1.2 spec, but caused by unambiguous content. |
| stringKeys | `boolean` | `false` | Parse all mapping keys as strings. Treat all non-scalar keys as errors. |
| keepIndentStep | `boolean` | `false` | Include an `indentStep` value on each parsed `Pair`, preserving the original indentation between keys and values in block maps. |
| uniqueKeys | `boolean ⎮ (a, b) => boolean` | `true` | Whether key uniqueness is checked, or customised. If set to be a function, it will be passed two parsed nodes and should return a boolean value indicating their equality. |

[bigint]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/BigInt
Expand Down
1 change: 1 addition & 0 deletions docs/05_content_nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ On the other hand, `!!int` and `!!float` stringifiers will take `format` into ac
class Pair {
key: Node
value: Node | null
indentStep?: number // set when parsed with keepIndentStep: true
}

class Collection implements NodeBase {
Expand Down
6 changes: 6 additions & 0 deletions src/compose/resolve-block-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ export function resolveBlockMap(
offset = valueNode.range![2]
const pair = new Pair(keyNode, valueNode)
if (ctx.options.keepSourceTokens) pair.srcToken = collItem
if (ctx.options.keepIndentStep) {
pair.indentStep =
valueProps.hasNewline && value && 'indent' in value
? value.indent - bm.indent
: 0
}
map.items.push(pair)
} else {
// key with no value
Expand Down
2 changes: 1 addition & 1 deletion src/compose/resolve-flow-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,4 @@ export function resolveFlowCollection(
}

return coll
}
}
10 changes: 9 additions & 1 deletion src/nodes/Pair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export class Pair<
/** The CST token that was composed into this pair. */
declare srcToken?: CollectionItem

/**
* The number of spaces of indentation between the key and the value.
*
* If the value is in flow / on the same line as the key,
* the indentStep will be 0.
*/
indentStep?: number

constructor(key: NodeOf<K>, value: NodeOf<V> | null = null) {
this.key = key
this.value = value
Expand Down Expand Up @@ -46,4 +54,4 @@ export class Pair<
? stringifyPair(this, ctx, onComment, onChompKeep)
: JSON.stringify(this)
}
}
}
11 changes: 11 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ export type ParseOptions = {
* Default: `true`
*/
uniqueKeys?: boolean | ((a: Node, b: Node) => boolean)

/**
* Include a `indentStep` value on each parsed `Pair`.
*
* This helps keep the original indentation and formatting after making
* transformations to the document. Currently, this only adds formatting
* information to Pairs found in block maps.
*
* Default: `false`
*/
keepIndentStep?: boolean
}

export type DocumentOptions = {
Expand Down
2 changes: 1 addition & 1 deletion src/stringify/stringify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,4 @@ export function stringify(
return node instanceof Scalar || str[0] === '{' || str[0] === '['
? `${props} ${str}`
: `${props}\n${ctx.indent}${str}`
}
}
16 changes: 12 additions & 4 deletions src/stringify/stringifyPair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ import { stringify } from './stringify.ts'
import { indentComment, lineComment } from './stringifyComment.ts'

export function stringifyPair(
{ key, value }: Readonly<Pair>,
{ key, value, indentStep: pairIndentStep }: Readonly<Pair>,
ctx: StringifyContext,
onComment?: () => void,
onChompKeep?: () => void
): string {
const {
let {
indent,
indentStep,
noValues,
options: { commentString, indentSeq, simpleKeys }
} = ctx

// Use the indentStep that is on the Pair by default,
// since that is preserved from the original document.
if (pairIndentStep !== undefined) {
indentStep = ' '.repeat(pairIndentStep)
}

if (simpleKeys) {
if (key.comment) {
throw new Error('With simple keys, key nodes cannot have comments')
Expand Down Expand Up @@ -153,7 +160,8 @@ export function stringifyPair(
}
if (sp0 === -1 || nl0 < sp0) hasPropsLine = true
}
if (!hasPropsLine) ws = `\n${ctx.indent}`
if (!hasPropsLine && (indentStep.length > 0 || !value.flow))
ws = `\n${ctx.indent}`
}
} else if (valueStr === '' || valueStr[0] === '\n') {
ws = ''
Expand All @@ -169,4 +177,4 @@ export function stringifyPair(
}

return str
}
}
173 changes: 173 additions & 0 deletions tests/doc/stringify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,179 @@ describe('custom indent', () => {
})
})

describe('keepIndentStep: true', () => {
test('keep object indentation', () => {
const src = source`
key:
super: indented
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('keep array indentation', () => {
const src = source`
key:
- name: foo
- name: bar
- name: bam
- name: baz
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('keep complex indentation', () => {
const src = source`
key:
super:
- name: foo
- name: bar
- name: bam
- name: baz
metadata:
foo: why
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('handles multiline seq flow on same line', () => {
const src = source`
key: [
aaaaaaaa,
bbbbbbbb,
cccccccc,
dddddddd,
eeeeeeee,
ffffffff,
gggggggg,
hhhhhhhh
]
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})
test('handles multiline flow on same line', () => {
const src = source`
key: !tag {
one: aaaaaaaa,
two: bbbbbbbb,
three: cccccccc,
four: dddddddd,
five: eeeeeeee
}
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('handles multiline flow on next line', () => {
const src = source`
key:
!tag {
one: aaaaaaaa,
two: bbbbbbbb,
three: cccccccc,
four: dddddddd,
five: eeeeeeee
}
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('handles multiline flow on next line', () => {
const src = source`
key:
!tag {
one: aaaaaaaa,
two: bbbbbbbb,
three: cccccccc,
four: dddddddd,
five: eeeeeeee
}
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('handles explicit key map with no indent', () => {
const src = source`
foo:
? [ key ]
: !tag
- foo
- bar
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('handles explicit key map with indent', () => {
const src = source`
foo:
? [ key ]
: !tag
- foo
- bar
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('comment after key, value on next line', () => {
const src = source`
key: # comment
value: indented
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(
source`
key:
# comment
value: indented
`
)
})

test('comment on value line', () => {
const src = source`
key:
value: indented # comment
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('comment before nested key at value indent level', () => {
const src = source`
key:
# comment
super: indented
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
expect(doc.toString()).toBe(src)
})

test('nesting parsed node under a new key preserves inner indentation', () => {
const src = source`
a:
b: 1
c: 2
`
const doc = YAML.parseDocument(src, { keepIndentStep: true })
const wrapper = YAML.parseDocument('outer: null')
wrapper.set('outer', doc.value)
expect(wrapper.toString()).toBe(source`
outer:
a:
b: 1
c: 2
`)
})
})

describe('indentSeq: false', () => {
let obj: unknown
beforeEach(() => {
Expand Down