diff --git a/packages/printer/src/formatters/escapeQuotes.ts b/packages/printer/src/formatters/escapeQuotes.ts new file mode 100644 index 0000000..1db8a38 --- /dev/null +++ b/packages/printer/src/formatters/escapeQuotes.ts @@ -0,0 +1,6 @@ +export function escapeQuotes(str: string, quotes: string) { + if (!quotes) return str + if (quotes == '"') return JSON.stringify(str) + str = str.split(quotes).join('\\' + quotes) + return `${quotes}${str}${quotes}` +} diff --git a/packages/printer/src/formatters/formatter.ts b/packages/printer/src/formatters/formatter.ts index cb599c8..c8e2474 100644 --- a/packages/printer/src/formatters/formatter.ts +++ b/packages/printer/src/formatters/formatter.ts @@ -4,6 +4,7 @@ import { isString } from '@logpot/utils' import { toColorizer } from '../consoleColor' import { PrintContext } from '../printContext' import { Printer } from '../printer' +import { escapeQuotes } from './escapeQuotes' import { getText } from './getText' // A common interface for all formatters. @@ -41,10 +42,8 @@ export class StringFormatter extends PrimitiveFormatter { return isString(value) } format(value: unknown, ctx: PrintContext) { - return getText( - ctx, - toColorizer(ctx.colorConfig.string)(`${ctx.quotes}${value}${ctx.quotes}`) - ) + const str = escapeQuotes(value as string, ctx.quotes) + return getText(ctx, toColorizer(ctx.colorConfig.string)(str)) } } @@ -147,7 +146,7 @@ export class DateFormatter extends PrimitiveFormatter { case 'epoch': return colorizer(date.getTime().toString()) } - return getText(ctx, colorizer(`${ctx.quotes}${str}${ctx.quotes}`)) + return getText(ctx, colorizer(escapeQuotes(str, ctx.quotes))) } } diff --git a/packages/printer/src/formatters/objectFormatter.ts b/packages/printer/src/formatters/objectFormatter.ts index eedcf0a..1dfa137 100644 --- a/packages/printer/src/formatters/objectFormatter.ts +++ b/packages/printer/src/formatters/objectFormatter.ts @@ -3,6 +3,7 @@ import { isEmptyPlainObject, isPlainObject } from '@logpot/utils' import { toColorizer } from '../consoleColor' import { PrintContext } from '../printContext' import { Printer } from '../printer' +import { escapeQuotes } from './escapeQuotes' import { IFormatter } from './formatter' import { getText } from './getText' import { ObjectFormatterConfig } from './objectFormatterConfig' @@ -84,7 +85,7 @@ export class ObjectFormatter implements IFormatter { printer: Printer ): string { const coloredKey = toColorizer(ctx.colorConfig.key)( - `${ctx.quotes}${key}${ctx.quotes}` + escapeQuotes(key, ctx.quotes) ) let indent = ctx.indent diff --git a/packages/printer/src/formatters/stringFormatter.test.ts b/packages/printer/src/formatters/stringFormatter.test.ts new file mode 100644 index 0000000..6ce9a5b --- /dev/null +++ b/packages/printer/src/formatters/stringFormatter.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest' + +import { colorlessConfig } from '../colorConfig' +import { PrintContext } from '../printContext' +import { StringFormatter } from './formatter' + +const makeCtx = ( + overrides: Partial> = {} +): PrintContext => ({ + indent: '', + maxIndent: 10, + indentString: ' ', + colorConfig: colorlessConfig, + seen: new WeakSet(), + quotes: overrides.quotes ?? '"', + keys: [], + prefix: '', +}) + +describe('StringFormatter', () => { + it('escapes double quotes when using double quotes', () => { + const fmt = new StringFormatter() + const ctx = makeCtx() + const value = 'a "quote" b' + const result = fmt.format(value, ctx) + const expected = `${ctx.quotes}${value + .split(ctx.quotes) + .join(`\\${ctx.quotes}`)}${ctx.quotes}` + expect(result).toBe(expected) + }) + + it('escapes single quotes when using single quotes', () => { + const fmt = new StringFormatter() + const ctx = makeCtx({ quotes: "'" }) + const value = "a 'quote' b" + const result = fmt.format(value, ctx) + const expected = `${ctx.quotes}${value + .split(ctx.quotes) + .join(`\\${ctx.quotes}`)}${ctx.quotes}` + expect(result).toBe(expected) + }) + + it('does not escape when quotes are disabled', () => { + const fmt = new StringFormatter() + const ctx = makeCtx({ quotes: '' }) + const value = 'a "quote" b' + const result = fmt.format(value, ctx) + expect(result).toBe(value) + }) +})