Skip to content

Commit ba18dc9

Browse files
committed
feat(python): support FileImports
1 parent 10fef7a commit ba18dc9

4 files changed

Lines changed: 52 additions & 17 deletions

File tree

lang/collect/collect.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,6 @@ func (c *Collector) Collect(ctx context.Context) error {
195195
if err != nil {
196196
return err
197197
}
198-
// HACK: skip imported symbols (do not expose imported symbols in Python)
199-
// TODO: make this behavior consistent in python and rust (where we have pub use vs use)
200-
if c.Language == uniast.Python && (strings.HasPrefix(content, "from ") || strings.HasPrefix(content, "import ")) {
201-
continue
202-
}
203198
// collect tokens
204199
tokens, err := c.cli.SemanticTokens(ctx, sym.Location)
205200
if err != nil {

lang/python/spec.go

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"fmt"
1919
"os"
2020
"path/filepath"
21+
"regexp"
2122
"strings"
2223

2324
lsp "github.com/cloudwego/abcoder/lang/lsp"
@@ -147,12 +148,6 @@ func (c *PythonSpec) DeclareTokenOfSymbol(sym lsp.DocumentSymbol) int {
147148

148149
func (c *PythonSpec) IsEntityToken(tok lsp.Token) bool {
149150
typ := tok.Type
150-
if strings.HasPrefix(tok.Text, "from ") || strings.HasPrefix(tok.Text, "import ") {
151-
// Python LSP highlights imported symbols as function/types
152-
// We decide that imported symbols are not entities.
153-
// In fact, they ARE, just in a different place.
154-
return false
155-
}
156151
return typ == "function" || typ == "variable" || typ == "property" || typ == "class" || typ == "type"
157152
}
158153

@@ -209,10 +204,6 @@ func (c *PythonSpec) IsMainFunction(sym lsp.DocumentSymbol) bool {
209204
}
210205

211206
func (c *PythonSpec) IsEntitySymbol(sym lsp.DocumentSymbol) bool {
212-
// Same as in IsEntityToken, we do not consider imported symbols as entities.
213-
if strings.HasPrefix(sym.Text, "from ") || strings.HasPrefix(sym.Text, "import ") {
214-
return false
215-
}
216207
typ := sym.Kind
217208
return typ == lsp.SKObject || typ == lsp.SKMethod || typ == lsp.SKFunction || typ == lsp.SKVariable ||
218209
typ == lsp.SKStruct || typ == lsp.SKEnum || typ == lsp.SKTypeParameter || typ == lsp.SKConstant || typ == lsp.SKClass
@@ -390,7 +381,39 @@ func (c *PythonSpec) GetUnloadedSymbol(from lsp.Token, define lsp.Location) (str
390381
panic("TODO")
391382
}
392383

393-
// TODO!
394384
func (c *PythonSpec) FileImports(content []byte) ([]uniast.Import, error) {
395-
return nil, nil
385+
// Reference:
386+
// https://docs.python.org/3/reference/grammar.html
387+
// There are two types of imports in Python:
388+
// import-as: on ONE line
389+
// import xxx as x, yyy as y
390+
// from-import: on ONE line
391+
// from ... import *
392+
// from ... import xxx as x, yyy as y
393+
// or on POSSIBLY MULTIPLE lines, enclosed by parentheses
394+
// from ... import ( xxx, yyy as y ... )
395+
// And imports are simple stmts, so they MUST end with \n.
396+
patterns := []string{
397+
// Matches: import <anything> (on a single line)
398+
`(?m)^import\s+(.*)$`,
399+
// Matches: from <anything> import <anything> (on a single line, without parentheses)
400+
`(?m)^from\s+(.*?)\s+import\s+([^()\n]*)$`,
401+
// Matches: from <anything> import ( <anything> ) where <anything> can span multiple lines
402+
`(?m)^from\s+(.*?)\s+import\s+\(([\s\S]*?)\)$`,
403+
}
404+
405+
res := []uniast.Import{}
406+
for _, p := range patterns {
407+
re, err := regexp.Compile(p)
408+
if err != nil {
409+
return nil, fmt.Errorf("error compiling regex pattern '%s': %w", p, err)
410+
}
411+
matches := re.FindAllStringSubmatch(string(content), -1) // -1 to find all non-overlapping matches
412+
for _, match := range matches {
413+
res = append(res, uniast.Import{
414+
Path: match[0],
415+
})
416+
}
417+
}
418+
return res, nil
396419
}

testdata/pyfileimports/main.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import abc
2+
import base64
3+
4+
from os import path
5+
from sys import (
6+
argv,
7+
exit
8+
)
9+
from collections import defaultdict
10+
11+
from math import (
12+
cos,
13+
sin
14+
15+
16+
)
17+
import copy

0 commit comments

Comments
 (0)