Skip to content

Commit 65de110

Browse files
authored
Merge pull request #5494 from rmosolgo/optimize-invalid-number-followed-by-name-regexp
Optimize INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP
2 parents 5065756 + 3e60ec2 commit 65de110

1 file changed

Lines changed: 18 additions & 12 deletions

File tree

lib/graphql/language.rb

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,28 @@ def self.escape_single_quoted_newlines(query_str)
7979

8080
LEADING_REGEX = Regexp.union(" ", *Lexer::Punctuation.constants.map { |const| Lexer::Punctuation.const_get(const) })
8181

82+
# Optimized pattern using:
83+
# - Possessive quantifiers (*+, ++) to prevent backtracking in number patterns
84+
# - Atomic group (?>...) for IGNORE to prevent backtracking
85+
# - Single unified number pattern instead of three alternatives
86+
EFFICIENT_NUMBER_REGEXP = /-?(?:0|[1-9][0-9]*+)(?:\.[0-9]++)?(?:[eE][+-]?[0-9]++)?/
87+
EFFICIENT_IGNORE_REGEXP = /(?>[, \r\n\t]+|\#[^\n]*$)*/
88+
89+
MAYBE_INVALID_NUMBER = /\d[_a-zA-Z]/
90+
8291
INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{
8392
(?<leading>#{LEADING_REGEX})
84-
(
85-
((?<num>#{Lexer::INT_REGEXP}(#{Lexer::FLOAT_EXP_REGEXP})?)(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
86-
|
87-
((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP}#{Lexer::FLOAT_EXP_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
88-
|
89-
((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
90-
)}x
93+
(?<num>#{EFFICIENT_NUMBER_REGEXP})
94+
(?<name>#{Lexer::IDENTIFIER_REGEXP})
95+
#{EFFICIENT_IGNORE_REGEXP}
96+
:
97+
}x
9198

9299
def self.add_space_between_numbers_and_names(query_str)
93-
if query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
94-
query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<leading>\\k<num> \\k<name>:")
95-
else
96-
query_str
97-
end
100+
# Fast check for digit followed by identifier char. If this doesn't match, skip the more expensive regexp entirely.
101+
return query_str unless query_str.match?(MAYBE_INVALID_NUMBER)
102+
return query_str unless query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
103+
query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<leading>\\k<num> \\k<name>:")
98104
end
99105
end
100106
end

0 commit comments

Comments
 (0)