Repository: eemeli/yaml
Pull request #343 (2021-12-27 08:53:37)

Add collectionStyle option
Add to ToString options:

```ts
type ToStringOptions = { ...
  /**
   * Enforce `'block'` or `'flow'` style on maps and sequences.
   * Empty collections will always be stringified as `{}` or `[]`.
   *
   * Default: `'any'`, allowing each node to set its style separately
   * with its `flow: boolean` (default `false`) property.
   */
  collectionStyle?: 'any' | 'block' | 'flow'
}
```

This should help in particular with the production of JSON-ish output, where block collections are not supported.


Patch:
diff --git a/docs/03_options.md b/docs/03_options.md
index d589e86..7cabf29 100644
--- a/docs/03_options.md
+++ b/docs/03_options.md
@@ -149,6 +149,7 @@ Used by: `stringify()` and `doc.toString()`
 | Name                           | Type                             | Default   | Description                                                                                                                                                                                                                   |
 | ------------------------------ | -------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | blockQuote                     | `boolean ⎮ 'folded' ⎮ 'literal'` | `true`    | Use block quote styles for scalar values where applicable. Set to `false` to disable block quotes completely.                                                                                                                 |
+| collectionStyle                | `'any' ⎮ 'block' ⎮ 'flow'`       | `'any'`   | Enforce `'block'` or `'flow'` style on maps and sequences. By default, allows each collection to set its own `flow: boolean` property.                                                                                        |
 | commentString                  | `(comment: string) => string`    |           | Output should be valid for the current schema. By default, empty comment lines are left empty, lines consisting of a single space are replaced by `#`, and all other lines are prefixed with a `#`.                           |
 | defaultKeyType                 | `Type ⎮ null`                    | `null`    | If not `null`, overrides `defaultStringType` for implicit key values.                                                                                                                                                         |
 | defaultStringType              | `Type`                           | `'PLAIN'` | The default type of string literal used to stringify values.                                                                                                                                                                  |
diff --git a/src/options.ts b/src/options.ts
index 2766680..16eb638 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -223,6 +223,15 @@ export type ToStringOptions = {
    */
   blockQuote?: boolean | 'folded' | 'literal'
 
+  /**
+   * Enforce `'block'` or `'flow'` style on maps and sequences.
+   * Empty collections will always be stringified as `{}` or `[]`.
+   *
+   * Default: `'any'`, allowing each node to set its style separately
+   * with its `flow: boolean` (default `false`) property.
+   */
+  collectionStyle?: 'any' | 'block' | 'flow'
+
   /**
    * Comment stringifier.
    * Output should be valid for the current schema.
diff --git a/src/stringify/stringify.ts b/src/stringify/stringify.ts
index d929224..0f204b5 100644
--- a/src/stringify/stringify.ts
+++ b/src/stringify/stringify.ts
@@ -24,9 +24,11 @@ export type StringifyContext = {
   indent: string
   indentStep: string
   indentAtStart?: number
-  inFlow?: boolean
+  inFlow: boolean | null
   inStringifyKey?: boolean
-  options: Readonly<Required<Omit<ToStringOptions, 'indent'>>>
+  options: Readonly<
+    Required<Omit<ToStringOptions, 'collectionStyle' | 'indent'>>
+  >
 }
 
 export function createStringifyContext(
@@ -56,11 +58,24 @@ export function createStringifyContext(
     options
   )
 
+  let inFlow: boolean | null
+  switch (opt.collectionStyle) {
+    case 'block':
+      inFlow = false
+      break
+    case 'flow':
+      inFlow = true
+      break
+    default:
+      inFlow = null
+  }
+
   return {
     anchors: new Set(),
     doc,
     indent: '',
     indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : '  ',
+    inFlow,
     options: opt
   }
 }
diff --git a/src/stringify/stringifyCollection.ts b/src/stringify/stringifyCollection.ts
index 7521e61..53b228a 100644
--- a/src/stringify/stringifyCollection.ts
+++ b/src/stringify/stringifyCollection.ts
@@ -16,10 +16,8 @@ export function stringifyCollection(
   ctx: StringifyContext,
   options: StringifyCollectionOptions
 ) {
-  const stringify =
-    collection.flow || ctx.inFlow
-      ? stringifyFlowCollection
-      : stringifyBlockCollection
+  const flow = ctx.inFlow ?? collection.flow
+  const stringify = flow ? stringifyFlowCollection : stringifyBlockCollection
   return stringify(collection, ctx, options)
 }
 


Test patch:
diff --git a/tests/doc/stringify.js b/tests/doc/stringify.js
index 553b67d..469a552 100644
--- a/tests/doc/stringify.js
+++ b/tests/doc/stringify.js
@@ -720,6 +720,59 @@ describe('indentSeq: false', () => {
   })
 })
 
+describe('collectionStyle', () => {
+  test('collectionStyle: undefined', () => {
+    const doc = new YAML.Document({ foo: ['bar'] })
+    expect(doc.toString()).toBe('foo:\n  - bar\n')
+
+    doc.contents.flow = false
+    doc.get('foo').flow = true
+    expect(doc.toString()).toBe('foo: [ bar ]\n')
+
+    doc.contents.flow = true
+    doc.get('foo').flow = false
+    expect(doc.toString()).toBe('{ foo: [ bar ] }\n')
+  })
+
+  test("collectionStyle: 'any'", () => {
+    const doc = new YAML.Document({ foo: ['bar'] })
+    expect(doc.toString({ collectionStyle: 'any' })).toBe('foo:\n  - bar\n')
+
+    doc.contents.flow = false
+    doc.get('foo').flow = true
+    expect(doc.toString({ collectionStyle: 'any' })).toBe('foo: [ bar ]\n')
+
+    doc.contents.flow = true
+    doc.get('foo').flow = false
+    expect(doc.toString({ collectionStyle: 'any' })).toBe('{ foo: [ bar ] }\n')
+  })
+
+  test("collectionStyle: 'block'", () => {
+    const doc = new YAML.Document({ foo: ['bar'] })
+    expect(doc.toString({ collectionStyle: 'block' })).toBe('foo:\n  - bar\n')
+
+    doc.contents.flow = false
+    doc.get('foo').flow = true
+    expect(doc.toString({ collectionStyle: 'block' })).toBe('foo:\n  - bar\n')
+
+    doc.contents.flow = true
+    doc.get('foo').flow = false
+    expect(doc.toString({ collectionStyle: 'block' })).toBe('foo:\n  - bar\n')
+  })
+
+  test("collectionStyle: 'flow'", () => {
+    const doc = new YAML.Document({ foo: ['bar'] })
+    expect(doc.toString({ collectionStyle: 'flow' })).toBe('{ foo: [ bar ] }\n')
+
+    doc.get('foo').flow = true
+    expect(doc.toString({ collectionStyle: 'flow' })).toBe('{ foo: [ bar ] }\n')
+
+    doc.contents.flow = true
+    doc.get('foo').flow = false
+    expect(doc.toString({ collectionStyle: 'flow' })).toBe('{ foo: [ bar ] }\n')
+  })
+})
+
 describe('Scalar options', () => {
   describe('defaultStringType & defaultKeyType', () => {
     test('PLAIN, PLAIN', () => {