refactor(al): move extract_al_display_name into ALLanguageServer class

Move the AL name extraction pattern and function into the class as a
static method since they are only used by the language server.

Addresses PR #960 review comment.
This commit is contained in:
Torben Leth committed 2026-01-20 01:15:48 +01:00
1 parent b9f82774ce
commit d05d8b765f
2 files changed
+54 -54

No files matched your search

@@ -24,45 +24,6 @@ from solidlsp.settings import SolidLSPSettings
log = logging.getLogger(__name__)
# Regex pattern to match AL object names like:
# - 'Table 50000 "TEST Customer"' -> captures 'TEST Customer'
# - 'Codeunit 50000 CustomerMgt' -> captures 'CustomerMgt'
# - 'Interface IPaymentProcessor' -> captures 'IPaymentProcessor'
# - 'Enum 50000 CustomerType' -> captures 'CustomerType'
# Pattern: <ObjectType> [<ID>] (<QuotedName>|<UnquotedName>)
_AL_OBJECT_NAME_PATTERN = re.compile(
r"^(?:Table|Page|Codeunit|Enum|Interface|Report|Query|XMLPort|PermissionSet|"
r"PermissionSetExtension|Profile|PageExtension|TableExtension|EnumExtension|"
r"PageCustomization|ReportExtension|ControlAddin|DotNetPackage)" # Object type
r"(?:\s+\d+)?" # Optional object ID
r"\s+" # Required space before name
r'(?:"([^"]+)"|(\S+))$' # Quoted name (group 1) or unquoted identifier (group 2)
)
def extract_al_display_name(full_name: str) -> str:
"""
Extract the display name from an AL symbol's full name.
AL Language Server returns symbol names in format:
- 'Table 50000 "TEST Customer"' -> 'TEST Customer'
- 'Codeunit 50000 CustomerMgt' -> 'CustomerMgt'
- 'Interface IPaymentProcessor' -> 'IPaymentProcessor'
- 'fields' -> 'fields' (non-AL-object symbols pass through unchanged)
Args:
full_name: The full symbol name as returned by AL Language Server
Returns:
The extracted display name for matching, or the original name if not an AL object
"""
match = _AL_OBJECT_NAME_PATTERN.match(full_name)
if match:
# Return quoted name (group 1) or unquoted name (group 2)
return match.group(1) or match.group(2) or full_name
return full_name
class ALLanguageServer(SolidLanguageServer):
"""
@@ -79,6 +40,45 @@ class ALLanguageServer(SolidLanguageServer):
- File opening requirement before symbol retrieval
"""
# Regex pattern to match AL object names like:
# - 'Table 50000 "TEST Customer"' -> captures 'TEST Customer'
# - 'Codeunit 50000 CustomerMgt' -> captures 'CustomerMgt'
# - 'Interface IPaymentProcessor' -> captures 'IPaymentProcessor'
# - 'Enum 50000 CustomerType' -> captures 'CustomerType'
# Pattern: <ObjectType> [<ID>] (<QuotedName>|<UnquotedName>)
_AL_OBJECT_NAME_PATTERN = re.compile(
r"^(?:Table|Page|Codeunit|Enum|Interface|Report|Query|XMLPort|PermissionSet|"
r"PermissionSetExtension|Profile|PageExtension|TableExtension|EnumExtension|"
r"PageCustomization|ReportExtension|ControlAddin|DotNetPackage)" # Object type
r"(?:\s+\d+)?" # Optional object ID
r"\s+" # Required space before name
r'(?:"([^"]+)"|(\S+))$' # Quoted name (group 1) or unquoted identifier (group 2)
)
@staticmethod
def _extract_al_display_name(full_name: str) -> str:
"""
Extract the display name from an AL symbol's full name.
AL Language Server returns symbol names in format:
- 'Table 50000 "TEST Customer"' -> 'TEST Customer'
- 'Codeunit 50000 CustomerMgt' -> 'CustomerMgt'
- 'Interface IPaymentProcessor' -> 'IPaymentProcessor'
- 'fields' -> 'fields' (non-AL-object symbols pass through unchanged)
Args:
full_name: The full symbol name as returned by AL Language Server
Returns:
The extracted display name for matching, or the original name if not an AL object
"""
match = ALLanguageServer._AL_OBJECT_NAME_PATTERN.match(full_name)
if match:
# Return quoted name (group 1) or unquoted name (group 2)
return match.group(1) or match.group(2) or full_name
return full_name
def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
"""
Initialize the AL Language Server.
@@ -1025,7 +1025,7 @@ class ALLanguageServer(SolidLanguageServer):
# Normalize names by stripping AL object metadata, storing originals for hover
def normalize_name(symbol: UnifiedSymbolInformation) -> None:
original_name = symbol["name"]
normalized_name = extract_al_display_name(original_name)
normalized_name = self._extract_al_display_name(original_name)
# Store original name if it was normalized (for hover injection)
# Only store if we have valid position data to avoid false matches at (0, 0)
+14 -14
View File
@@ -4,7 +4,7 @@ import pytest
from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer
from solidlsp.language_servers.al_language_server import extract_al_display_name
from solidlsp.language_servers.al_language_server import ALLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.conftest import language_tests_enabled
@@ -13,49 +13,49 @@ pytestmark = [pytest.mark.al, pytest.mark.skipif(not language_tests_enabled(Lang
class TestExtractALDisplayName:
"""Tests for the extract_al_display_name function."""
"""Tests for the ALLanguageServer._extract_al_display_name method."""
def test_table_with_quoted_name(self) -> None:
"""Test extraction from Table with quoted name."""
assert extract_al_display_name('Table 50000 "TEST Customer"') == "TEST Customer"
assert ALLanguageServer._extract_al_display_name('Table 50000 "TEST Customer"') == "TEST Customer"
def test_page_with_quoted_name(self) -> None:
"""Test extraction from Page with quoted name."""
assert extract_al_display_name('Page 50001 "TEST Customer Card"') == "TEST Customer Card"
assert ALLanguageServer._extract_al_display_name('Page 50001 "TEST Customer Card"') == "TEST Customer Card"
def test_codeunit_unquoted(self) -> None:
"""Test extraction from Codeunit with unquoted name."""
assert extract_al_display_name("Codeunit 50000 CustomerMgt") == "CustomerMgt"
assert ALLanguageServer._extract_al_display_name("Codeunit 50000 CustomerMgt") == "CustomerMgt"
def test_enum_unquoted(self) -> None:
"""Test extraction from Enum with unquoted name."""
assert extract_al_display_name("Enum 50000 CustomerType") == "CustomerType"
assert ALLanguageServer._extract_al_display_name("Enum 50000 CustomerType") == "CustomerType"
def test_interface_no_id(self) -> None:
"""Test extraction from Interface (no ID)."""
assert extract_al_display_name("Interface IPaymentProcessor") == "IPaymentProcessor"
assert ALLanguageServer._extract_al_display_name("Interface IPaymentProcessor") == "IPaymentProcessor"
def test_table_extension(self) -> None:
"""Test extraction from TableExtension."""
assert extract_al_display_name('TableExtension 50000 "Ext Customer"') == "Ext Customer"
assert ALLanguageServer._extract_al_display_name('TableExtension 50000 "Ext Customer"') == "Ext Customer"
def test_page_extension(self) -> None:
"""Test extraction from PageExtension."""
assert extract_al_display_name('PageExtension 50000 "My Page Ext"') == "My Page Ext"
assert ALLanguageServer._extract_al_display_name('PageExtension 50000 "My Page Ext"') == "My Page Ext"
def test_non_al_object_unchanged(self) -> None:
"""Test that non-AL-object names pass through unchanged."""
assert extract_al_display_name("fields") == "fields"
assert extract_al_display_name("CreateCustomer") == "CreateCustomer"
assert extract_al_display_name("Name") == "Name"
assert ALLanguageServer._extract_al_display_name("fields") == "fields"
assert ALLanguageServer._extract_al_display_name("CreateCustomer") == "CreateCustomer"
assert ALLanguageServer._extract_al_display_name("Name") == "Name"
def test_report_with_quoted_name(self) -> None:
"""Test extraction from Report."""
assert extract_al_display_name('Report 50000 "Sales Invoice"') == "Sales Invoice"
assert ALLanguageServer._extract_al_display_name('Report 50000 "Sales Invoice"') == "Sales Invoice"
def test_query_unquoted(self) -> None:
"""Test extraction from Query."""
assert extract_al_display_name("Query 50000 CustomerQuery") == "CustomerQuery"
assert ALLanguageServer._extract_al_display_name("Query 50000 CustomerQuery") == "CustomerQuery"
@pytest.mark.al