Skip to content

Commit 304d0fc

Browse files
feat: add alignValues option (#102)
## PR Checklist - [x] Addresses an existing open issue: fixes #12 - [x] That issue was marked as [`status: accepting prs`](https://github.com/dmnd/dedent/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22) - [x] Steps in [CONTRIBUTING.md](https://github.com/dmnd/dedent/blob/main/.github/CONTRIBUTING.md) were taken ## Overview This PR implements the `alignValues` option for `dedent()` to handle interpolated values with proper indentation alignment. When enabled via `dedent({ alignValues: true })`, the function will align each interpolated value's indentation to match its starting line's indentation within the template literal. And added a basic test for nested interpolated string. --------- Co-authored-by: Josh Goldberg ✨ <[email protected]>
1 parent aab442c commit 304d0fc

File tree

5 files changed

+124
-1
lines changed

5 files changed

+124
-1
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,52 @@ dedenter`input`;
9393
dedenter(`input`);
9494
```
9595

96+
### `alignValues`
97+
98+
When an interpolation evaluates to a multi-line string, only its first line is placed where the `${...}` appears. Subsequent lines keep whatever indentation they already had inside that value (often none), so they can appear “shifted left”.
99+
100+
Enable `alignValues` to fix that visual jump. When `true`, for every multi-line interpolated value, each line after the first gets extra indentation appended so it starts in the same column as the first line.
101+
102+
```js
103+
import dedent from "dedent";
104+
105+
const list = dedent`
106+
- apples
107+
- bananas
108+
- cherries
109+
`;
110+
111+
const withoutAlign = dedent`
112+
List without alignValues (default):
113+
${list}
114+
Done.
115+
`;
116+
117+
const withAlign = dedent.withOptions({ alignValues: true })`
118+
List with alignValues: true
119+
${list}
120+
Done.
121+
`;
122+
123+
console.log(withoutAlign);
124+
console.log("---");
125+
console.log(withAlign);
126+
```
127+
128+
```plaintext
129+
List without alignValues (default):
130+
- apples
131+
- bananas
132+
- cherries
133+
Done.
134+
---
135+
List with alignValues: true
136+
- apples
137+
- bananas
138+
- cherries
139+
Done.
140+
```
141+
96142
### `escapeSpecialCharacters`
97143

98144
JavaScript string tags by default add an extra `\` escape in front of some special characters such as `$` dollar signs.

src/__snapshots__/dedent.test.ts.snap

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,39 @@ exports[`dedent string tag character escapes with escapeSpecialCharacters undefi
9696
9797
exports[`dedent string tag character escapes with escapeSpecialCharacters undefined opening braces 1`] = `"{"`;
9898
99+
exports[`dedent with alignValues false with nested text 1`] = `
100+
"Some nested items I want to dedent:
101+
* first
102+
* second
103+
* third
104+
* fourth
105+
* fifth
106+
* sixth
107+
That's all!"
108+
`;
109+
110+
exports[`dedent with alignValues true with nested text 1`] = `
111+
"Some nested items I want to dedent:
112+
* first
113+
* second
114+
* third
115+
* fourth
116+
* fifth
117+
* sixth
118+
That's all!"
119+
`;
120+
121+
exports[`dedent with alignValues undefined with nested text 1`] = `
122+
"Some nested items I want to dedent:
123+
* first
124+
* second
125+
* third
126+
* fourth
127+
* fifth
128+
* sixth
129+
That's all!"
130+
`;
131+
99132
exports[`dedent with trimWhitespace false with leading whitespace 1`] = `
100133
"
101134

src/dedent.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,26 @@ describe("dedent", () => {
273273
it("does not replace \\n when called as a function", () => {
274274
expect(dedent(`\\nu`)).toBe("\\nu");
275275
});
276+
277+
describe.each([undefined, false, true])(
278+
"with alignValues %s",
279+
(alignValues) => {
280+
test("with nested text", () => {
281+
expect(
282+
dedent.withOptions({ alignValues })`
283+
Some nested items I want to dedent:
284+
* first
285+
${dedent`
286+
* second
287+
* third`}
288+
* fourth
289+
${dedent`
290+
* fifth
291+
* sixth`}
292+
That's all!
293+
`,
294+
).toMatchSnapshot();
295+
});
296+
},
297+
);
276298
});

src/dedent.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function createDedent(options: DedentOptions) {
2020
) {
2121
const raw = typeof strings === "string" ? [strings] : strings.raw;
2222
const {
23+
alignValues = false,
2324
escapeSpecialCharacters = Array.isArray(strings),
2425
trimWhitespace = true,
2526
} = options;
@@ -41,8 +42,10 @@ function createDedent(options: DedentOptions) {
4142
result += next;
4243

4344
if (i < values.length) {
45+
const value = alignValues ? alignValue(values[i], result) : values[i];
46+
4447
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
45-
result += values[i];
48+
result += value;
4649
}
4750
}
4851

@@ -84,3 +87,21 @@ function createDedent(options: DedentOptions) {
8487
return result;
8588
}
8689
}
90+
91+
/**
92+
* Adjusts the indentation of a multi-line interpolated value to match the current line.
93+
*/
94+
function alignValue(value: unknown, precedingText: string) {
95+
if (typeof value !== "string" || !value.includes("\n")) {
96+
return value;
97+
}
98+
99+
const currentLine = precedingText.slice(precedingText.lastIndexOf("\n") + 1);
100+
const indentMatch = currentLine.match(/^(\s+)/);
101+
if (indentMatch) {
102+
const indent = indentMatch[1];
103+
return value.replace(/\n/g, `\n${indent}`);
104+
}
105+
106+
return value;
107+
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export interface DedentOptions {
2+
alignValues?: boolean;
23
escapeSpecialCharacters?: boolean;
34
trimWhitespace?: boolean;
45
}

0 commit comments

Comments
 (0)