import { CST, Parser } from 'yaml' import { source } from './_utils.ts' function cstDoc(src: string) { const tokens = Array.from(new Parser().parse(src)) expect(tokens).toHaveLength(1) expect(tokens[0].type).toBe('document') return tokens[0] as CST.Document } describe('CST.visit', () => { test('Visit paths in order', () => { const doc = cstDoc(source` foo: - baz - [ bar: 42 ] ? { fuzz, ball } : fii `) const paths: CST.VisitPath[] = [] CST.visit(doc, (_item, path) => { paths.push(path) }) expect(paths).toMatchObject([ [], [['value', 0]], [ ['value', 0], ['value', 0] ], [ ['value', 0], ['value', 1] ], [ ['value', 0], ['value', 1], ['value', 0] ], [['value', 1]], [ ['value', 1], ['key', 0] ], [ ['value', 1], ['key', 1] ] ]) }) test('break visit', () => { const doc = cstDoc(source` - " foo" - "foo" - "bar" `) let visits = 0 CST.visit(doc, item => { visits += 1 const scalar = CST.resolveAsScalar(item.value) if (scalar?.value === 'foo') return CST.visit.BREAK }) expect(visits).toBe(3) }) test('remove item', () => { const doc = cstDoc(source` - " foo" - "foo" - "bar" `) let visits = 0 CST.visit(doc, item => { visits += 1 const scalar = CST.resolveAsScalar(item.value) if (scalar?.value === 'foo') return CST.visit.REMOVE }) expect(visits).toBe(4) expect(CST.stringify(doc)).toBe(source` - " foo" - "bar" `) }) test('replace value with block scalar in seq', () => { const doc = cstDoc(source` - " foo" - "foo" - "bar" `) let visits = 0 CST.visit(doc, item => { visits += 1 if (item.value) { const scalar = CST.resolveAsScalar(item.value) if (scalar?.value === 'foo') CST.setScalarValue(item.value, 'foo', { type: 'BLOCK_LITERAL' }) } }) expect(visits).toBe(4) expect(CST.stringify(doc)).toBe(source` - " foo" - |- foo - "bar" `) }) test('add item', () => { const doc = cstDoc(source` - " foo" - "foo" - "bar" `) let visits = 0 CST.visit(doc, (item, path) => { visits += 1 if (CST.isScalar(item.value)) { const scalar = CST.resolveAsScalar(item.value) if (scalar?.value === 'foo') { const parent = CST.visit.parentCollection(doc, path) const idx = path[path.length - 1][1] const { indent } = item.value parent.items.splice(idx, 0, { start: item.start.slice(), value: CST.createScalarToken('hip', { indent }) }) return idx + 2 } } }) expect(visits).toBe(4) expect(CST.stringify(doc)).toBe(source` - " foo" - hip - "foo" - "bar" `) }) test('replace value with flow scalar in map', () => { const doc = cstDoc(source` - a: A b: B\t#comment c: C `) let visits = 0 CST.visit(doc, item => { visits += 1 if (item.value) { const scalar = CST.resolveAsScalar(item.value) if (scalar?.value === 'B') { CST.setScalarValue(item.value, 'foo\n\nbar', { afterKey: !!item.key }) } } }) expect(visits).toBe(5) expect(CST.stringify(doc)).toBe(source` - a: A b: foo bar\t#comment c: C `) }) test('skip children', () => { const doc = cstDoc(source` - " foo" - [ foo, 13, 42 ] - "bar" `) let visits = 0 CST.visit(doc, item => { visits += 1 if (item.value?.type === 'flow-collection') return CST.visit.SKIP }) expect(visits).toBe(4) }) test('set next index', () => { const doc = cstDoc(source` - "foo" - [ foo, 13, 42 ] - "bar" `) let visits = 0 CST.visit(doc, item => { visits += 1 const scalar = CST.resolveAsScalar(item.value) if (scalar?.value === 'foo') return 2 }) expect(visits).toBe(3) }) }) test('Line comment before unindented block-seq in block-map (#525)', () => { const src = 'a:\n#\n- b' const [doc] = Array.from(new Parser().parse(src)) expect(CST.stringify(doc)).toBe(src) }) describe('CST.setScalarValue block scalar indent (#349)', () => { function setBlockScalar(src: string, value: string, afterKey = true) { const doc = cstDoc(src) let found: CST.BlockScalar | null = null CST.visit(doc, item => { if (item.value?.type === 'block-scalar') { found = item.value CST.setScalarValue(item.value, value, { afterKey: afterKey && !!item.key }) } }) expect(found).not.toBe(null) return { src: CST.stringify(doc), token: found as unknown as CST.BlockScalar } } test('keeps content indented by more than one step', () => { const src = 'key:\n |\n line one\n line two\n' expect(setBlockScalar(src, 'new one\nnew two\n').src).toBe( 'key:\n |\n new one\n new two\n' ) }) test('keeps content indent when not marked as being after a key', () => { const src = 'key: |\n line one\n' expect(setBlockScalar(src, 'new one\n', false).src).toBe( 'key: |\n new one\n' ) }) test('keeps the indent of a nested block scalar', () => { const src = 'a:\n b: |\n deep\n' expect(setBlockScalar(src, 'new\n').src).toBe('a:\n b: |\n new\n') }) test('keeps the indent given by an explicit indentation indicator', () => { const src = 'key: |2\n line one\n' expect(setBlockScalar(src, 'new one\n').src).toBe('key: |\n new one\n') }) test('keeps the indent when the first content line is empty', () => { const src = 'key: |\n\n line one\n' expect(setBlockScalar(src, 'new one\n').src).toBe('key: |\n new one\n') }) test('leaves an indent of one step alone', () => { const src = 'key: |\n line one\n' expect(setBlockScalar(src, 'new one\n').src).toBe('key: |\n new one\n') }) test('resolves back to the value that was set', () => { const src = 'key:\n |\n line one\n line two\n' const { token } = setBlockScalar(src, 'new one\nnew two\n') expect(CST.resolveAsScalar(token).value).toBe('new one\nnew two\n') }) test('still indents a block scalar replacing a flow scalar', () => { const doc = cstDoc('key: "old"\n') CST.visit(doc, item => { if (item.value && CST.isScalar(item.value)) CST.setScalarValue(item.value, 'new one\nnew two\n', { afterKey: !!item.key, type: 'BLOCK_LITERAL' }) }) expect(CST.stringify(doc)).toBe('key: |\n new one\n new two\n') }) })