xref: /aosp_15_r20/external/bazelbuild-rules_python/gazelle/python/file_parser_test.go (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1// Copyright 2023 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package python
16
17import (
18	"context"
19	"testing"
20
21	"github.com/stretchr/testify/assert"
22)
23
24func TestParseImportStatements(t *testing.T) {
25	t.Parallel()
26	units := []struct {
27		name     string
28		code     string
29		filepath string
30		result   []module
31	}{
32		{
33			name:     "not has import",
34			code:     "a = 1\nb = 2",
35			filepath: "",
36			result:   nil,
37		},
38		{
39			name:     "has import",
40			code:     "import unittest\nimport os.path\nfrom foo.bar import abc.xyz",
41			filepath: "abc.py",
42			result: []module{
43				{
44					Name:       "unittest",
45					LineNumber: 1,
46					Filepath:   "abc.py",
47					From:       "",
48				},
49				{
50					Name:       "os.path",
51					LineNumber: 2,
52					Filepath:   "abc.py",
53					From:       "",
54				},
55				{
56					Name:       "foo.bar.abc.xyz",
57					LineNumber: 3,
58					Filepath:   "abc.py",
59					From:       "foo.bar",
60				},
61			},
62		},
63		{
64			name: "has import in def",
65			code: `def foo():
66	import unittest
67`,
68			filepath: "abc.py",
69			result: []module{
70				{
71					Name:       "unittest",
72					LineNumber: 2,
73					Filepath:   "abc.py",
74					From:       "",
75				},
76			},
77		},
78		{
79			name:     "invalid syntax",
80			code:     "import os\nimport",
81			filepath: "abc.py",
82			result: []module{
83				{
84					Name:       "os",
85					LineNumber: 1,
86					Filepath:   "abc.py",
87					From:       "",
88				},
89			},
90		},
91		{
92			name:     "import as",
93			code:     "import os as b\nfrom foo import bar as c# 123",
94			filepath: "abc.py",
95			result: []module{
96				{
97					Name:       "os",
98					LineNumber: 1,
99					Filepath:   "abc.py",
100					From:       "",
101				},
102				{
103					Name:       "foo.bar",
104					LineNumber: 2,
105					Filepath:   "abc.py",
106					From:       "foo",
107				},
108			},
109		},
110		// align to https://docs.python.org/3/reference/simple_stmts.html#index-34
111		{
112			name: "complex import",
113			code: "from unittest import *\nfrom foo import (bar as c, baz, qux as d)\nfrom . import abc",
114			result: []module{
115				{
116					Name:       "unittest.*",
117					LineNumber: 1,
118					From:       "unittest",
119				},
120				{
121					Name:       "foo.bar",
122					LineNumber: 2,
123					From:       "foo",
124				},
125				{
126					Name:       "foo.baz",
127					LineNumber: 2,
128					From:       "foo",
129				},
130				{
131					Name:       "foo.qux",
132					LineNumber: 2,
133					From:       "foo",
134				},
135			},
136		},
137	}
138	for _, u := range units {
139		t.Run(u.name, func(t *testing.T) {
140			p := NewFileParser()
141			code := []byte(u.code)
142			p.SetCodeAndFile(code, "", u.filepath)
143			output, err := p.Parse(context.Background())
144			assert.NoError(t, err)
145			assert.Equal(t, u.result, output.Modules)
146		})
147	}
148}
149
150func TestParseComments(t *testing.T) {
151	t.Parallel()
152	units := []struct {
153		name   string
154		code   string
155		result []comment
156	}{
157		{
158			name:   "not has comment",
159			code:   "a = 1\nb = 2",
160			result: nil,
161		},
162		{
163			name:   "has comment",
164			code:   "# a = 1\n# b = 2",
165			result: []comment{"# a = 1", "# b = 2"},
166		},
167		{
168			name:   "has comment in if",
169			code:   "if True:\n  # a = 1\n  # b = 2",
170			result: []comment{"# a = 1", "# b = 2"},
171		},
172		{
173			name:   "has comment inline",
174			code:   "import os# 123\nfrom pathlib import Path as b#456",
175			result: []comment{"# 123", "#456"},
176		},
177	}
178	for _, u := range units {
179		t.Run(u.name, func(t *testing.T) {
180			p := NewFileParser()
181			code := []byte(u.code)
182			p.SetCodeAndFile(code, "", "")
183			output, err := p.Parse(context.Background())
184			assert.NoError(t, err)
185			assert.Equal(t, u.result, output.Comments)
186		})
187	}
188}
189
190func TestParseMain(t *testing.T) {
191	t.Parallel()
192	units := []struct {
193		name   string
194		code   string
195		result bool
196	}{
197		{
198			name:   "not has main",
199			code:   "a = 1\nb = 2",
200			result: false,
201		},
202		{
203			name: "has main in function",
204			code: `def foo():
205	if __name__ == "__main__":
206		a = 3
207`,
208			result: false,
209		},
210		{
211			name: "has main",
212			code: `
213import unittest
214
215from lib import main
216
217
218class ExampleTest(unittest.TestCase):
219    def test_main(self):
220        self.assertEqual(
221            "",
222            main([["A", 1], ["B", 2]]),
223        )
224
225
226if __name__ == "__main__":
227    unittest.main()
228`,
229			result: true,
230		},
231	}
232	for _, u := range units {
233		t.Run(u.name, func(t *testing.T) {
234			p := NewFileParser()
235			code := []byte(u.code)
236			p.SetCodeAndFile(code, "", "")
237			output, err := p.Parse(context.Background())
238			assert.NoError(t, err)
239			assert.Equal(t, u.result, output.HasMain)
240		})
241	}
242}
243
244func TestParseFull(t *testing.T) {
245	p := NewFileParser()
246	code := []byte(`from bar import abc`)
247	p.SetCodeAndFile(code, "foo", "a.py")
248	output, err := p.Parse(context.Background())
249	assert.NoError(t, err)
250	assert.Equal(t, ParserOutput{
251		Modules:  []module{{Name: "bar.abc", LineNumber: 1, Filepath: "foo/a.py", From: "bar"}},
252		Comments: nil,
253		HasMain:  false,
254		FileName: "a.py",
255	}, *output)
256}
257