/
stripIgnoredCharacters.js
129 lines (117 loc) · 3.49 KB
/
stripIgnoredCharacters.js
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
*/
import inspect from '../jsutils/inspect';
import { Source } from '../language/source';
import { createLexer, TokenKind, isPunctuatorToken } from '../language/lexer';
import {
dedentBlockStringValue,
getBlockStringIndentation,
} from '../language/blockString';
/**
* Strips characters that are not significant to the validity or execution
* of a GraphQL document:
* - UnicodeBOM
* - WhiteSpace
* - LineTerminator
* - Comment
* - Comma
* - BlockString indentation
*
* Note: It is required to have a delimiter character between neighboring
* non-punctuator tokens and this function always uses single space as delimiter.
*
* It is guaranteed that both input and output documents if parsed would result
* in the exact same AST except for nodes location.
*
* Warning: It is guaranteed that this function will always produce stable results.
* However, it's not guaranteed that it will stay the same between different
* releases due to bugfixes or changes in the GraphQL specification.
*
* Query example:
*
* query SomeQuery($foo: String!, $bar: String) {
* someField(foo: $foo, bar: $bar) {
* a
* b {
* c
* d
* }
* }
* }
*
* Becomes:
*
* query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{c d}}}
*
* SDL example:
*
* """
* Type description
* """
* type Foo {
* """
* Field description
* """
* bar: String
* }
*
* Becomes:
*
* """Type description""" type Foo{"""Field description""" bar:String}
*/
export function stripIgnoredCharacters(source: string | Source): string {
const sourceObj = typeof source === 'string' ? new Source(source) : source;
if (!(sourceObj instanceof Source)) {
throw new TypeError(
`Must provide string or Source. Received: ${inspect(sourceObj)}`,
);
}
const body = sourceObj.body;
const lexer = createLexer(sourceObj);
let strippedBody = '';
let wasLastAddedTokenNonPunctuator = false;
while (lexer.advance().kind !== TokenKind.EOF) {
const currentToken = lexer.token;
const tokenKind = currentToken.kind;
/**
* Every two non-punctuator tokens should have space between them.
* Also prevent case of non-punctuator token following by spread resulting
* in invalid token (e.g. `1...` is invalid Float token).
*/
const isNonPunctuator = !isPunctuatorToken(currentToken);
if (wasLastAddedTokenNonPunctuator) {
if (isNonPunctuator || currentToken.kind === TokenKind.SPREAD) {
strippedBody += ' ';
}
}
const tokenBody = body.slice(currentToken.start, currentToken.end);
if (tokenKind === TokenKind.BLOCK_STRING) {
strippedBody += dedentBlockString(tokenBody);
} else {
strippedBody += tokenBody;
}
wasLastAddedTokenNonPunctuator = isNonPunctuator;
}
return strippedBody;
}
function dedentBlockString(blockStr) {
// skip leading and trailing triple quotations
const rawStr = blockStr.slice(3, -3);
let body = dedentBlockStringValue(rawStr);
const lines = body.split(/\r\n|[\n\r]/g);
if (getBlockStringIndentation(lines) > 0) {
body = '\n' + body;
}
const lastChar = body[body.length - 1];
const hasTrailingQuote = lastChar === '"' && body.slice(-4) !== '\\"""';
if (hasTrailingQuote || lastChar === '\\') {
body += '\n';
}
return '"""' + body + '"""';
}