diff --git a/CHANGELOG.md b/CHANGELOG.md index 83d6be5c..a068d6f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Reduced nesting of `/lib` folder. - Parse numbers according to YAML 1.2 instead of YAML 1.1 (`01234` is now decimal, `0o1234` is octal, `1:23` is parsed as string instead of base60). -- `dump()` no longer quotes `:` except when necessary, #470. +- `dump()` no longer quotes `:`, `[`, `]`, `(`, `)` except when necessary, #470, #557. ### Added - Added `.mjs` (es modules) support. diff --git a/lib/dumper.js b/lib/dumper.js index 6bead1d7..cc7e48bb 100644 --- a/lib/dumper.js +++ b/lib/dumper.js @@ -9,6 +9,7 @@ var DEFAULT_SCHEMA = require('./schema/default'); var _toString = Object.prototype.toString; var _hasOwnProperty = Object.prototype.hasOwnProperty; +var CHAR_BOM = 0xFEFF; var CHAR_TAB = 0x09; /* Tab */ var CHAR_LINE_FEED = 0x0A; /* LF */ var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */ @@ -193,50 +194,60 @@ function isWhitespace(c) { function isPrintable(c) { return (0x00020 <= c && c <= 0x00007E) || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029) - || ((0x0E000 <= c && c <= 0x00FFFD) && c !== 0xFEFF /* BOM */) + || ((0x0E000 <= c && c <= 0x00FFFD) && c !== CHAR_BOM) || (0x10000 <= c && c <= 0x10FFFF); } // [34] ns-char ::= nb-char - s-white // [27] nb-char ::= c-printable - b-char - c-byte-order-mark // [26] b-char ::= b-line-feed | b-carriage-return -// [24] b-line-feed ::= #xA /* LF */ -// [25] b-carriage-return ::= #xD /* CR */ -// [3] c-byte-order-mark ::= #xFEFF -function isNsChar(c) { - return isPrintable(c) && !isWhitespace(c) - // byte-order-mark - && c !== 0xFEFF - // b-char +// Including s-white (for some reason, examples doesn't match specs in this aspect) +// ns-char ::= c-printable - b-line-feed - b-carriage-return - c-byte-order-mark +function isNsCharOrWhitespace(c) { + return isPrintable(c) + && c !== CHAR_BOM + // - b-char && c !== CHAR_CARRIAGE_RETURN && c !== CHAR_LINE_FEED; } -// Simplified test for values allowed after the first character in plain style. -function isPlainSafe(c, prev) { - // Uses a subset of nb-char - c-flow-indicator - ":" - "#" - // where nb-char ::= c-printable - b-char - c-byte-order-mark. - return isPrintable(c) && c !== 0xFEFF - // - c-flow-indicator - && c !== CHAR_COMMA - && c !== CHAR_LEFT_SQUARE_BRACKET - && c !== CHAR_RIGHT_SQUARE_BRACKET - && c !== CHAR_LEFT_CURLY_BRACKET - && c !== CHAR_RIGHT_CURLY_BRACKET - // - "#" - // /* An ns-char preceding */ "#" - && ((c !== CHAR_SHARP) || (prev && isNsChar(prev))) - // - ":" - && ((c !== CHAR_COLON) || (prev && isNsChar(prev))) - // - but ": " isn't plain-safe - && !(prev && prev === CHAR_COLON && !isNsChar(c)); +// [127] ns-plain-safe(c) ::= c = flow-out ⇒ ns-plain-safe-out +// c = flow-in ⇒ ns-plain-safe-in +// c = block-key ⇒ ns-plain-safe-out +// c = flow-key ⇒ ns-plain-safe-in +// [128] ns-plain-safe-out ::= ns-char +// [129] ns-plain-safe-in ::= ns-char - c-flow-indicator +// [130] ns-plain-char(c) ::= ( ns-plain-safe(c) - “:” - “#” ) +// | ( /* An ns-char preceding */ “#” ) +// | ( “:” /* Followed by an ns-plain-safe(c) */ ) +function isPlainSafe(c, prev, flowLevel) { + var cIsNsCharOrWhitespace = isNsCharOrWhitespace(c); + var cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c); + return ( + // ns-plain-safe + flowLevel < 0 ? // c = flow-in + cIsNsCharOrWhitespace + : cIsNsCharOrWhitespace + // - c-flow-indicator + && c !== CHAR_COMMA + && c !== CHAR_LEFT_SQUARE_BRACKET + && c !== CHAR_RIGHT_SQUARE_BRACKET + && c !== CHAR_LEFT_CURLY_BRACKET + && c !== CHAR_RIGHT_CURLY_BRACKET + ) + // ns-plain-char + && c !== CHAR_SHARP // false on '#' + && !(prev === CHAR_COLON && !cIsNsChar) // false on ': ' + || (isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP) // change to true on '[^ ]#' + || (prev === CHAR_COLON && cIsNsChar); // change to true on ':[^ ]' } // Simplified test for values allowed as the first character in plain style. function isPlainSafeFirst(c) { // Uses a subset of ns-char - c-indicator // where ns-char = nb-char - s-white. - return isPrintable(c) && c !== 0xFEFF + // No support of ( ( “?” | “:” | “-” ) /* Followed by an ns-plain-safe(c)) */ ) part + return isPrintable(c) && c !== CHAR_BOM && !isWhitespace(c) // - s-white // - (c-indicator ::= // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}” @@ -303,7 +314,7 @@ var STYLE_PLAIN = 1, // STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1). // STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1). function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, - testAmbiguousType, quotingType, forceQuotes) { + testAmbiguousType, quotingType, forceQuotes, flowLevel) { var i; var char = 0; @@ -323,7 +334,7 @@ function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, if (!isPrintable(char)) { return STYLE_DOUBLE; } - plain = plain && isPlainSafe(char, prevChar); + plain = plain && isPlainSafe(char, prevChar, flowLevel); prevChar = char; } } else { @@ -343,7 +354,7 @@ function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, } else if (!isPrintable(char)) { return STYLE_DOUBLE; } - plain = plain && isPlainSafe(char, prevChar); + plain = plain && isPlainSafe(char, prevChar, flowLevel); prevChar = char; } // in case the end is missing a \n @@ -411,7 +422,7 @@ function writeScalar(state, string, level, iskey) { } switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, - testAmbiguity, state.quotingType, state.forceQuotes && !iskey)) { + testAmbiguity, state.quotingType, state.forceQuotes && !iskey, state.flowLevel)) { case STYLE_PLAIN: return string; diff --git a/test/issues/0521.js b/test/issues/0521.js index 9c72380e..c8e671a2 100644 --- a/test/issues/0521.js +++ b/test/issues/0521.js @@ -6,21 +6,22 @@ var yaml = require('../../'); it('Don\'t quote strings with # without need', function () { - var data = yaml.load(` -http://example.com/page#anchor: no#quotes#required + var required = ` +http://example.com/page#anchor: no:quotes#required parameter#fallback: 'quotes #required' +'quotes: required': Visit [link](http://example.com/foo#bar) 'foo #bar': key is quoted -`); +`.replace(/^\n/, ''); var sample = { - 'http://example.com/page#anchor': 'no#quotes#required', + 'http://example.com/page#anchor': 'no:quotes#required', 'parameter#fallback': 'quotes #required', + 'quotes: required': 'Visit [link](http://example.com/foo#bar)', 'foo #bar': 'key is quoted' }; - assert.deepStrictEqual( + assert.strictEqual( yaml.dump(sample), - yaml.dump(data) + required ); - });