-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathlanguage.rb
More file actions
112 lines (103 loc) · 3.85 KB
/
Copy pathlanguage.rb
File metadata and controls
112 lines (103 loc) · 3.85 KB
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
# frozen_string_literal: true
require "graphql/language/block_string"
require "graphql/language/comment"
require "graphql/language/printer"
require "graphql/language/sanitized_printer"
require "graphql/language/document_from_schema_definition"
require "graphql/language/generation"
require "graphql/language/lexer"
require "graphql/language/nodes"
require "graphql/language/cache"
require "graphql/language/parser"
require "graphql/language/static_visitor"
require "graphql/language/visitor"
require "graphql/language/definition_slice"
require "strscan"
module GraphQL
module Language
# @api private
def self.serialize(value)
if value.is_a?(Hash)
serialized_hash = value.map do |k, v|
"#{k}:#{serialize v}"
end.join(",")
"{#{serialized_hash}}"
elsif value.is_a?(Array)
serialized_array = value.map do |v|
serialize v
end.join(",")
"[#{serialized_array}]"
else
JSON.generate(value, quirks_mode: true)
end
rescue JSON::GeneratorError
if Float::INFINITY == value
"Infinity"
else
raise
end
end
# Returns a new string if any single-quoted newlines were escaped.
# Otherwise, returns `query_str` unchanged.
# @return [String]
def self.escape_single_quoted_newlines(query_str)
scanner = StringScanner.new(query_str)
inside_single_quoted_string = false
inside_triple_quoted_string = false
new_query_str = nil
while !scanner.eos?
if scanner.skip('"""')
inside_triple_quoted_string = !inside_triple_quoted_string
new_query_str && (new_query_str << scanner.matched)
elsif scanner.skip(/(?:\\"|[^"\n\r])+/m)
new_query_str && (new_query_str << scanner.matched)
elsif scanner.skip('"')
new_query_str && (new_query_str << '"')
if !inside_triple_quoted_string
inside_single_quoted_string = !inside_single_quoted_string
end
elsif scanner.skip("\n")
if inside_single_quoted_string
new_query_str ||= query_str[0, scanner.pos - 1]
new_query_str << '\\n'
else
new_query_str && (new_query_str << "\n")
end
elsif scanner.skip("\r")
if inside_single_quoted_string
new_query_str ||= query_str[0, scanner.pos - 1]
new_query_str << '\\r'
else
new_query_str && (new_query_str << "\r")
end
elsif scanner.eos?
break
else
raise ArgumentError, "Unmatchable string scanner segment: #{scanner.rest.inspect}"
end
end
new_query_str || query_str
end
LEADING_REGEX = Regexp.union(" ", *Lexer::Punctuation.constants.map { |const| Lexer::Punctuation.const_get(const) })
# Optimized pattern using:
# - Possessive quantifiers (*+, ++) to prevent backtracking in number patterns
# - Atomic group (?>...) for IGNORE to prevent backtracking
# - Single unified number pattern instead of three alternatives
EFFICIENT_NUMBER_REGEXP = /-?(?:0|[1-9][0-9]*+)(?:\.[0-9]++)?(?:[eE][+-]?[0-9]++)?/
EFFICIENT_IGNORE_REGEXP = /(?>[, \r\n\t]+|\#[^\n]*$)*/
MAYBE_INVALID_NUMBER = /\d[_a-zA-Z]/
INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{
(?<leading>#{LEADING_REGEX})
(?<num>#{EFFICIENT_NUMBER_REGEXP})
(?<name>#{Lexer::IDENTIFIER_REGEXP})
#{EFFICIENT_IGNORE_REGEXP}
:
}x
def self.add_space_between_numbers_and_names(query_str)
# Fast check for digit followed by identifier char. If this doesn't match, skip the more expensive regexp entirely.
return query_str unless query_str.match?(MAYBE_INVALID_NUMBER)
return query_str unless query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<leading>\\k<num> \\k<name>:")
end
end
end