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
 
 
x3
 
x3
x3
x3
x3
 
x3
x3
x3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x3
x3
x3
 
 
x91
x91
x91
x91
 
 
 
 
 
 
x91
x91
 
x91
x91
x356
x91
 
 
x91
 
x172
x172
x172
x172
x172
x172
x172
 
x172
 
x91

















































































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",
      ),
      "",
    );
}