Skip to content

Commit a712a73

Browse files
committed
search symbols in source tokens instead of raw string:
raw string matching lead to false positives (e.g 'os' if found under 'pos' but it's not the actual symbol that we were searching).
1 parent ae63149 commit a712a73

File tree

1 file changed

+59
-18
lines changed

1 file changed

+59
-18
lines changed

pyls/plugins/importmagic_lint.py

+59-18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import logging
33
import re
44
import sys
5+
import tokenize
6+
from io import BytesIO
57
from concurrent.futures import ThreadPoolExecutor
68
import importmagic
79
from pyls import hookimpl, lsp, _utils
@@ -62,6 +64,59 @@ def _get_imports_list(source, index=None):
6264
return imported
6365

6466

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+
65120
@hookimpl
66121
def pyls_initialize():
67122
_index_cache['default'] = None
@@ -107,27 +162,16 @@ def pyls_lint(document):
107162

108163
# Annoyingly, we only get the text of an unresolved import, so we'll look for it ourselves
109164
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):
115166
diagnostics.append({
116167
'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,
121169
'message': "Unresolved import '%s'" % unres,
122170
'severity': lsp.DiagnosticSeverity.Hint,
123171
})
124172

125173
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):
131175
# Find out if the unref is an import or a variable/func
132176
imports = _get_imports_list(document.source)
133177
if unref in imports:
@@ -137,10 +181,7 @@ def pyls_lint(document):
137181

138182
diagnostics.append({
139183
'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,
144185
'message': message,
145186
'severity': lsp.DiagnosticSeverity.Warning,
146187
})

0 commit comments

Comments
 (0)