Skip to content

Commit e0cba4a

Browse files
committed
Added support for LSP hover and definitions
Other changes: * Fixed some issues with testing on Windows * Removing sources from the DB if they're not accessible when parsing * Renamed hdl_checker_base -> base_server
1 parent 1e51a1d commit e0cba4a

38 files changed

+1176
-621
lines changed

.ci/scripts/docker_entry_point.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ln -s /builders "/home/$USERNAME/builders"
3434

3535
su -l "$USERNAME" -c " \
3636
cd /hdl_checker && \
37-
tox ${TOX_ARGS[*]}; \
37+
tox ${TOX_ARGS[*]} && \
3838
coverage combine && \
3939
coverage xml && \
4040
coverage report && \
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
entity entity_a is
3+
end entity;
4+
5+
entity entity_a is
6+
end entity;
7+
8+
entity entity_b is
9+
end entity;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
use work.entity_a;
3+
4+
use work.entity_b;
5+
6+
entity entity_c is
7+
end entity;
8+
9+
architecture entity_c of entity_c is
10+
begin
11+
end entity_c;
12+

.ci/test_support/test_project/config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"basic_library/package_with_constants.vhd",
77
"basic_library/package_with_functions.vhd",
88
"basic_library/very_common_pkg.vhd",
9+
"basic_library/two_entities_one_file.vhd",
10+
"basic_library/use_entity_a_and_b.vhd",
911
"verilog/parity.sv",
1012
"verilog/parity.v"
1113
]

.ci/test_support/test_project/ghdl.prj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ vhdl basic_library basic_library/clock_divider.vhd
77
vhdl another_library another_library/foo.vhd
88
vhdl basic_library basic_library/package_with_functions.vhd
99
vhdl basic_library basic_library/clk_en_generator.vhd
10-
10+
vhdl basic_library basic_library/two_entities_one_file.vhd
11+
vhdl basic_library basic_library/use_entity_a_and_b.vhd

.ci/test_support/test_project/msim.prj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ vhdl basic_library basic_library/package_with_functions.vhd
88
vhdl basic_library basic_library/clk_en_generator.vhd
99
verilog verilog verilog/parity.v
1010
systemverilog verilog verilog/parity.sv
11+
vhdl basic_library basic_library/two_entities_one_file.vhd
12+
vhdl basic_library basic_library/use_entity_a_and_b.vhd

.ci/test_support/test_project/vimhdl.prj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ vhdl basic_library basic_library/package_with_functions.vhd
66
vhdl basic_library basic_library/clk_en_generator.vhd
77
verilog verilog verilog/parity.v
88
systemverilog verilog verilog/parity.sv
9+
vhdl basic_library basic_library/two_entities_one_file.vhd
10+
vhdl basic_library basic_library/use_entity_a_and_b.vhd

.ci/test_support/test_project/xvhdl.prj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ vhdl basic_library basic_library/clock_divider.vhd
77
vhdl another_library another_library/foo.vhd
88
vhdl basic_library basic_library/package_with_functions.vhd
99
vhdl basic_library basic_library/clk_en_generator.vhd
10+
vhdl basic_library basic_library/two_entities_one_file.vhd
11+
vhdl basic_library basic_library/use_entity_a_and_b.vhd

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,5 @@ target/
6666
tags
6767
# .coverage
6868
# htmlcov/
69-
source.vhd
70-
.ci/test_support/grlib/
7169
.mypy_cache
70+
.hdl_checker/

hdl_checker/hdl_checker_base.py renamed to hdl_checker/base_server.py

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import traceback
2626
from collections import namedtuple
2727
from multiprocessing.pool import ThreadPool
28-
from threading import RLock
2928
from pprint import pformat
30-
from typing import Any, AnyStr, Dict, Iterable, Optional, Set, Union
29+
from threading import RLock
30+
from typing import Any, AnyStr, Dict, Iterable, Optional, Set, Tuple, Union
3131

3232
import six
3333

