From c2c5d7726ced85bc4d38e0bdec686b941f365036 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Wed, 8 May 2024 02:42:57 +0200 Subject: [PATCH] feat: add `unindent` function --- src/__snapshots__/string.test.ts.snap | 31 +++++++++++++++++++ src/string.test.ts | 44 ++++++++++++++++++++++++++- src/string.ts | 39 ++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/__snapshots__/string.test.ts.snap diff --git a/src/__snapshots__/string.test.ts.snap b/src/__snapshots__/string.test.ts.snap new file mode 100644 index 0000000..7666bdc --- /dev/null +++ b/src/__snapshots__/string.test.ts.snap @@ -0,0 +1,31 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`unindent > base 1`] = ` +"if (a) { + b() +}" +`; + +exports[`unindent > indent deep 1`] = ` +" if (a) { + b() +}" +`; + +exports[`unindent > multi-start and end 1`] = ` +" if (a) { + b() +}" +`; + +exports[`unindent > no start or end 1`] = ` +"if (a) { + b() +}" +`; + +exports[`unindent > with leading indent 1`] = ` +" if (a) { + b() +}" +`; diff --git a/src/string.test.ts b/src/string.test.ts index fdc89de..6821e6d 100644 --- a/src/string.test.ts +++ b/src/string.test.ts @@ -1,5 +1,5 @@ import { expect, it } from 'vitest' -import { capitalize, ensurePrefix, ensureSuffix, slash, template } from './string' +import { capitalize, ensurePrefix, ensureSuffix, slash, template, unindent } from './string' it('template', () => { expect( @@ -119,3 +119,45 @@ it('capitalize', () => { expect(capitalize('āÁĂÀ')).toEqual('Āáăà') expect(capitalize('\a')).toEqual('A') }) + +it('unindent', () => { + expect( + unindent` + if (a) { + b() + } + `, + ).toMatchSnapshot('base') + + expect( + unindent` + if (a) { + b() + } + `, + ).toMatchSnapshot('with leading indent') + + expect( + unindent` + + if (a) { + b() + } + + `, + ).toMatchSnapshot('multi-start and end') + + expect( + unindent`if (a) { + b() +}`, + ).toMatchSnapshot('no start or end') + + expect( + unindent` + if (a) { + b() + } + `, + ).toMatchSnapshot('indent deep') +}) diff --git a/src/string.ts b/src/string.ts index 8d066b3..edde718 100644 --- a/src/string.ts +++ b/src/string.ts @@ -106,3 +106,42 @@ export function randomStr(size = 16, dict = urlAlphabet) { export function capitalize(str: string): string { return str[0].toUpperCase() + str.slice(1).toLowerCase() } + +const _reFullWs = /^\s*$/ + +/** + * Remove common leading whitespace from a template string. + * Will also remove empty lines at the beginning and end. + * @category string + * @example + * ```ts + * const str = unindent` + * if (a) { + * b() + * } + * ` + */ +export function unindent(str: TemplateStringsArray | string) { + const lines = (typeof str === 'string' ? str : str[0]).split('\n') + const whitespaceLines = lines.map(line => _reFullWs.test(line)) + + const commonIndent = lines + .reduce((min, line, idx) => { + if (whitespaceLines[idx]) + return min + const indent = line.match(/^\s*/)?.[0].length + return indent === undefined ? min : Math.min(min, indent) + }, Number.POSITIVE_INFINITY) + + let emptyLinesHead = 0 + while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead]) + emptyLinesHead++ + let emptyLinesTail = 0 + while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1]) + emptyLinesTail++ + + return lines + .slice(emptyLinesHead, lines.length - emptyLinesTail) + .map(line => line.slice(commonIndent)) + .join('\n') +}