Skip to content

Commit

Permalink
feat: add unindent function
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed May 8, 2024
1 parent feac67b commit c2c5d77
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 1 deletion.
31 changes: 31 additions & 0 deletions src/__snapshots__/string.test.ts.snap
Original file line number Diff line number Diff line change
@@ -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()
}"
`;
44 changes: 43 additions & 1 deletion src/string.test.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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')
})
39 changes: 39 additions & 0 deletions src/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}

0 comments on commit c2c5d77

Please sign in to comment.