@@ -41,6 +41,7 @@
4141
from hdl_checker.database import Database
4242
from hdl_checker.diagnostics import CheckerDiagnostic, DiagType, PathNotInProjectFile
4343
from hdl_checker.parsers.config_parser import ConfigParser
44+
from hdl_checker.parsers.elements.dependency_spec import DependencySpec
4445
from hdl_checker.parsers.elements.identifier import Identifier
4546
from hdl_checker.path import Path, TemporaryPath
4647
from hdl_checker.serialization import StateEncoder, jsonObjectHook
@@ -54,6 +55,11 @@
5455
)
5556
from hdl_checker.utils import removeDirIfExists, removeIfExists, toBytes
5657

58+
try:
59+
from functools import lru_cache
60+
except ImportError:
61+
from backports.functools_lru_cache import lru_cache # type: ignore
62+
5763
_logger = logging.getLogger(__name__)
5864

5965
if six.PY2:
@@ -85,7 +91,7 @@ def __init__(self, root_dir): # type: (Path) -> None
8591
self._lock = RLock()
8692
self.config_file = None # type: Optional[WatchedFile]
8793

88-
self.database = Database()
94+
self._database = Database()
8995
self._builder = Fallback(self.work_dir, self.database)
9096

9197
if not p.exists(str(self.work_dir)):
@@ -94,6 +100,13 @@ def __init__(self, root_dir): # type: (Path) -> None
94100
self._recoverCacheIfPossible()
95101
self._saveCache()
96102

103+
# Use this to know which methods should be cache
104+
self._cached_methods = {
105+
getattr(self, x)
106+
for x in dir(self)
107+
if hasattr(getattr(self, x), "cache_clear")
108+
}
109+
97110
@property
98111
def builder(self):
99112
"""
@@ -103,6 +116,24 @@ def builder(self):
103116
self._updateConfigIfNeeded()
104117
return self._builder
105118

119+
@property
120+
def database(self):
121+
"""
122+
Parses the config file if it has been set and returns the builder in
123+
use
124+
"""
125+
self._updateConfigIfNeeded()
126+
return self._database
127+
128+
def __hash__(self):
129+
# Just to allow lru_cache
130+
return hash(0)
131+
132+
def _clearLruCaches(self):
133+
"Clear caches from lru_caches"
134+
for meth in self._cached_methods:
135+
meth.cache_clear()
136+
106137
def setConfig(self, filename):
107138
# type: (Union[Path, str]) -> None
108139
"""
@@ -190,11 +221,10 @@ def configure(self, config):
190221

191222
# Add the flags from the root config file last, it should overwrite
192223
# values set by the included files
193-
# self.database.addSources(config.pop("sources", ()), config_root_path)
194-
# if config
195-
assert not config, "Some configuration elements weren't used:\n{}".format(
196-
pformat(config)
197-
)
224+
if config:
225+
_logger.warning(
226+
"Some configuration elements weren't used:\n%s", pformat(config)
227+
)
198228

199229
def _getCacheFilename(self):
200230
# type: () -> Path
@@ -227,7 +257,7 @@ def _setState(self, state):
227257
"""
228258
Serializer load implementation
229259
"""
230-
self.database = state.pop("database")
260+
self._database = state.pop("database")
231261
self._builder = state.pop("builder", Fallback)
232262
config_file = state.pop("config_file", None)
233263
if config_file is None:
@@ -284,10 +314,11 @@ def clean(self):
284314
removeDirIfExists(str(self.work_dir))
285315

286316
del self._builder
287-
del self.database
317+
del self._database
288318

289-
self.database = Database()
290-
self._builder = Fallback(self.work_dir, self.database)
319+
database = Database()
320+
self._database = database
321+
self._builder = Fallback(self.work_dir, database)
291322

292323
@abc.abstractmethod
293324
def _handleUiInfo(self, message): # type: (AnyStr) -> None
@@ -401,7 +432,7 @@ def getMessagesByPath(self, path):
401432
Returns the messages for the given path, including messages
402433
from the configured builder (if available) and static checks
403434
"""
404-
self._updateConfigIfNeeded()
435+
self._clearLruCaches()
405436

406437
path = Path(path, self.root_dir)
407438

@@ -451,9 +482,9 @@ def getMessagesWithText(self, path, content):
451482
Dumps content to a temprary file and replaces the temporary file name
452483
for path on the diagnostics received
453484
"""
485+
self._clearLruCaches()
454486
with self._lock:
455487
_logger.info("Getting messages for '%s' with content", path)
456-
self._updateConfigIfNeeded()
457488

