Skip to content

Commit df0f899

Browse files
committed
RFC: Lexing is Greedy
Adds the test cases described in graphql/graphql-spec#599 Replicates graphql/graphql-js@c68acd8
1 parent 6adf99c commit df0f899

File tree

3 files changed

+45
-4
lines changed

3 files changed

+45
-4
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The current version 3.0.0b0 of GraphQL-core is up-to-date
1616
with GraphQL.js version 14.5.0.
1717

1818
All parts of the API are covered by an extensive test suite
19-
of currently 1988 unit tests.
19+
of currently 1989 unit tests.
2020

2121

2222
## Documentation

src/graphql/language/lexer.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ def read_number(
213213
position += 1
214214
char = body[position : position + 1]
215215
position = self.read_digits(position, char)
216+
char = body[position : position + 1]
217+
218+
# Numbers cannot be followed by . or e
219+
if char and char in ".eE":
220+
raise GraphQLSyntaxError(
221+
source,
222+
position,
223+
f"Invalid number, expected digit but got: {print_char(char)}.",
224+
)
225+
216226
return Token(
217227
TokenKind.FLOAT if is_float else TokenKind.INT,
218228
start,

tests/language/test_lexer.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,19 @@ def disallows_uncommon_control_characters():
3333
"\x07", "Cannot contain the invalid character '\\x07'.", (1, 1)
3434
)
3535

36-
# noinspection PyArgumentEqualDefault
3736
def accepts_bom_header():
3837
token = lex_one("\uFEFF foo")
3938
assert token == Token(TokenKind.NAME, 2, 5, 1, 3, None, "foo")
4039

41-
# noinspection PyArgumentEqualDefault
40+
def tracks_line_breaks():
41+
assert lex_one("foo") == Token(TokenKind.NAME, 0, 3, 1, 1, None, "foo")
42+
assert lex_one("\nfoo") == Token(TokenKind.NAME, 1, 4, 2, 1, None, "foo")
43+
assert lex_one("\rfoo") == Token(TokenKind.NAME, 1, 4, 2, 1, None, "foo")
44+
assert lex_one("\r\nfoo") == Token(TokenKind.NAME, 2, 5, 2, 1, None, "foo")
45+
assert lex_one("\n\rfoo") == Token(TokenKind.NAME, 2, 5, 3, 1, None, "foo")
46+
assert lex_one("\r\r\n\nfoo") == Token(TokenKind.NAME, 4, 7, 4, 1, None, "foo")
47+
assert lex_one("\n\n\r\rfoo") == Token(TokenKind.NAME, 4, 7, 5, 1, None, "foo")
48+
4249
def records_line_and_column():
4350
token = lex_one("\n \r\n \r foo\n")
4451
assert token == Token(TokenKind.NAME, 8, 11, 4, 3, None, "foo")
@@ -50,7 +57,6 @@ def can_be_stringified_or_pyutils_inspected():
5057
assert repr(token) == "<Token Name 'foo' 1:1>"
5158
assert inspect(token) == repr(token)
5259

53-
# noinspection PyArgumentEqualDefault
5460
def skips_whitespace_and_comments():
5561
token = lex_one("\n\n foo\n\n\n")
5662
assert token == Token(TokenKind.NAME, 6, 9, 3, 5, None, "foo")
@@ -114,6 +120,7 @@ def lexes_empty_string():
114120

115121
# noinspection PyArgumentEqualDefault
116122
def lexes_strings():
123+
assert lex_one('""') == Token(TokenKind.STRING, 0, 2, 1, 1, None, "")
117124
assert lex_one('"simple"') == Token(
118125
TokenKind.STRING, 0, 8, 1, 1, None, "simple"
119126
)
@@ -135,6 +142,8 @@ def lexes_strings():
135142

136143
def lex_reports_useful_string_errors():
137144
assert_syntax_error('"', "Unterminated string.", (1, 2))
145+
assert_syntax_error('"""', "Unterminated string.", (1, 4))
146+
assert_syntax_error('""""', "Unterminated string.", (1, 5))
138147
assert_syntax_error('"no end quote', "Unterminated string.", (1, 14))
139148
assert_syntax_error(
140149
"'single quotes'",
@@ -175,6 +184,7 @@ def lex_reports_useful_string_errors():
175184

176185
# noinspection PyArgumentEqualDefault
177186
def lexes_block_strings():
187+
assert lex_one('""""""') == Token(TokenKind.BLOCK_STRING, 0, 6, 1, 1, None, "")
178188
assert lex_one('"""simple"""') == Token(
179189
TokenKind.BLOCK_STRING, 0, 12, 1, 1, None, "simple"
180190
)
@@ -276,10 +286,22 @@ def lex_reports_useful_number_errors():
276286
assert_syntax_error(
277287
"00", "Invalid number, unexpected digit after 0: '0'.", (1, 2)
278288
)
289+
assert_syntax_error(
290+
"01", "Invalid number, unexpected digit after 0: '1'.", (1, 2)
291+
)
292+
assert_syntax_error(
293+
"01.23", "Invalid number, unexpected digit after 0: '1'.", (1, 2)
294+
)
279295
assert_syntax_error("+1", "Cannot parse the unexpected character '+'.", (1, 1))
280296
assert_syntax_error(
281297
"1.", "Invalid number, expected digit but got: <EOF>.", (1, 3)
282298
)
299+
assert_syntax_error(
300+
"1e", "Invalid number, expected digit but got: <EOF>.", (1, 3)
301+
)
302+
assert_syntax_error(
303+
"1E", "Invalid number, expected digit but got: <EOF>.", (1, 3)
304+
)
283305
assert_syntax_error(
284306
"1.e1", "Invalid number, expected digit but got: 'e'.", (1, 3)
285307
)
@@ -298,6 +320,15 @@ def lex_reports_useful_number_errors():
298320
assert_syntax_error(
299321
"1.0eA", "Invalid number, expected digit but got: 'A'.", (1, 5)
300322
)
323+
assert_syntax_error(
324+
"1.2e3e", "Invalid number, expected digit but got: 'e'.", (1, 6)
325+
)
326+
assert_syntax_error(
327+
"1.2e3.4", "Invalid number, expected digit but got: '.'.", (1, 6)
328+
)
329+
assert_syntax_error(
330+
"1.23.4", "Invalid number, expected digit but got: '.'.", (1, 5)
331+
)
301332

302333
# noinspection PyArgumentEqualDefault
303334
def lexes_punctuation():

0 commit comments

Comments
 (0)