File: parser_test.py

package info (click to toggle)
sqlfluff 3.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,984 kB
  • sloc: python: 106,138; sql: 34,188; makefile: 52; sh: 8
file content (201 lines) | stat: -rw-r--r-- 6,983 bytes parent folder | download | duplicates (2)
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"""The Test file for Parsers (Matchable Classes)."""

import pytest

from sqlfluff.core.parser import (
    KeywordSegment,
    MultiStringParser,
    RawSegment,
    RegexParser,
    StringParser,
    TypedParser,
)
from sqlfluff.core.parser.context import ParseContext


def test__parser__repr():
    """Test the __repr__ method of the parsers."""
    # For the string parser note the uppercase template.
    assert repr(StringParser("foo", KeywordSegment)) == "<StringParser: 'FOO'>"
    # NOTE: For MultiStringParser we only test with one element here
    # because for more than one, the order is unpredictable.
    assert (
        repr(MultiStringParser(["a"], KeywordSegment)) == "<MultiStringParser: {'A'}>"
    )
    # For the typed & regex parser it's case sensitive (although lowercase
    # by convention).
    assert repr(TypedParser("foo", KeywordSegment)) == "<TypedParser: 'foo'>"
    assert repr(RegexParser(r"fo|o", KeywordSegment)) == "<RegexParser: 'fo|o'>"


class ExampleSegment(RawSegment):
    """A minimal example segment for testing."""

    type = "example"


def test__parser__typedparser__match(generate_test_segments):
    """Test the match method of TypedParser."""
    parser = TypedParser("single_quote", ExampleSegment)
    ctx = ParseContext(dialect=None)
    # NOTE: The second element of the sequence has single quotes
    # and the test fixture will set the type accordingly.
    segments = generate_test_segments(["foo", "'bar'"])

    result1 = parser.match(segments, 0, ctx)
    assert not result1

    result2 = parser.match(segments, 1, ctx)
    assert result2
    assert result2.matched_slice == slice(1, 2)
    assert result2.matched_class is ExampleSegment


def test__parser__typedparser__simple():
    """Test the simple method of TypedParser."""
    parser = TypedParser("single_quote", ExampleSegment)
    ctx = ParseContext(dialect=None)
    assert parser.simple(ctx) == (frozenset(), frozenset(["single_quote"]))


def test__parser__stringparser__match(generate_test_segments):
    """Test the match method of StringParser."""
    parser = StringParser("foo", ExampleSegment, type="test")
    ctx = ParseContext(dialect=None)
    segments = generate_test_segments(["foo", "bar", "foo"])

    result1 = parser.match(segments, 0, ctx)
    assert result1
    assert result1.matched_slice == slice(0, 1)
    assert result1.matched_class is ExampleSegment
    assert result1.segment_kwargs == {"instance_types": ("test",)}

    result2 = parser.match(segments, 1, ctx)
    assert not result2

    result3 = parser.match(segments, 2, ctx)
    assert result3
    assert result3.matched_slice == slice(2, 3)
    assert result3.matched_class is ExampleSegment
    assert result3.segment_kwargs == {"instance_types": ("test",)}


def test__parser__stringparser__simple():
    """Test the simple method of StringParser."""
    parser = StringParser("foo", ExampleSegment)
    ctx = ParseContext(dialect=None)
    assert parser.simple(ctx) == (frozenset(["FOO"]), frozenset())


def test__parser__regexparser__match(generate_test_segments):
    """Test the match method of RegexParser."""
    parser = RegexParser(r"b.r", ExampleSegment)
    ctx = ParseContext(dialect=None)
    segments = generate_test_segments(["foo", "bar", "boo"])

    assert not parser.match(segments, 0, ctx)
    assert not parser.match(segments, 2, ctx)

    result = parser.match(segments, 1, ctx)
    assert result
    assert result.matched_slice == slice(1, 2)
    assert result.matched_class is ExampleSegment


def test__parser__regexparser__simple():
    """Test the simple method of RegexParser."""
    parser = RegexParser(r"b.r", ExampleSegment)
    ctx = ParseContext(dialect=None)
    assert parser.simple(ctx) is None


def test__parser__multistringparser__match(generate_test_segments):
    """Test the match method of MultiStringParser."""
    parser = MultiStringParser(["foo", "bar"], ExampleSegment)
    ctx = ParseContext(dialect=None)
    segments = generate_test_segments(["foo", "fo", "bar", "boo"])

    assert not parser.match(segments, 1, ctx)
    assert not parser.match(segments, 3, ctx)

    result1 = parser.match(segments, 0, ctx)
    assert result1
    assert result1.matched_slice == slice(0, 1)
    assert result1.matched_class is ExampleSegment

    result2 = parser.match(segments, 2, ctx)
    assert result2
    assert result2.matched_slice == slice(2, 3)
    assert result2.matched_class is ExampleSegment


def test__parser__multistringparser__simple():
    """Test the MultiStringParser matchable."""
    parser = MultiStringParser(["foo", "bar"], KeywordSegment)
    ctx = ParseContext(dialect=None)
    assert parser.simple(ctx) == (frozenset(["FOO", "BAR"]), frozenset())


@pytest.mark.parametrize(
    "new_type",
    [None, "bar"],
)
def test__parser__typedparser_rematch(new_type, generate_test_segments):
    """Test that TypedParser allows rematching.

    Because the TypedParser looks for types and then changes the
    type as a result, there is a risk of preventing rematching.
    This is a problem because we use it when checking that fix edits
    haven't broken the parse tree.

    In this example the TypedParser is looking for a "single_quote"
    type segment, but is due to mutate to an Example segment, which
    inherits directly from `RawSegment`. Unless the TypedParser
    steps in, this would apparently present a rematching issue.
    """
    pre_match_types = {
        "single_quote",
        "raw",
        "base",
    }
    post_match_types = {
        # Make sure we got the "example" class
        "example",
        # But we *also* get the "single_quote" class.
        # On the second pass this is the main crux of the test.
        "single_quote",
        "raw",
        "base",
    }
    kwargs = {}
    expected_type = "example"
    if new_type:
        post_match_types.add(new_type)
        kwargs = {"type": new_type}
        expected_type = new_type

    segments = generate_test_segments(["'foo'"])
    # Check types pre-match
    assert segments[0].class_types == pre_match_types

    parser = TypedParser("single_quote", ExampleSegment, **kwargs)
    # Just check that our assumptions about inheritance are right.
    assert not ExampleSegment.class_is_type("single_quote")
    ctx = ParseContext(dialect=None)

    match1 = parser.match(segments, 0, ctx)
    assert match1
    segments1 = match1.apply(segments)
    # Check types post-match 1
    assert segments1[0].class_types == post_match_types
    assert segments1[0].get_type() == expected_type
    assert segments1[0].to_tuple(show_raw=True) == (expected_type, "'foo'")

    # Do a rematch to check it works.
    match = parser.match(segments1, 0, ctx)
    assert match
    # Check types post-match 2
    segments2 = match.apply(segments1)
    assert segments2[0].class_types == post_match_types
    assert segments2[0].get_type() == expected_type
    assert segments2[0].to_tuple(show_raw=True) == (expected_type, "'foo'")