Skip to content

Commit b3787bb

Browse files
Citoleebyron
authored andcommitted
Improve dedent (fixes #1202) (#1203)
* Improve dedent utility tag function used for tests Changed the behavior of the function to not have the undocumented side effect of converting the string to its escaped (raw) form any more, and to also consider tabs in addition to spaces. Also added unit tests for the dedent function and adapted existing tests that were depending on the old behavior of the function. * Show test output as printed, avoid double backslash * Test that dedent properly supports interpolation * Make expected output in dedent-test easier to read
1 parent 6f15123 commit b3787bb

File tree

4 files changed

+172
-21
lines changed

4 files changed

+172
-21
lines changed

src/jsutils/__tests__/dedent-test.js

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import { expect } from 'chai';
9+
import { describe, it } from 'mocha';
10+
import dedent from '../dedent';
11+
12+
describe('dedent', () => {
13+
it('removes indentation in typical usage', () => {
14+
const output = dedent`
15+
type Query {
16+
me: User
17+
}
18+
19+
type User {
20+
id: ID
21+
name: String
22+
}
23+
`;
24+
expect(output).to.equal(
25+
[
26+
'type Query {',
27+
' me: User',
28+
'}',
29+
'',
30+
'type User {',
31+
' id: ID',
32+
' name: String',
33+
'}',
34+
'',
35+
].join('\n'),
36+
);
37+
});
38+
39+
it('removes only the first level of indentation', () => {
40+
const output = dedent`
41+
qux
42+
quux
43+
quuux
44+
quuuux
45+
`;
46+
expect(output).to.equal(
47+
['qux', ' quux', ' quuux', ' quuuux', ''].join('\n'),
48+
);
49+
});
50+
51+
it('does not escape special characters', () => {
52+
const output = dedent`
53+
type Root {
54+
field(arg: String = "wi\th de\fault"): String
55+
}
56+
`;
57+
expect(output).to.equal(
58+
[
59+
'type Root {',
60+
' field(arg: String = "wi\th de\fault"): String',
61+
'}',
62+
'',
63+
].join('\n'),
64+
);
65+
});
66+
67+
it('also works as an ordinary function on strings', () => {
68+
const output = dedent(`
69+
type Query {
70+
me: User
71+
}
72+
`);
73+
expect(output).to.equal(['type Query {', ' me: User', '}', ''].join('\n'));
74+
});
75+
76+
it('also removes indentation using tabs', () => {
77+
const output = dedent`
78+
\t\t type Query {
79+
\t\t me: User
80+
\t\t }
81+
`;
82+
expect(output).to.equal(['type Query {', ' me: User', '}', ''].join('\n'));
83+
});
84+
85+
it('removes leading newlines', () => {
86+
const output = dedent`
87+
88+
89+
type Query {
90+
me: User
91+
}`;
92+
expect(output).to.equal(['type Query {', ' me: User', '}'].join('\n'));
93+
});
94+
95+
it('does not remove trailing newlines', () => {
96+
const output = dedent`
97+
type Query {
98+
me: User
99+
}
100+
101+
`;
102+
expect(output).to.equal(
103+
['type Query {', ' me: User', '}', '', ''].join('\n'),
104+
);
105+
});
106+
107+
it('removes all trailing spaces and tabs', () => {
108+
const output = dedent`
109+
type Query {
110+
me: User
111+
}
112+
\t\t \t `;
113+
expect(output).to.equal(['type Query {', ' me: User', '}', ''].join('\n'));
114+
});
115+
116+
it('works on text without leading newline', () => {
117+
const output = dedent` type Query {
118+
me: User
119+
}`;
120+
expect(output).to.equal(['type Query {', ' me: User', '}'].join('\n'));
121+
});
122+
123+
it('supports expression interpolation', () => {
124+
const name = 'Luke Skywalker';
125+
const age = 42;
126+
const output = dedent`
127+
{
128+
"me": {
129+
"name": "${name}"
130+
"age": ${age}
131+
}
132+
}
133+
`;
134+
expect(output).to.equal(
135+
[
136+
'{',
137+
' "me": {',
138+
' "name": "Luke Skywalker"',
139+
' "age": 42',
140+
' }',
141+
'}',
142+
'',
143+
].join('\n'),
144+
);
145+
});
146+
});

src/jsutils/dedent.js

+18-17
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@
88
*/
99

1010
/**
11-
* fixes indentation by removing leading spaces from each line
11+
* fixes indentation by removing leading spaces and tabs from each line
1212
*/
1313
function fixIndent(str: string): string {
14-
const indent = /^\n?( *)/.exec(str)[1]; // figure out indent
15-
return str
16-
.replace(RegExp('^' + indent, 'mg'), '') // remove indent
14+
const trimmedStr = str
1715
.replace(/^\n*/m, '') // remove leading newline
18-
.replace(/ *$/, ''); // remove trailing spaces
16+
.replace(/[ \t]*$/, ''); // remove trailing spaces and tabs
17+
const indent = /^[ \t]*/.exec(trimmedStr)[0]; // figure out indent
18+
return trimmedStr.replace(RegExp('^' + indent, 'mg'), ''); // remove indent
1919
}
2020

2121
/**
2222
* An ES6 string tag that fixes indentation. Also removes leading newlines
23-
* but keeps trailing ones
23+
* and trailing spaces and tabs, but keeps trailing newlines.
2424
*
2525
* Example usage:
2626
* const str = dedent`
@@ -31,19 +31,20 @@ function fixIndent(str: string): string {
3131
* str === "{\n test\n}\n";
3232
*/
3333
export default function dedent(
34-
strings: string | { raw: [string] },
34+
strings: string | Array<string>,
3535
...values: Array<string>
36-
) {
37-
const raw = typeof strings === 'string' ? [strings] : strings.raw;
38-
let res = '';
39-
// interpolation
40-
for (let i = 0; i < raw.length; i++) {
41-
res += raw[i].replace(/\\`/g, '`'); // handle escaped backticks
36+
): string {
37+
// when used as an ordinary function, allow passing a singleton string
38+
const strArray = typeof strings === 'string' ? [strings] : strings;
39+
const numValues = values.length;
4240

43-
if (i < values.length) {
44-
res += values[i];
41+
const str = strArray.reduce((prev, cur, index) => {
42+
let next = prev + cur;
43+
if (index < numValues) {
44+
next += values[index]; // interpolation
4545
}
46-
}
46+
return next;
47+
}, '');
4748

48-
return fixIndent(res);
49+
return fixIndent(str);
4950
}

src/language/__tests__/printer-test.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ describe('Printer: Query document', () => {
145145

146146
const printed = print(ast);
147147

148-
expect(printed).to.equal(dedent`
148+
expect(printed).to.equal(
149+
dedent(String.raw`
149150
query queryName($foo: ComplexType, $site: Site = MOBILE) {
150151
whoever123is: node(id: [123, 456]) {
151152
id
@@ -198,6 +199,7 @@ describe('Printer: Query document', () => {
198199
unnamed(truthy: true, falsey: false, nullish: null)
199200
query
200201
}
201-
`);
202+
`),
203+
);
202204
});
203205
});

src/utilities/__tests__/schemaPrinter-test.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,17 @@ describe('Type System Printer', () => {
206206
type: GraphQLString,
207207
args: { argOne: { type: GraphQLString, defaultValue: 'tes\t de\fault' } },
208208
});
209-
expect(output).to.equal(dedent`
209+
expect(output).to.equal(
210+
dedent(String.raw`
210211
schema {
211212
query: Root
212213
}
213214
214215
type Root {
215216
singleField(argOne: String = "tes\t de\fault"): String
216217
}
217-
`);
218+
`),
219+
);
218220
});
219221

220222
it('Prints String Field With Int Arg With Default Null', () => {

0 commit comments

Comments
 (0)