All files / text / unstable_dedent.ts

91.67% Branches 11/12
91.67% Lines 33/36
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
109
110
111
 
 
x5
 
x5
x5
x5
x5
 
x5
x5
x5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x5
x5
x5
 
 
x107
x107
x107
x107
x107
 
x107
 
 
 
 
 
 
x107
x107
 
x107
x107
x418
x107
 
 
x107
 
x202
x202
x202
x202
x202
x202
x202
 
x202
 
x107




















































































I
























// Copyright 2018-2026 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 a single trailing newline (with any preceding whitespace on that line)
 * - 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 a single trailing newline (with any preceding whitespace on that line)
 * - 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/, "").replace(
    /\n[\t ]*$/,
    "",
  );
  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/, "").replace(/\n[\t ]*$/, "");

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