stripIgnoredCharacters.js.flow 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. // @flow strict
  2. import { Source, isSource } from '../language/source';
  3. import { TokenKind } from '../language/tokenKind';
  4. import { Lexer, isPunctuatorTokenKind } from '../language/lexer';
  5. import {
  6. dedentBlockStringValue,
  7. getBlockStringIndentation,
  8. } from '../language/blockString';
  9. /**
  10. * Strips characters that are not significant to the validity or execution
  11. * of a GraphQL document:
  12. * - UnicodeBOM
  13. * - WhiteSpace
  14. * - LineTerminator
  15. * - Comment
  16. * - Comma
  17. * - BlockString indentation
  18. *
  19. * Note: It is required to have a delimiter character between neighboring
  20. * non-punctuator tokens and this function always uses single space as delimiter.
  21. *
  22. * It is guaranteed that both input and output documents if parsed would result
  23. * in the exact same AST except for nodes location.
  24. *
  25. * Warning: It is guaranteed that this function will always produce stable results.
  26. * However, it's not guaranteed that it will stay the same between different
  27. * releases due to bugfixes or changes in the GraphQL specification.
  28. *
  29. * Query example:
  30. *
  31. * query SomeQuery($foo: String!, $bar: String) {
  32. * someField(foo: $foo, bar: $bar) {
  33. * a
  34. * b {
  35. * c
  36. * d
  37. * }
  38. * }
  39. * }
  40. *
  41. * Becomes:
  42. *
  43. * query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{c d}}}
  44. *
  45. * SDL example:
  46. *
  47. * """
  48. * Type description
  49. * """
  50. * type Foo {
  51. * """
  52. * Field description
  53. * """
  54. * bar: String
  55. * }
  56. *
  57. * Becomes:
  58. *
  59. * """Type description""" type Foo{"""Field description""" bar:String}
  60. */
  61. export function stripIgnoredCharacters(source: string | Source): string {
  62. const sourceObj = isSource(source) ? source : new Source(source);
  63. const body = sourceObj.body;
  64. const lexer = new Lexer(sourceObj);
  65. let strippedBody = '';
  66. let wasLastAddedTokenNonPunctuator = false;
  67. while (lexer.advance().kind !== TokenKind.EOF) {
  68. const currentToken = lexer.token;
  69. const tokenKind = currentToken.kind;
  70. /**
  71. * Every two non-punctuator tokens should have space between them.
  72. * Also prevent case of non-punctuator token following by spread resulting
  73. * in invalid token (e.g. `1...` is invalid Float token).
  74. */
  75. const isNonPunctuator = !isPunctuatorTokenKind(currentToken.kind);
  76. if (wasLastAddedTokenNonPunctuator) {
  77. if (isNonPunctuator || currentToken.kind === TokenKind.SPREAD) {
  78. strippedBody += ' ';
  79. }
  80. }
  81. const tokenBody = body.slice(currentToken.start, currentToken.end);
  82. if (tokenKind === TokenKind.BLOCK_STRING) {
  83. strippedBody += dedentBlockString(tokenBody);
  84. } else {
  85. strippedBody += tokenBody;
  86. }
  87. wasLastAddedTokenNonPunctuator = isNonPunctuator;
  88. }
  89. return strippedBody;
  90. }
  91. function dedentBlockString(blockStr: string): string {
  92. // skip leading and trailing triple quotations
  93. const rawStr = blockStr.slice(3, -3);
  94. let body = dedentBlockStringValue(rawStr);
  95. if (getBlockStringIndentation(body) > 0) {
  96. body = '\n' + body;
  97. }
  98. const lastChar = body[body.length - 1];
  99. const hasTrailingQuote = lastChar === '"' && body.slice(-4) !== '\\"""';
  100. if (hasTrailingQuote || lastChar === '\\') {
  101. body += '\n';
  102. }
  103. return '"""' + body + '"""';
  104. }