Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve dedent (fixes #1202) #1203

Merged
merged 4 commits into from
Jan 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions src/jsutils/__tests__/dedent-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import { expect } from 'chai';
import { describe, it } from 'mocha';
import dedent from '../dedent';

describe('dedent', () => {
it('removes indentation in typical usage', () => {
const output = dedent`
type Query {
me: User
}

type User {
id: ID
name: String
}
`;
expect(output).to.equal(
[
'type Query {',
' me: User',
'}',
'',
'type User {',
' id: ID',
' name: String',
'}',
'',
].join('\n'),
);
});

it('removes only the first level of indentation', () => {
const output = dedent`
qux
quux
quuux
quuuux
`;
expect(output).to.equal(
['qux', ' quux', ' quuux', ' quuuux', ''].join('\n'),
);
});

it('does not escape special characters', () => {
const output = dedent`
type Root {
field(arg: String = "wi\th de\fault"): String
}
`;
expect(output).to.equal(
[
'type Root {',
' field(arg: String = "wi\th de\fault"): String',
'}',
'',
].join('\n'),
);
});

it('also works as an ordinary function on strings', () => {
const output = dedent(`
type Query {
me: User
}
`);
expect(output).to.equal(['type Query {', ' me: User', '}', ''].join('\n'));
});

it('also removes indentation using tabs', () => {
const output = dedent`
\t\t type Query {
\t\t me: User
\t\t }
`;
expect(output).to.equal(['type Query {', ' me: User', '}', ''].join('\n'));
});

it('removes leading newlines', () => {
const output = dedent`


type Query {
me: User
}`;
expect(output).to.equal(['type Query {', ' me: User', '}'].join('\n'));
});

it('does not remove trailing newlines', () => {
const output = dedent`
type Query {
me: User
}

`;
expect(output).to.equal(
['type Query {', ' me: User', '}', '', ''].join('\n'),
);
});

it('removes all trailing spaces and tabs', () => {
const output = dedent`
type Query {
me: User
}
\t\t \t `;
expect(output).to.equal(['type Query {', ' me: User', '}', ''].join('\n'));
});

it('works on text without leading newline', () => {
const output = dedent` type Query {
me: User
}`;
expect(output).to.equal(['type Query {', ' me: User', '}'].join('\n'));
});

it('supports expression interpolation', () => {
const name = 'Luke Skywalker';
const age = 42;
const output = dedent`
{
"me": {
"name": "${name}"
"age": ${age}
}
}
`;
expect(output).to.equal(
[
'{',
' "me": {',
' "name": "Luke Skywalker"',
' "age": 42',
' }',
'}',
'',
].join('\n'),
);
});
});
35 changes: 18 additions & 17 deletions src/jsutils/dedent.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@
*/

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

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

if (i < values.length) {
res += values[i];
const str = strArray.reduce((prev, cur, index) => {
let next = prev + cur;
if (index < numValues) {
next += values[index]; // interpolation
}
}
return next;
}, '');

return fixIndent(res);
return fixIndent(str);
}
6 changes: 4 additions & 2 deletions src/language/__tests__/printer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ describe('Printer: Query document', () => {

const printed = print(ast);

expect(printed).to.equal(dedent`
expect(printed).to.equal(
dedent(String.raw`
query queryName($foo: ComplexType, $site: Site = MOBILE) {
whoever123is: node(id: [123, 456]) {
id
Expand Down Expand Up @@ -198,6 +199,7 @@ describe('Printer: Query document', () => {
unnamed(truthy: true, falsey: false, nullish: null)
query
}
`);
`),
);
});
});
6 changes: 4 additions & 2 deletions src/utilities/__tests__/schemaPrinter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,17 @@ describe('Type System Printer', () => {
type: GraphQLString,
args: { argOne: { type: GraphQLString, defaultValue: 'tes\t de\fault' } },
});
expect(output).to.equal(dedent`
expect(output).to.equal(
dedent(String.raw`
schema {
query: Root
}

type Root {
singleField(argOne: String = "tes\t de\fault"): String
}
`);
`),
);
});

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