Skip to content

Commit

Permalink
Merge pull request #204 from galaxyproject/integrate_linting
Browse files Browse the repository at this point in the history
Integrate Galaxy linting
  • Loading branch information
davelopez authored Sep 11, 2022
2 parents 2192938 + 097fc7d commit cce5e48
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 2 deletions.
4 changes: 3 additions & 1 deletion server/galaxyls/services/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from galaxyls.services.tools.generators.command import GalaxyToolCommandSnippetGenerator
from galaxyls.services.tools.generators.tests import GalaxyToolTestSnippetGenerator
from galaxyls.services.tools.iuc import IUCToolParamAttributeSorter
from galaxyls.services.tools.linting import GalaxyToolLinter
from galaxyls.services.tools.macros import MacroDefinitionsProvider
from galaxyls.services.tools.refactor import (
RefactoringService,
Expand Down Expand Up @@ -69,6 +70,7 @@ def __init__(self, server_name: str) -> None:
self.test_discovery_service: TestsDiscoveryService = ToolTestsDiscoveryService()
self.macro_expander = MacroExpanderService()
self.refactoring_service: Optional[RefactoringService] = None
self.linter = GalaxyToolLinter()

def set_workspace(self, workspace: Workspace) -> None:
macro_definitions_provider = MacroDefinitionsProvider(workspace)
Expand All @@ -82,7 +84,7 @@ def get_diagnostics(self, xml_document: XmlDocument) -> List[Diagnostic]:
"""Validates the Galaxy tool XML document and returns a list
of diagnostics if there are any problems.
"""
return self.xsd_service.validate_document(xml_document)
return self.xsd_service.validate_document(xml_document) + self.linter.lint_document(xml_document)

def get_documentation(self, xml_document: XmlDocument, position: Position) -> Optional[Hover]:
"""Gets the documentation about the element at the given position."""
Expand Down
10 changes: 10 additions & 0 deletions server/galaxyls/services/tools/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Optional,
)

from pygls.lsp.types import Diagnostic
from pygls.workspace import Workspace

from galaxyls.services.xml.document import XmlDocument
Expand Down Expand Up @@ -41,3 +42,12 @@ def discover_tests_in_workspace(self, workspace: Workspace) -> List[TestSuiteInf
@abc.abstractmethod
def discover_tests_in_document(self, xml_document: XmlDocument) -> Optional[TestSuiteInfoResult]:
raise NotImplementedError


class ToolLinter(metaclass=abc.ABCMeta):
"""Interface class for linting tool documents."""

@abc.abstractmethod
def lint_document(self, xml_document: XmlDocument) -> List[Diagnostic]:
"""Returns a list of diagnostics with all the issues found in the given document."""
raise NotImplementedError
64 changes: 64 additions & 0 deletions server/galaxyls/services/tools/linting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from typing import List, cast
from lxml import etree

from galaxy.tool_util.lint import (
lint_tool_source_with,
LintContext,
LintLevel,
XMLLintMessageXPath,
)
from galaxy.tool_util.parser import get_tool_source
from galaxy.util import xml_macros
from galaxyls.services.tools.common import ToolLinter
from galaxyls.services.xml.document import XmlDocument
from pygls.lsp.types import Diagnostic, Range, DiagnosticSeverity


class GalaxyToolLinter(ToolLinter):
diagnostics_source = "Galaxy Tool Linter"

def lint_document(self, xml_document: XmlDocument) -> List[Diagnostic]:
""" """
result: List[Diagnostic] = []
if xml_document.is_macros_file:
return result
xml_tree, _ = xml_macros.load_with_references(xml_document.document.path)
tool_source = get_tool_source(xml_tree=xml_tree)
lint_context = LintContext(level=LintLevel.SILENT, lint_message_class=XMLLintMessageXPath)
context = lint_tool_source_with(lint_context, tool_source)
result.extend(
[
self._to_diagnostic(lint_message, xml_tree, xml_document, DiagnosticSeverity.Error)
for lint_message in context.error_messages
]
)
result.extend(
[
self._to_diagnostic(lint_message, xml_tree, xml_document, DiagnosticSeverity.Warning)
for lint_message in context.warn_messages
]
)
return result

def _to_diagnostic(
self,
lint_message: XMLLintMessageXPath,
xml_tree: etree._ElementTree,
xml_document: XmlDocument,
level: DiagnosticSeverity,
) -> Diagnostic:
range = self._get_range_from_xpath(lint_message.xpath, xml_tree, xml_document)
result = Diagnostic(
range=range,
message=lint_message.message,
source=self.diagnostics_source,
severity=level,
)
return result

def _get_range_from_xpath(self, xpath: str, xml_tree: etree._ElementTree, xml_document: XmlDocument) -> Range:
result = None
found = cast(list, xml_tree.xpath(xpath))[0]
if found is not None:
result = xml_document.get_element_name_range_at_line(found.tag, found.sourceline - 1)
return result or xml_document.get_default_range()
21 changes: 21 additions & 0 deletions server/galaxyls/services/xml/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
convert_document_offsets_to_range,
)

DEFAULT_RANGE = Range(
start=Position(line=0, character=0),
end=Position(line=0, character=0),
)


class XmlDocument(XmlSyntaxNode):
"""Represents a parsed XML document.
Expand Down Expand Up @@ -219,3 +224,19 @@ def get_line_indentation(self, line_number: int) -> str:
line_text = self.document.lines[line_number]
indentation = line_text[: len(line_text) - len(line_text.lstrip())]
return indentation

def get_default_range(self) -> Range:
"""Gets the range of the root tag or the first character of the document if there is no root."""
if self.root:
return self.get_element_name_range(self.root) or DEFAULT_RANGE
return DEFAULT_RANGE

def get_element_name_range_at_line(self, name: str, line_number: int) -> Range:
"""Gets the range of the element with the given tag name located at the given line number."""
line_text = self.document.lines[line_number]
start = line_text.index(f"<{name}") + 1
end = start + len(name)
return Range(
start=Position(line=line_number, character=start),
end=Position(line=line_number, character=end),
)
2 changes: 1 addition & 1 deletion server/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pygls==0.11.3
lxml==4.9.1
anytree==2.8.0
galaxy-tool-util==21.9.2
galaxy-tool-util==22.1.2
pydantic==1.8.2

0 comments on commit cce5e48

Please sign in to comment.