All files / text / unstable_dedent.ts

100.00% Branches 17/17
100.00% Lines 33/33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x3
x3
x3
 
 
x13
x13
x13
x13
 
x13
x13
x40
 
 
x40
x42
x42
 
x65
x40
x69
x69
x40
x50
x50
x40
 
x13
x13
x44
x13
 
 
x13
x15
x15
 
x21
x21
x21
x21
x13





































































































// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

/**
 * Removes indentation from multiline strings.
 *
 * - Removes leading newline
 * - Removes trailing whitespace, including newlines
 * - Replaces whitespace-only lines with empty lines
 * - Finds the minimum indentation among remaining lines and removes that much indentation from all of them
 *
 * @param input The string to remove indentation from.
 * @returns The string without indentation.
 *
 * @example Usage
 * ```ts
 * import { dedent } from "@std/text/unstable-dedent";
 * import { assertEquals } from "@std/assert";
 *
 * assertEquals(
 *   dedent(`
 *     {
 *       msg: "Hello",
 *     }
 *   `),
 *   `{\n  msg: "Hello",\n}`
 * );
 * ```
 */
export function dedent(input: string): string;

/**
 * Removes indentation from multiline strings.
 *
 * - Removes leading newline
 * - Removes trailing whitespace, including newlines
 * - Replaces whitespace-only lines with empty lines
 * - Finds the minimum indentation among remaining lines and removes that much indentation from all of them
 *
 * @param input The template to remove indentation from.
 * @param values The template substitution values.
 * @returns The string without indentation.
 *
 * @example Usage
 * ```ts
 * import { dedent } from "@std/text/unstable-dedent";
 * import { assertEquals } from "@std/assert";
 *
 * assertEquals(
 *   dedent`line 1
 *          line 2`,
 *   "line 1\nline 2"
 * );
 * ```
 */
export function dedent(
  input: TemplateStringsArray,
  ...values: unknown[]
): string;

export function dedent(
  input: TemplateStringsArray | string,
  ...values: unknown[]
): string {
  // Substitute nonempty placeholder so multiline substitutions do not affect indent width.
  const joinedTemplate = typeof input === "string" ? input : input.join("x");
  const ignoreFirstUnindented = !joinedTemplate.startsWith("\n");
  const trimmedTemplate = joinedTemplate.replace(/^\n/, "").trimEnd();
  const lines = trimmedTemplate.split("\n");

  let minIndentWidth: number | undefined = undefined;
  for (let i = 0; i < lines.length; i++) {
    const indentMatch = lines[i]!.match(/^(\s*)\S/);

    // Skip empty lines
    if (indentMatch === null) {
      continue;
    }

    const indentWidth = indentMatch[1]!.length;
    if (ignoreFirstUnindented && i === 0 && indentWidth === 0) {
      continue;
    }
    if (minIndentWidth === undefined || indentWidth < minIndentWidth) {
      minIndentWidth = indentWidth;
    }
  }

  const inputString = typeof input === "string"
    ? input
    : String.raw({ raw: input }, ...values);
  const trimmedInput = inputString.replace(/^\n/, "").trimEnd();

  // No lines to indent
  if (minIndentWidth === undefined || minIndentWidth === 0) {
    return trimmedInput;
  }

  const minIndentRegex = new RegExp(`^\\s{${minIndentWidth}}`, "gm");
  return trimmedInput
    .replaceAll(minIndentRegex, "")
    .replaceAll(/^\s+$/gm, "");
}