All files / text / unstable_dedent.ts

91.67% Branches 11/12
91.18% Lines 31/34
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
104
105
106
107
108
 
 
x5
 
x5
x5
x5
x5
 
x5
x5
x5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x5
x5
x5
 
 
x106
x106
x106
x106
 
 
 
 
 
 
x106
x106
 
x106
x106
x414
x106
 
 
x106
 
x200
x200
x200
x200
x200
x200
x200
 
x200
 
x106

















































































I
























// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.
import { longestCommonPrefix } from "./unstable_longest_common_prefix.ts";

const WHITE_SPACE = String.raw`\t\v\f\ufeff\p{Space_Separator}`;
const INDENT_REGEXP = new RegExp(
  String.raw`^[${WHITE_SPACE}]+`,
  "u",
);
const WHITE_SPACE_ONLY_LINE_REGEXP = new RegExp(
  String.raw`^[${WHITE_SPACE}]+$`,
  "mu",
);

/**
 * 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");

  const linesToCheck = lines.slice(
    ignoreFirstUnindented && !INDENT_REGEXP.test(lines[0] ?? "") ? 1 : 0,
  )
    .filter((l) => l.length > 0 && !WHITE_SPACE_ONLY_LINE_REGEXP.test(l));

  const commonPrefix = longestCommonPrefix(linesToCheck);
  const indent = commonPrefix.match(INDENT_REGEXP)?.[0];

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

  // No lines to indent
  if (!indent) return trimmedInput;

  const minIndentRegex = new RegExp(String.raw`^${indent}`, "gmu");
  return trimmedInput
    .replaceAll(minIndentRegex, "")
    .replaceAll(
      new RegExp(
        WHITE_SPACE_ONLY_LINE_REGEXP,
        WHITE_SPACE_ONLY_LINE_REGEXP.flags + "g",
      ),
      "",
    );
}