458489
ext = path.name.split(".")[-1]
459490
temporary_file = tempfile.NamedTemporaryFile(suffix="." + ext, delete=False)
@@ -463,7 +494,6 @@ def getMessagesWithText(self, path, content):
463494
temporary_file.file.write(toBytes(content)) # type: ignore
464495
temporary_file.close()
465496

466-
467497
# If the reference path was added to the database, add the
468498
# temporary file with the same attributes
469499
if path in self.database.paths:
@@ -494,3 +524,21 @@ def getMessagesWithText(self, path, content):
494524
diags.add(PathNotInProjectFile(path))
495525

496526
return diags
527+
528+
@lru_cache()
529+
def resolveDependencyToPath(self, dependency):
530+
# type: (DependencySpec) -> Optional[Tuple[Path, Identifier]]
531+
"""
532+
Retrieves the build sequence for the dependency's owner and extracts
533+
the path that implements a design unit whose names match the
534+
dependency.
535+
"""
536+
for library, path in self.database.getBuildSequence(
537+
dependency.owner, self.builder.builtin_libraries
538+
):
539+
if dependency.name in (
540+
x.name for x in self.database.getDesignUnitsByPath(path)
541+
):
542+
return path, library
543+
544+
return None

hdl_checker/builder_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from .builders.xvhdl import XVHDL
3737

3838
from hdl_checker.parsers.elements.identifier import Identifier
39-
from hdl_checker.path import Path # pylint: disable=unused-import
39+
from hdl_checker.path import Path
4040
from hdl_checker.types import BuildFlags, FileType
4141
from hdl_checker.utils import removeDirIfExists
4242

hdl_checker/builders/fallback.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,28 @@
1616
# along with HDL Checker. If not, see <http://www.gnu.org/licenses/>.
1717
"Fallback builder for cases where no builder is found"
1818

19-
from hdl_checker import types as t
2019
from hdl_checker.builders.base_builder import BaseBuilder
20+
from hdl_checker.types import FileType
21+
2122

2223
class Fallback(BaseBuilder):
2324
"Dummy fallback builder"
2425

2526
# Implementation of abstract class properties
26-
builder_name = 'fallback'
27-
file_types = {t.FileType.vhdl, t.FileType.verilog, t.FileType.systemverilog}
27+
builder_name = "fallback"
28+
file_types = {FileType.vhdl, FileType.verilog, FileType.systemverilog}
2829

2930
def __init__(self, *args, **kwargs):
3031
# type: (...) -> None
31-
self._version = '<undefined>'
32+
self._version = "<undefined>"
3233
super(Fallback, self).__init__(*args, **kwargs)
3334

3435
# Since Fallback._buildSource returns nothing,
3536
# Fallback._makeRecords is never called
36-
def _makeRecords(self, _): # pragma: no cover
37+
def _makeRecords(self, _): # pragma: no cover
3738
return []
3839

39-
def _shouldIgnoreLine(self, line): # pragma: no cover
40+
def _shouldIgnoreLine(self, line): # pragma: no cover
4041
return True
4142

4243
def _checkEnvironment(self):
@@ -46,8 +47,8 @@ def _checkEnvironment(self):
4647
def isAvailable():
4748
return True
4849

49-
def _buildSource(self, path, library, flags=None): # pragma: no cover
50+
def _buildSource(self, path, library, flags=None): # pragma: no cover
5051
return [], []
5152

52-
def _createLibrary(self, library): # pragma: no cover
53+
def _createLibrary(self, library): # pragma: no cover
5354
pass

hdl_checker/config_generators/base_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from hdl_checker.builder_utils import AnyValidBuilder
2424
from hdl_checker.path import Path
25-
from hdl_checker.types import BuildFlags, BuildFlagScope, FileType
25+
from hdl_checker.types import BuildFlags, FileType
2626

