From f5c2f7fbb4ed473a99acc4ad639a08f6604b0ada Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Mon, 25 May 2026 23:09:47 +0200 Subject: [PATCH 1/2] Unescape \: in parsed dictionary entries --- src/plugins/props/types/map.js | 2 ++ test/PropType.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/plugins/props/types/map.js b/src/plugins/props/types/map.js index f0718d88..fad47e5b 100644 --- a/src/plugins/props/types/map.js +++ b/src/plugins/props/types/map.js @@ -51,7 +51,9 @@ const MapType = PropType.register({ v = typeof defaultValue === "function" ? defaultValue(k, index) : defaultValue; } k = k?.trim?.() ?? k; + k = k?.replaceAll?.("\\:", ":") ?? k; v = v?.trim?.() ?? v; + v = v?.replaceAll?.("\\:", ":") ?? v; if (v === "false") { v = false; } diff --git a/test/PropType.js b/test/PropType.js index 3e82be90..174893f5 100644 --- a/test/PropType.js +++ b/test/PropType.js @@ -356,6 +356,24 @@ export default { }, expect: { 0: "a", 1: "b", 2: "c" }, }, + { + name: "Escaped colons", + run (input) { + return PropType.for({ is: Object }).parse(input); + }, + tests: [ + { + name: "In values", + arg: "url: https\\://example.com", + expect: { url: "https://example.com" }, + }, + { + name: "In keys", + arg: "foo\\:bar: baz", + expect: { "foo:bar": "baz" }, + }, + ], + }, ], }, { From 3a603b4bcf61160eb5cc0305ee48516fe7e6c5ef Mon Sep 17 00:00:00 2001 From: Dmitry Sharabin Date: Thu, 28 May 2026 16:02:24 +0200 Subject: [PATCH 2/2] Address Lea's feedback: Move escape handling from parseEntries into split() --- src/plugins/props/types/map.js | 7 ++----- src/plugins/props/util/split.js | 19 +++++++++++++------ test/PropType.js | 18 ------------------ test/split.js | 15 +++++++++++++++ 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/plugins/props/types/map.js b/src/plugins/props/types/map.js index fad47e5b..130998c6 100644 --- a/src/plugins/props/types/map.js +++ b/src/plugins/props/types/map.js @@ -1,8 +1,7 @@ import PropType from "../util/PropType.js"; +import { split } from "../util/split.js"; import Iterable from "./iterable.js"; -const entrySplitter = /(?= 2) { k = parts.shift(); v = parts.join(":"); @@ -51,9 +50,7 @@ const MapType = PropType.register({ v = typeof defaultValue === "function" ? defaultValue(k, index) : defaultValue; } k = k?.trim?.() ?? k; - k = k?.replaceAll?.("\\:", ":") ?? k; v = v?.trim?.() ?? v; - v = v?.replaceAll?.("\\:", ":") ?? v; if (v === "false") { v = false; } diff --git a/src/plugins/props/util/split.js b/src/plugins/props/util/split.js index df0c0a5d..ebf7e522 100644 --- a/src/plugins/props/util/split.js +++ b/src/plugins/props/util/split.js @@ -15,10 +15,14 @@ function regexEscape (string) { return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); } +function unescapeSeparator (value, separator) { + return separator ? value.replaceAll(`\\${separator}`, separator) : value; +} + /** * Split a value by a separator, respecting pairs (parens, strings, etc.) but - * failing back gracefully for malformed input. Yields each top-level part as - * a trimmed string. + * failing back gracefully for malformed input. The separator can be escaped + * with a backslash. Yields each top-level part as a trimmed string. * * @param {string} value * @param {object} [options] @@ -34,7 +38,7 @@ export function* split (value, { separator = ",", pairs = defaultPairs } = {}) { let isSeparatorWhitespace = !separator; let separatorRegex = isSeparatorWhitespace ? /\s+/g - : RegExp(regexEscape(separator).replace(/^\s*|\s*$/g, "\\s*"), "g"); + : RegExp(`(? unescapeSeparator(p, separator)); return; } @@ -72,7 +79,7 @@ export function* split (value, { separator = ",", pairs = defaultPairs } = {}) { } else if (matched.trim() === separator) { if (stack.length === 0) { - yield value.slice(lastIndex, index).trim(); + yield unescapeSeparator(value.slice(lastIndex, index).trim(), separator); lastIndex = index + matched.length; } } @@ -98,6 +105,6 @@ export function* split (value, { separator = ",", pairs = defaultPairs } = {}) { } if (lastIndex < value.length) { - yield value.slice(lastIndex).trim(); + yield unescapeSeparator(value.slice(lastIndex).trim(), separator); } } diff --git a/test/PropType.js b/test/PropType.js index 174893f5..3e82be90 100644 --- a/test/PropType.js +++ b/test/PropType.js @@ -356,24 +356,6 @@ export default { }, expect: { 0: "a", 1: "b", 2: "c" }, }, - { - name: "Escaped colons", - run (input) { - return PropType.for({ is: Object }).parse(input); - }, - tests: [ - { - name: "In values", - arg: "url: https\\://example.com", - expect: { url: "https://example.com" }, - }, - { - name: "In keys", - arg: "foo\\:bar: baz", - expect: { "foo:bar": "baz" }, - }, - ], - }, ], }, { diff --git a/test/split.js b/test/split.js index 7c9eeb9f..83f40c8f 100644 --- a/test/split.js +++ b/test/split.js @@ -50,5 +50,20 @@ export default { arg: `a ", b, c`, expect: [`a "`, "b", "c"], }, + { + name: "Escaped separator", + tests: [ + { + name: "Default", + arg: "a\\, b, c", + expect: ["a, b", "c"], + }, + { + name: "Custom", + args: ["foo\\:bar: baz", { separator: ":" }], + expect: ["foo:bar", "baz"], + }, + ], + }, ], };