2
2
import logging
3
3
import re
4
4
import sys
5
+ import tokenize
6
+ from io import BytesIO
5
7
from concurrent .futures import ThreadPoolExecutor
6
8
import importmagic
7
9
from pyls import hookimpl , lsp , _utils
@@ -62,6 +64,59 @@ def _get_imports_list(source, index=None):
62
64
return imported
63
65
64
66
67
+ def _tokenize (source ):
68
+ """Tokenize python source code.
69
+ """
70
+ stream = BytesIO (source .encode ())
71
+ return list (tokenize .tokenize (stream .readline ))
72
+
73
+
74
+ def _search_symbol (source , symbol ):
75
+ """Search symbol in python source code.
76
+
77
+ Args:
78
+ source: str object of the source code
79
+ symbol: str object of the symbol to search
80
+
81
+ Returns:
82
+ list of locations where the symbol was found. Each element have the following format
83
+ {
84
+ 'start': {
85
+ 'line': int,
86
+ 'character': int
87
+ },
88
+ 'end': {
89
+ 'line': int,
90
+ 'character': int
91
+ }
92
+ }
93
+ """
94
+ symbol_tokens = _tokenize (symbol )
95
+ source_tokens = _tokenize (source )
96
+
97
+ get_str = lambda token : token [1 ]
98
+ symbol_tokens_str = list (map (get_str , symbol_tokens ))
99
+ source_tokens_str = list (map (get_str , source_tokens ))
100
+
101
+ symbol_len = len (symbol_tokens )
102
+ locations = []
103
+ for i in len (source_tokens ):
104
+ if source_tokens_str [i :i + symbol_len ] == symbol_tokens_str :
105
+ location_range = {
106
+ 'start' : {
107
+ 'line' : source_tokens [2 ][0 ] - 1 ,
108
+ 'character' : source_tokens [2 ][1 ],
109
+ },
110
+ 'end' : {
111
+ 'line' : source_tokens [3 ][0 ] - 1 ,
112
+ 'character' : source_tokens [3 ][1 ],
113
+ }
114
+ }
115
+ locations .append (location_range )
116
+
117
+ return locations
118
+
119
+
65
120
@hookimpl
66
121
def pyls_initialize ():
67
122
_index_cache ['default' ] = None
@@ -107,27 +162,16 @@ def pyls_lint(document):
107
162
108
163
# Annoyingly, we only get the text of an unresolved import, so we'll look for it ourselves
109
164
for unres in unresolved :
110
- for line_no , line in enumerate (document .lines ):
111
- pos = line .find (unres )
112
- if pos < 0 :
113
- continue
114
-
165
+ for location_range in _search_symbol (document .source , unres ):
115
166
diagnostics .append ({
116
167
'source' : SOURCE ,
117
- 'range' : {
118
- 'start' : {'line' : line_no , 'character' : pos },
119
- 'end' : {'line' : line_no , 'character' : pos + len (unres )}
120
- },
168
+ 'range' : location_range ,
121
169
'message' : "Unresolved import '%s'" % unres ,
122
170
'severity' : lsp .DiagnosticSeverity .Hint ,
123
171
})
124
172
125
173
for unref in unreferenced :
126
- for line_no , line in enumerate (document .lines ):
127
- pos = line .find (unref )
128
- if pos < 0 :
129
- continue
130
-
174
+ for location_range in _search_symbol (document .source , unref ):
131
175
# Find out if the unref is an import or a variable/func
132
176
imports = _get_imports_list (document .source )
133
177
if unref in imports :
@@ -137,10 +181,7 @@ def pyls_lint(document):
137
181
138
182
diagnostics .append ({
139
183
'source' : SOURCE ,
140
- 'range' : {
141
- 'start' : {'line' : line_no , 'character' : pos },
142
- 'end' : {'line' : line_no , 'character' : pos + len (unref )}
143
- },
184
+ 'range' : location_range ,
144
185
'message' : message ,
145
186
'severity' : lsp .DiagnosticSeverity .Warning ,
146
187
})
0 commit comments