2727
_SOURCE_EXTENSIONS = "vhdl", "sv", "v"
2828
_HEADER_EXTENSIONS = "vh", "svh"

hdl_checker/config_generators/simple_finder.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222

2323
from .base_generator import BaseGenerator
2424

25+
from hdl_checker.exceptions import UnknownTypeExtension
2526
from hdl_checker.path import Path
26-
from hdl_checker.types import FileType, UnknownTypeExtension
27+
from hdl_checker.types import FileType
2728
from hdl_checker.utils import isFileReadable
2829

2930
_SOURCE_EXTENSIONS = "vhdl", "sv", "v"

hdl_checker/database.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,23 @@
3535
Union,
3636
)
3737

38-
from hdl_checker.diagnostics import (
38+
from hdl_checker.diagnostics import ( # pylint: disable=unused-import
3939
CheckerDiagnostic,
4040
DependencyNotUnique,
4141
PathNotInProjectFile,
4242
)
4343
from hdl_checker.parser_utils import flattenConfig, getSourceParserFromPath
4444
from hdl_checker.parsers.elements.dependency_spec import DependencySpec
45-
from hdl_checker.parsers.elements.design_unit import tAnyDesignUnit
45+
from hdl_checker.parsers.elements.design_unit import ( # pylint: disable=unused-import,
46+
tAnyDesignUnit,
47+
)
4648
from hdl_checker.parsers.elements.identifier import Identifier
47-
from hdl_checker.path import Path, TemporaryPath
48-
from hdl_checker.types import BuildFlags, BuildFlagScope, FileType
49+
from hdl_checker.path import Path, TemporaryPath # pylint: disable=unused-import
50+
from hdl_checker.types import ( # pylint: disable=unused-import
51+
BuildFlags,
52+
BuildFlagScope,
53+
FileType,
54+
)
4955
from hdl_checker.utils import HashableByKey, getMostCommonItem, isFileReadable
5056

5157
try:
@@ -61,7 +67,7 @@
6167
LibraryUnitTuple = Tuple[UnresolvedLibrary, Identifier]
6268

6369

64-
class Database(HashableByKey):
70+
class Database(HashableByKey): # pylint: disable=too-many-instance-attributes
6571
"Stores info on and provides operations for a project file set"
6672

6773
def __init__(self): # type: () -> None
@@ -85,6 +91,7 @@ def __init__(self): # type: () -> None
8591

8692
@property
8793
def __hash_key__(self):
94+
# Just to allow lru_cache
8895
return 0
8996

9097
@property
@@ -261,8 +268,8 @@ def __jsonEncode__(self):
261268
def __jsonDecode__(cls, state):
262269
# pylint: disable=protected-access
263270
obj = cls()
264-
obj._design_units = {x for x in state.pop("design_units")}
265-
obj._inferred_libraries = {x for x in state.pop("inferred_libraries")}
271+
obj._design_units = set(state.pop("design_units"))
272+
obj._inferred_libraries = set(state.pop("inferred_libraries"))
266273
for info in state.pop("sources"):
267274
path = info.pop("path")
268275
obj._paths.add(path)
@@ -278,8 +285,8 @@ def __jsonDecode__(cls, state):
278285
obj._flags_map[path][BuildFlagScope.dependencies] = tuple(
279286
info.pop("flags", {}).pop(BuildFlagScope.dependencies.value, ())
280287
)
281-
obj._dependencies_map[path] = {x for x in info.pop("dependencies")}
282-
obj._diags[path] = {x for x in info.pop("diags")}
288+
obj._dependencies_map[path] = set(info.pop("dependencies"))
289+
obj._diags[path] = set(info.pop("diags"))
283290
# pylint: enable=protected-access
284291

285292
return obj
@@ -375,6 +382,7 @@ def _parseSourceIfNeeded(self, path):
375382
"""
376383
if not isFileReadable(path):
377384
_logger.warning("Won't parse file that's not readable %s", repr(path))
385+
self.removeSource(path)
378386
return
379387

380388
# Sources will get parsed on demand

0 commit comments

Comments
 (0)