mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1147 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1147 lines
		
	
	
		
			46 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python3
 | 
						|
# pragma pylint: disable=too-many-lines
 | 
						|
import argparse
 | 
						|
import fnmatch
 | 
						|
import json
 | 
						|
import os
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
import traceback
 | 
						|
 | 
						|
from typing import Any, List, Optional, Tuple, Union
 | 
						|
 | 
						|
import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows.
 | 
						|
from deepdiff import DeepDiff
 | 
						|
 | 
						|
# {{{ JsonRpcProcess
 | 
						|
class BadHeader(Exception):
 | 
						|
    def __init__(self, msg: str):
 | 
						|
        super().__init__("Bad header: " + msg)
 | 
						|
 | 
						|
class JsonRpcProcess:
 | 
						|
    exe_path: str
 | 
						|
    exe_args: List[str]
 | 
						|
    process: subprocess.Popen
 | 
						|
    trace_io: bool
 | 
						|
 | 
						|
    def __init__(self, exe_path: str, exe_args: List[str], trace_io: bool = True):
 | 
						|
        self.exe_path = exe_path
 | 
						|
        self.exe_args = exe_args
 | 
						|
        self.trace_io = trace_io
 | 
						|
 | 
						|
    def __enter__(self):
 | 
						|
        self.process = subprocess.Popen(
 | 
						|
            [self.exe_path, *self.exe_args],
 | 
						|
            stdin=subprocess.PIPE,
 | 
						|
            stdout=subprocess.PIPE,
 | 
						|
            stderr=subprocess.PIPE
 | 
						|
        )
 | 
						|
        return self
 | 
						|
 | 
						|
    def __exit__(self, exception_type, exception_value, traceback) -> None:
 | 
						|
        self.process.kill()
 | 
						|
        self.process.wait(timeout=2.0)
 | 
						|
 | 
						|
    def trace(self, topic: str, message: str) -> None:
 | 
						|
        if self.trace_io:
 | 
						|
            print(f"{SGR_TRACE}{topic}:{SGR_RESET} {message}")
 | 
						|
 | 
						|
    def receive_message(self) -> Union[None, dict]:
 | 
						|
        # Note, we should make use of timeout to avoid infinite blocking if nothing is received.
 | 
						|
        CONTENT_LENGTH_HEADER = "Content-Length: "
 | 
						|
        CONTENT_TYPE_HEADER = "Content-Type: "
 | 
						|
        if self.process.stdout is None:
 | 
						|
            return None
 | 
						|
        message_size = None
 | 
						|
        while True:
 | 
						|
            # read header
 | 
						|
            line = self.process.stdout.readline()
 | 
						|
            if line == '':
 | 
						|
                # server quit
 | 
						|
                return None
 | 
						|
            line = line.decode("utf-8")
 | 
						|
            if not line.endswith("\r\n"):
 | 
						|
                raise BadHeader("missing newline")
 | 
						|
            # remove the "\r\n"
 | 
						|
            line = line[:-2]
 | 
						|
            if line == '':
 | 
						|
                break # done with the headers
 | 
						|
            if line.startswith(CONTENT_LENGTH_HEADER):
 | 
						|
                line = line[len(CONTENT_LENGTH_HEADER):]
 | 
						|
                if not line.isdigit():
 | 
						|
                    raise BadHeader("size is not int")
 | 
						|
                message_size = int(line)
 | 
						|
            elif line.startswith(CONTENT_TYPE_HEADER):
 | 
						|
                # nothing todo with type for now.
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                raise BadHeader("unknown header")
 | 
						|
        if message_size is None:
 | 
						|
            raise BadHeader("missing size")
 | 
						|
        rpc_message = self.process.stdout.read(message_size).decode("utf-8")
 | 
						|
        json_object = json.loads(rpc_message)
 | 
						|
        self.trace('receive_message', json.dumps(json_object, indent=4, sort_keys=True))
 | 
						|
        return json_object
 | 
						|
 | 
						|
    def send_message(self, method_name: str, params: Optional[dict]) -> None:
 | 
						|
        if self.process.stdin is None:
 | 
						|
            return
 | 
						|
        message = {
 | 
						|
            'jsonrpc': '2.0',
 | 
						|
            'method': method_name,
 | 
						|
            'params': params
 | 
						|
        }
 | 
						|
        json_string = json.dumps(obj=message)
 | 
						|
        rpc_message = f"Content-Length: {len(json_string)}\r\n\r\n{json_string}"
 | 
						|
        self.trace(f'send_message ({method_name})', json.dumps(message, indent=4, sort_keys=True))
 | 
						|
        self.process.stdin.write(rpc_message.encode("utf-8"))
 | 
						|
        self.process.stdin.flush()
 | 
						|
 | 
						|
    def call_method(self, method_name: str, params: Optional[dict]) -> Any:
 | 
						|
        self.send_message(method_name, params)
 | 
						|
        return self.receive_message()
 | 
						|
 | 
						|
    def send_notification(self, name: str, params: Optional[dict] = None) -> None:
 | 
						|
        self.send_message(name, params)
 | 
						|
 | 
						|
# }}}
 | 
						|
 | 
						|
SGR_RESET = '\033[m'
 | 
						|
SGR_TRACE = '\033[1;36m'
 | 
						|
SGR_NOTICE = '\033[1;35m'
 | 
						|
SGR_TEST_BEGIN = '\033[1;33m'
 | 
						|
SGR_ASSERT_BEGIN = '\033[1;34m'
 | 
						|
SGR_STATUS_OKAY = '\033[1;32m'
 | 
						|
SGR_STATUS_FAIL = '\033[1;31m'
 | 
						|
 | 
						|
class ExpectationFailed(Exception):
 | 
						|
    def __init__(self, actual, expected):
 | 
						|
        self.actual = actual
 | 
						|
        self.expected = expected
 | 
						|
        diff = DeepDiff(actual, expected)
 | 
						|
        super().__init__(
 | 
						|
            f"Expectation failed.\n\tExpected {expected}\n\tbut got {actual}.\n\t{diff}"
 | 
						|
        )
 | 
						|
 | 
						|
def create_cli_parser() -> argparse.ArgumentParser:
 | 
						|
    parser = argparse.ArgumentParser(description="Solidity LSP Test suite")
 | 
						|
    parser.set_defaults(trace_io=False)
 | 
						|
    parser.add_argument(
 | 
						|
        "-T, --trace-io",
 | 
						|
        dest="trace_io",
 | 
						|
        action="store_true",
 | 
						|
        help="Be more verbose by also printing assertions."
 | 
						|
    )
 | 
						|
    parser.set_defaults(print_assertions=False)
 | 
						|
    parser.add_argument(
 | 
						|
        "-v, --print-assertions",
 | 
						|
        dest="print_assertions",
 | 
						|
        action="store_true",
 | 
						|
        help="Be more verbose by also printing assertions."
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "-t, --test-pattern",
 | 
						|
        dest="test_pattern",
 | 
						|
        type=str,
 | 
						|
        default="*",
 | 
						|
        help="Filters all available tests by matching against this test pattern (using globbing)",
 | 
						|
        nargs="?"
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "solc_path",
 | 
						|
        type=str,
 | 
						|
        default="solc",
 | 
						|
        help="Path to solc binary to test against",
 | 
						|
        nargs="?"
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "project_root_dir",
 | 
						|
        type=str,
 | 
						|
        default=f"{os.path.dirname(os.path.realpath(__file__))}/..",
 | 
						|
        help="Path to Solidity project's root directory (must be fully qualified).",
 | 
						|
        nargs="?"
 | 
						|
    )
 | 
						|
    return parser
 | 
						|
 | 
						|
class Counter:
 | 
						|
    total: int = 0
 | 
						|
    passed: int = 0
 | 
						|
    failed: int = 0
 | 
						|
 | 
						|
class SolidityLSPTestSuite: # {{{
 | 
						|
    test_counter = Counter()
 | 
						|
    assertion_counter = Counter()
 | 
						|
    print_assertions: bool = False
 | 
						|
    trace_io: bool = False
 | 
						|
    test_pattern: str
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        colorama.init()
 | 
						|
        args = create_cli_parser().parse_args()
 | 
						|
        self.solc_path = args.solc_path
 | 
						|
        self.project_root_dir = os.path.realpath(args.project_root_dir) + "/test/libsolidity/lsp"
 | 
						|
        self.project_root_uri = "file://" + self.project_root_dir
 | 
						|
        self.print_assertions = args.print_assertions
 | 
						|
        self.trace_io = args.trace_io
 | 
						|
        self.test_pattern = args.test_pattern
 | 
						|
 | 
						|
        print(f"{SGR_NOTICE}test pattern: {self.test_pattern}{SGR_RESET}")
 | 
						|
 | 
						|
    def main(self) -> int:
 | 
						|
        """
 | 
						|
        Runs all test cases.
 | 
						|
        Returns 0 on success and the number of failing assertions (capped to 127) otherwise.
 | 
						|
        """
 | 
						|
        all_tests = sorted([
 | 
						|
            str(name)[5:]
 | 
						|
            for name in dir(SolidityLSPTestSuite)
 | 
						|
            if callable(getattr(SolidityLSPTestSuite, name)) and name.startswith("test_")
 | 
						|
        ])
 | 
						|
        filtered_tests = fnmatch.filter(all_tests, self.test_pattern)
 | 
						|
        for method_name in filtered_tests:
 | 
						|
            test_fn = getattr(self, 'test_' + method_name)
 | 
						|
            title: str = test_fn.__name__[5:]
 | 
						|
            print(f"{SGR_TEST_BEGIN}Testing {title} ...{SGR_RESET}")
 | 
						|
            try:
 | 
						|
                with JsonRpcProcess(self.solc_path, ["--lsp"], trace_io=self.trace_io) as solc:
 | 
						|
                    test_fn(solc)
 | 
						|
                    self.test_counter.passed += 1
 | 
						|
            except ExpectationFailed as e:
 | 
						|
                self.test_counter.failed += 1
 | 
						|
                print(e)
 | 
						|
                print(traceback.format_exc())
 | 
						|
            except Exception as e: # pragma pylint: disable=broad-except
 | 
						|
                self.test_counter.failed += 1
 | 
						|
                print(f"Unhandled exception {e.__class__.__name__} caught: {e}")
 | 
						|
                print(traceback.format_exc())
 | 
						|
 | 
						|
        print(
 | 
						|
            f"\n{SGR_NOTICE}Summary:{SGR_RESET}\n\n"
 | 
						|
            f"  Test cases: {self.test_counter.passed} passed, {self.test_counter.failed} failed\n"
 | 
						|
            f"  Assertions: {self.assertion_counter.passed} passed, {self.assertion_counter.failed} failed\n"
 | 
						|
        )
 | 
						|
 | 
						|
        return min(max(self.test_counter.failed, self.assertion_counter.failed), 127)
 | 
						|
 | 
						|
    def setup_lsp(self, lsp: JsonRpcProcess, expose_project_root=True):
 | 
						|
        """
 | 
						|
        Prepares the solc LSP server by calling `initialize`,
 | 
						|
        and `initialized` methods.
 | 
						|
        """
 | 
						|
        params = {
 | 
						|
            'processId': None,
 | 
						|
            'rootUri': self.project_root_uri,
 | 
						|
            'trace': 'off',
 | 
						|
            'initializationOptions': {},
 | 
						|
            'capabilities': {
 | 
						|
                'textDocument': {
 | 
						|
                    'publishDiagnostics': {'relatedInformation': True}
 | 
						|
                },
 | 
						|
                'workspace': {
 | 
						|
                    'applyEdit': True,
 | 
						|
                    'configuration': True,
 | 
						|
                    'didChangeConfiguration': {'dynamicRegistration': True},
 | 
						|
                    'workspaceEdit': {'documentChanges': True},
 | 
						|
                    'workspaceFolders': True
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if not expose_project_root:
 | 
						|
            params['rootUri'] = None
 | 
						|
        lsp.call_method('initialize', params)
 | 
						|
        lsp.send_notification('initialized')
 | 
						|
 | 
						|
    # {{{ helpers
 | 
						|
    def get_test_file_path(self, test_case_name):
 | 
						|
        return f"{self.project_root_dir}/{test_case_name}.sol"
 | 
						|
 | 
						|
    def get_test_file_uri(self, test_case_name):
 | 
						|
        return "file://" + self.get_test_file_path(test_case_name)
 | 
						|
 | 
						|
    def get_test_file_contents(self, test_case_name):
 | 
						|
        """
 | 
						|
        Reads the file contents from disc for a given test case.
 | 
						|
        The `test_case_name` will be the basename of the file
 | 
						|
        in the test path (test/libsolidity/lsp).
 | 
						|
        """
 | 
						|
        with open(self.get_test_file_path(test_case_name), mode="r", encoding="utf-8", newline='') as f:
 | 
						|
            return f.read()
 | 
						|
 | 
						|
    def require_params_for_method(self, method_name: str, message: dict) -> Any:
 | 
						|
        """
 | 
						|
        Ensures the given RPC message does contain the
 | 
						|
        field 'method' with the given method name,
 | 
						|
        and then returns its passed params.
 | 
						|
        An exception is raised on expectation failures.
 | 
						|
        """
 | 
						|
        assert message is not None
 | 
						|
        if 'error' in message.keys():
 | 
						|
            code = message['error']["code"]
 | 
						|
            text = message['error']['message']
 | 
						|
            raise RuntimeError(f"Error {code} received. {text}")
 | 
						|
        if 'method' not in message.keys():
 | 
						|
            raise RuntimeError("No method received but something else.")
 | 
						|
        self.expect_equal(message['method'], method_name, "Ensure expected method name")
 | 
						|
        return message['params']
 | 
						|
 | 
						|
    def wait_for_diagnostics(self, solc: JsonRpcProcess, count: int) -> List[dict]:
 | 
						|
        """
 | 
						|
        Return `count` number of published diagnostic reports sorted by file URI.
 | 
						|
        """
 | 
						|
        reports = []
 | 
						|
        for _ in range(0, count):
 | 
						|
            message = solc.receive_message()
 | 
						|
            assert message is not None # This can happen if the server aborts early.
 | 
						|
            reports.append(
 | 
						|
                self.require_params_for_method(
 | 
						|
                    'textDocument/publishDiagnostics',
 | 
						|
                    message,
 | 
						|
                )
 | 
						|
            )
 | 
						|
        return sorted(reports, key=lambda x: x['uri'])
 | 
						|
 | 
						|
    def open_file_and_wait_for_diagnostics(
 | 
						|
        self,
 | 
						|
        solc_process: JsonRpcProcess,
 | 
						|
        test_case_name: str,
 | 
						|
        max_diagnostic_reports: int = 1
 | 
						|
    ) -> List[Any]:
 | 
						|
        """
 | 
						|
        Opens file for given test case and waits for diagnostics to be published.
 | 
						|
        """
 | 
						|
        assert max_diagnostic_reports > 0
 | 
						|
        solc_process.send_message(
 | 
						|
            'textDocument/didOpen',
 | 
						|
            {
 | 
						|
                'textDocument':
 | 
						|
                {
 | 
						|
                    'uri': self.get_test_file_uri(test_case_name),
 | 
						|
                    'languageId': 'Solidity',
 | 
						|
                    'version': 1,
 | 
						|
                    'text': self.get_test_file_contents(test_case_name)
 | 
						|
                }
 | 
						|
            }
 | 
						|
        )
 | 
						|
        return self.wait_for_diagnostics(solc_process, max_diagnostic_reports)
 | 
						|
 | 
						|
    def expect_equal(self, actual, expected, description="Equality") -> None:
 | 
						|
        self.assertion_counter.total += 1
 | 
						|
        prefix = f"[{self.assertion_counter.total}] {SGR_ASSERT_BEGIN}{description}: "
 | 
						|
        diff = DeepDiff(actual, expected)
 | 
						|
        if len(diff) == 0:
 | 
						|
            self.assertion_counter.passed += 1
 | 
						|
            if self.print_assertions:
 | 
						|
                print(prefix + SGR_STATUS_OKAY + 'OK' + SGR_RESET)
 | 
						|
            return
 | 
						|
 | 
						|
        # Failed assertions are always printed.
 | 
						|
        self.assertion_counter.failed += 1
 | 
						|
        print(prefix + SGR_STATUS_FAIL + 'FAILED' + SGR_RESET)
 | 
						|
        raise ExpectationFailed(actual, expected)
 | 
						|
 | 
						|
    def expect_empty_diagnostics(self, published_diagnostics: List[dict]) -> None:
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
 | 
						|
        self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
 | 
						|
    def expect_diagnostic(
 | 
						|
        self,
 | 
						|
        diagnostic,
 | 
						|
        code: int,
 | 
						|
        lineNo: int,
 | 
						|
        startEndColumns: Tuple[int, int]
 | 
						|
    ):
 | 
						|
        assert len(startEndColumns) == 2
 | 
						|
        [startColumn, endColumn] = startEndColumns
 | 
						|
        self.expect_equal(diagnostic['code'], code, f'diagnostic: {code}')
 | 
						|
        self.expect_equal(
 | 
						|
            diagnostic['range'],
 | 
						|
            {
 | 
						|
                'start': {'character': startColumn, 'line': lineNo},
 | 
						|
                'end': {'character': endColumn, 'line': lineNo}
 | 
						|
            },
 | 
						|
            "diagnostic: check range"
 | 
						|
        )
 | 
						|
 | 
						|
    def expect_location(
 | 
						|
        self,
 | 
						|
        obj: dict,
 | 
						|
        uri: str,
 | 
						|
        lineNo: int,
 | 
						|
        startEndColumns: Tuple[int, int]
 | 
						|
    ):
 | 
						|
        """
 | 
						|
        obj is an JSON object containing two keys:
 | 
						|
            - 'uri': a string of the document URI
 | 
						|
            - 'range': the location range, two child objects 'start' and 'end',
 | 
						|
                        each having a 'line' and 'character' integer value.
 | 
						|
        """
 | 
						|
        [startColumn, endColumn] = startEndColumns
 | 
						|
        self.expect_equal(obj['uri'], uri)
 | 
						|
        self.expect_equal(obj['range']['start']['line'], lineNo)
 | 
						|
        self.expect_equal(obj['range']['start']['character'], startColumn)
 | 
						|
        self.expect_equal(obj['range']['end']['line'], lineNo)
 | 
						|
        self.expect_equal(obj['range']['end']['character'], endColumn)
 | 
						|
 | 
						|
    def expect_goto_definition_location(
 | 
						|
        self,
 | 
						|
        solc: JsonRpcProcess,
 | 
						|
        document_uri: str,
 | 
						|
        document_position: Tuple[int, int],
 | 
						|
        expected_uri: str,
 | 
						|
        expected_lineNo: int,
 | 
						|
        expected_startEndColumns: Tuple[int, int],
 | 
						|
        description: str
 | 
						|
    ):
 | 
						|
        response = solc.call_method(
 | 
						|
            'textDocument/definition',
 | 
						|
            {
 | 
						|
                'textDocument': {
 | 
						|
                    'uri': document_uri,
 | 
						|
                },
 | 
						|
                'position': {
 | 
						|
                    'line': document_position[0],
 | 
						|
                    'character': document_position[1]
 | 
						|
                }
 | 
						|
            }
 | 
						|
        )
 | 
						|
        message = "Goto definition (" + description + ")"
 | 
						|
        self.expect_equal(len(response['result']), 1, message)
 | 
						|
        self.expect_location(response['result'][0], expected_uri, expected_lineNo, expected_startEndColumns)
 | 
						|
    # }}}
 | 
						|
 | 
						|
    # {{{ actual tests
 | 
						|
    def test_publish_diagnostics_warnings(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        TEST_NAME = 'publish_diagnostics_1'
 | 
						|
        published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME)
 | 
						|
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message")
 | 
						|
        report = published_diagnostics[0]
 | 
						|
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI")
 | 
						|
        diagnostics = report['diagnostics']
 | 
						|
 | 
						|
        self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
 | 
						|
        self.expect_diagnostic(diagnostics[0], code=6321, lineNo=13, startEndColumns=(44, 48))
 | 
						|
        self.expect_diagnostic(diagnostics[1], code=2072, lineNo= 7, startEndColumns=( 8, 19))
 | 
						|
        self.expect_diagnostic(diagnostics[2], code=2072, lineNo=15, startEndColumns=( 8, 20))
 | 
						|
 | 
						|
    def test_publish_diagnostics_errors(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        TEST_NAME = 'publish_diagnostics_2'
 | 
						|
        published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME)
 | 
						|
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message")
 | 
						|
        report = published_diagnostics[0]
 | 
						|
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI")
 | 
						|
        diagnostics = report['diagnostics']
 | 
						|
 | 
						|
        self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
 | 
						|
        self.expect_diagnostic(diagnostics[0], code=9574, lineNo= 7, startEndColumns=( 8, 21))
 | 
						|
        self.expect_diagnostic(diagnostics[1], code=6777, lineNo= 8, startEndColumns=( 8, 15))
 | 
						|
        self.expect_diagnostic(diagnostics[2], code=6160, lineNo=18, startEndColumns=(15, 36))
 | 
						|
 | 
						|
    def test_publish_diagnostics_errors_multiline(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        TEST_NAME = 'publish_diagnostics_3'
 | 
						|
        published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME)
 | 
						|
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message")
 | 
						|
        report = published_diagnostics[0]
 | 
						|
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI")
 | 
						|
        diagnostics = report['diagnostics']
 | 
						|
 | 
						|
        self.expect_equal(len(diagnostics), 1, "3 diagnostic messages")
 | 
						|
        self.expect_equal(diagnostics[0]['code'], 3656, "diagnostic: check code")
 | 
						|
        self.expect_equal(
 | 
						|
            diagnostics[0]['range'],
 | 
						|
            {
 | 
						|
                'end': {'character': 1, 'line': 9},
 | 
						|
                'start': {'character': 0, 'line': 7}
 | 
						|
            },
 | 
						|
            "diagnostic: check range"
 | 
						|
        )
 | 
						|
 | 
						|
    def test_textDocument_didOpen_with_relative_import(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        TEST_NAME = 'didOpen_with_import'
 | 
						|
        published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, 2)
 | 
						|
 | 
						|
        self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files")
 | 
						|
 | 
						|
        # primary file:
 | 
						|
        report = published_diagnostics[0]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI")
 | 
						|
        self.expect_equal(len(report['diagnostics']), 0, "no diagnostics")
 | 
						|
 | 
						|
        # imported file (./lib.sol):
 | 
						|
        report = published_diagnostics[1]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI")
 | 
						|
        self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
 | 
						|
        self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=31, startEndColumns=(8, 19))
 | 
						|
 | 
						|
    def test_didChange_in_A_causing_error_in_B(self, solc: JsonRpcProcess) -> None:
 | 
						|
        # Reusing another test but now change some file that generates an error in the other.
 | 
						|
        self.test_textDocument_didOpen_with_relative_import(solc)
 | 
						|
        self.open_file_and_wait_for_diagnostics(solc, 'lib', 2)
 | 
						|
        solc.send_message(
 | 
						|
            'textDocument/didChange',
 | 
						|
            {
 | 
						|
                'textDocument':
 | 
						|
                {
 | 
						|
                    'uri': self.get_test_file_uri('lib')
 | 
						|
                },
 | 
						|
                'contentChanges':
 | 
						|
                [
 | 
						|
                    {
 | 
						|
                        'range': {
 | 
						|
                            'start': { 'line': 24, 'character': 0 },
 | 
						|
                            'end':   { 'line': 29, 'character': 0 }
 | 
						|
                        },
 | 
						|
                        'text': "" # deleting function `add`
 | 
						|
                    }
 | 
						|
                ]
 | 
						|
            }
 | 
						|
        )
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files")
 | 
						|
 | 
						|
        # Main file now contains a new diagnostic
 | 
						|
        report = published_diagnostics[0]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri('didOpen_with_import'))
 | 
						|
        diagnostics = report['diagnostics']
 | 
						|
        self.expect_equal(len(diagnostics), 1, "now, no diagnostics")
 | 
						|
        self.expect_diagnostic(diagnostics[0], code=9582, lineNo=9, startEndColumns=(15, 22))
 | 
						|
 | 
						|
        # The modified file retains the same diagnostics.
 | 
						|
        report = published_diagnostics[1]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri('lib'))
 | 
						|
        self.expect_equal(len(report['diagnostics']), 0)
 | 
						|
        # The warning went away because the compiler aborts further processing after the error.
 | 
						|
 | 
						|
    def test_textDocument_didOpen_with_relative_import_without_project_url(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc, expose_project_root=False)
 | 
						|
        TEST_NAME = 'didOpen_with_import'
 | 
						|
        published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, 2)
 | 
						|
        self.verify_didOpen_with_import_diagnostics(published_diagnostics)
 | 
						|
 | 
						|
    def verify_didOpen_with_import_diagnostics(
 | 
						|
        self,
 | 
						|
        published_diagnostics: List[Any],
 | 
						|
        main_file_name='didOpen_with_import'
 | 
						|
    ):
 | 
						|
        self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files")
 | 
						|
 | 
						|
        # primary file:
 | 
						|
        report = published_diagnostics[0]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri(main_file_name), "Correct file URI")
 | 
						|
        self.expect_equal(len(report['diagnostics']), 0, "one diagnostic")
 | 
						|
 | 
						|
        # imported file (./lib.sol):
 | 
						|
        report = published_diagnostics[1]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI")
 | 
						|
        self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
 | 
						|
        self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=31, startEndColumns=(8, 19))
 | 
						|
 | 
						|
    def test_textDocument_didChange_updates_diagnostics(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        TEST_NAME = 'publish_diagnostics_1'
 | 
						|
        published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message")
 | 
						|
        report = published_diagnostics[0]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI")
 | 
						|
        diagnostics = report['diagnostics']
 | 
						|
        self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
 | 
						|
        self.expect_diagnostic(diagnostics[0], code=6321, lineNo=13, startEndColumns=(44, 48))
 | 
						|
        self.expect_diagnostic(diagnostics[1], code=2072, lineNo= 7, startEndColumns=( 8, 19))
 | 
						|
        self.expect_diagnostic(diagnostics[2], code=2072, lineNo=15, startEndColumns=( 8, 20))
 | 
						|
 | 
						|
        solc.send_message(
 | 
						|
            'textDocument/didChange',
 | 
						|
            {
 | 
						|
                'textDocument': {
 | 
						|
                    'uri': self.get_test_file_uri(TEST_NAME)
 | 
						|
                },
 | 
						|
                'contentChanges': [
 | 
						|
                    {
 | 
						|
                        'range': {
 | 
						|
                            'start': { 'line': 7, 'character': 1 },
 | 
						|
                            'end': {   'line': 8, 'character': 1 }
 | 
						|
                        },
 | 
						|
                        'text': ""
 | 
						|
                    }
 | 
						|
                ]
 | 
						|
            }
 | 
						|
        )
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1)
 | 
						|
        report = published_diagnostics[0]
 | 
						|
        self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI")
 | 
						|
        diagnostics = report['diagnostics']
 | 
						|
        self.expect_equal(len(diagnostics), 2)
 | 
						|
        self.expect_diagnostic(diagnostics[0], code=6321, lineNo=12, startEndColumns=(44, 48))
 | 
						|
        self.expect_diagnostic(diagnostics[1], code=2072, lineNo=14, startEndColumns=( 8, 20))
 | 
						|
 | 
						|
    def test_textDocument_didChange_delete_line_and_close(self, solc: JsonRpcProcess) -> None:
 | 
						|
        # Reuse this test to prepare and ensure it is as expected
 | 
						|
        self.test_textDocument_didOpen_with_relative_import(solc)
 | 
						|
        self.open_file_and_wait_for_diagnostics(solc, 'lib', 2)
 | 
						|
        # lib.sol: Fix the unused variable message by removing it.
 | 
						|
        solc.send_message(
 | 
						|
            'textDocument/didChange',
 | 
						|
            {
 | 
						|
                'textDocument':
 | 
						|
                {
 | 
						|
                    'uri': self.get_test_file_uri('lib')
 | 
						|
                },
 | 
						|
                'contentChanges': # delete the in-body statement: `uint unused;`
 | 
						|
                [
 | 
						|
                    {
 | 
						|
                        'range':
 | 
						|
                        {
 | 
						|
                            'start': { 'line': 31, 'character': 1 },
 | 
						|
                            'end':   { 'line': 32, 'character': 1 }
 | 
						|
                        },
 | 
						|
                        'text': ""
 | 
						|
                    }
 | 
						|
                ]
 | 
						|
            }
 | 
						|
        )
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.expect_equal(len(published_diagnostics), 2, "published diagnostics count")
 | 
						|
        report1 = published_diagnostics[0]
 | 
						|
        self.expect_equal(report1['uri'], self.get_test_file_uri('didOpen_with_import'), "Correct file URI")
 | 
						|
        self.expect_equal(len(report1['diagnostics']), 0, "no diagnostics in didOpen_with_import.sol")
 | 
						|
        report2 = published_diagnostics[1]
 | 
						|
        self.expect_equal(report2['uri'], self.get_test_file_uri('lib'), "Correct file URI")
 | 
						|
        self.expect_equal(len(report2['diagnostics']), 0, "no diagnostics in lib.sol")
 | 
						|
 | 
						|
        # Now close the file and expect the warning to re-appear
 | 
						|
        solc.send_message(
 | 
						|
            'textDocument/didClose',
 | 
						|
            { 'textDocument': { 'uri': self.get_test_file_uri('lib') }}
 | 
						|
        )
 | 
						|
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.verify_didOpen_with_import_diagnostics(published_diagnostics)
 | 
						|
 | 
						|
    def test_textDocument_opening_two_new_files_edit_and_close(self, solc: JsonRpcProcess) -> None:
 | 
						|
        """
 | 
						|
        Open two new files A and B, let A import B, expect no error,
 | 
						|
        then close B and now expect the error of file B not being found.
 | 
						|
        """
 | 
						|
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        FILE_A_URI = 'file:///a.sol'
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_A_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text': ''.join([
 | 
						|
                    '// SPDX-License-Identifier: UNLICENSED\n',
 | 
						|
                    'pragma solidity >=0.8.0;\n',
 | 
						|
                ])
 | 
						|
            }
 | 
						|
        })
 | 
						|
        reports = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(reports), 1, "one publish diagnostics notification")
 | 
						|
        self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
 | 
						|
        FILE_B_URI = 'file:///b.sol'
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_B_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text': ''.join([
 | 
						|
                    '// SPDX-License-Identifier: UNLICENSED\n',
 | 
						|
                    'pragma solidity >=0.8.0;\n',
 | 
						|
                ])
 | 
						|
            }
 | 
						|
        })
 | 
						|
        reports = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.expect_equal(len(reports), 2, "one publish diagnostics notification")
 | 
						|
        self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
        self.expect_equal(len(reports[1]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
 | 
						|
        solc.send_message('textDocument/didChange', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_A_URI
 | 
						|
            },
 | 
						|
            'contentChanges': [
 | 
						|
                {
 | 
						|
                    'range': {
 | 
						|
                        'start': { 'line': 2, 'character': 0 },
 | 
						|
                        'end': { 'line': 2, 'character': 0 }
 | 
						|
                    },
 | 
						|
                    'text': 'import "./b.sol";\n'
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        })
 | 
						|
        reports = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.expect_equal(len(reports), 2, "one publish diagnostics notification")
 | 
						|
        self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
        self.expect_equal(len(reports[1]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
 | 
						|
        solc.send_message(
 | 
						|
            'textDocument/didClose',
 | 
						|
            { 'textDocument': { 'uri': FILE_B_URI }}
 | 
						|
        )
 | 
						|
        # We only get one diagnostics message since the diagnostics for b.sol was empty.
 | 
						|
        reports = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(reports), 1, "one publish diagnostics notification")
 | 
						|
        self.expect_diagnostic(reports[0]['diagnostics'][0], 6275, 2, (0, 17)) # a.sol: File B not found
 | 
						|
        self.expect_equal(reports[0]['uri'], FILE_A_URI, "Correct uri")
 | 
						|
 | 
						|
    def test_textDocument_closing_virtual_file_removes_imported_real_file(self, solc: JsonRpcProcess) -> None:
 | 
						|
        """
 | 
						|
        We open a virtual file that imports a real file with a warning.
 | 
						|
        Once we close the virtual file, the warning is removed from the diagnostics,
 | 
						|
        since the real file is not considered part of the project anymore.
 | 
						|
        """
 | 
						|
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        FILE_A_URI = f'file://{self.project_root_dir}/a.sol'
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_A_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text':
 | 
						|
                    '// SPDX-License-Identifier: UNLICENSED\n'
 | 
						|
                    'pragma solidity >=0.8.0;\n'
 | 
						|
                    'import "./lib.sol";\n'
 | 
						|
            }
 | 
						|
        })
 | 
						|
        reports = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.expect_equal(len(reports), 2, '')
 | 
						|
        self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
        self.expect_diagnostic(reports[1]['diagnostics'][0], 2072, 31, (8, 19)) # unused variable in lib.sol
 | 
						|
 | 
						|
        # Now close the file and expect the warning for lib.sol to be removed
 | 
						|
        solc.send_message(
 | 
						|
            'textDocument/didClose',
 | 
						|
            { 'textDocument': { 'uri': FILE_A_URI }}
 | 
						|
        )
 | 
						|
        reports = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(reports), 1, '')
 | 
						|
        self.expect_equal(reports[0]['uri'], f'file://{self.project_root_dir}/lib.sol', "")
 | 
						|
        self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
 | 
						|
 | 
						|
 | 
						|
    def test_textDocument_didChange_at_eol(self, solc: JsonRpcProcess) -> None:
 | 
						|
        """
 | 
						|
        Append at one line and insert a new one below.
 | 
						|
        """
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        FILE_NAME = 'didChange_template'
 | 
						|
        FILE_URI = self.get_test_file_uri(FILE_NAME)
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text': self.get_test_file_contents(FILE_NAME)
 | 
						|
            }
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
 | 
						|
        self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "no diagnostics")
 | 
						|
        solc.send_message('textDocument/didChange', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_URI
 | 
						|
            },
 | 
						|
            'contentChanges': [
 | 
						|
                {
 | 
						|
                    'range': {
 | 
						|
                        'start': { 'line': 6, 'character': 0 },
 | 
						|
                        'end': { 'line': 6, 'character': 0 }
 | 
						|
                    },
 | 
						|
                    'text': " f"
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
 | 
						|
        report2 = published_diagnostics[0]
 | 
						|
        self.expect_equal(report2['uri'], FILE_URI, "Correct file URI")
 | 
						|
        self.expect_equal(len(report2['diagnostics']), 1, "one diagnostic")
 | 
						|
        self.expect_diagnostic(report2['diagnostics'][0], 7858, 6, (1, 2))
 | 
						|
 | 
						|
        solc.send_message('textDocument/didChange', {
 | 
						|
            'textDocument': { 'uri': FILE_URI },
 | 
						|
            'contentChanges': [
 | 
						|
                {
 | 
						|
                    'range': {
 | 
						|
                        'start': { 'line': 6, 'character': 2 },
 | 
						|
                        'end': { 'line': 6, 'character': 2 }
 | 
						|
                    },
 | 
						|
                    'text': 'unction f() public {}'
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
 | 
						|
        report3 = published_diagnostics[0]
 | 
						|
        self.expect_equal(report3['uri'], FILE_URI, "Correct file URI")
 | 
						|
        self.expect_equal(len(report3['diagnostics']), 1, "one diagnostic")
 | 
						|
        self.expect_diagnostic(report3['diagnostics'][0], 4126, 6, (1, 23))
 | 
						|
 | 
						|
    def test_textDocument_definition(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        FILE_NAME = 'goto_definition'
 | 
						|
        FILE_URI = self.get_test_file_uri(FILE_NAME)
 | 
						|
        LIB_URI = self.get_test_file_uri('lib')
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text': self.get_test_file_contents(FILE_NAME)
 | 
						|
            }
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files")
 | 
						|
        self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0)
 | 
						|
        self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1)
 | 
						|
        self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 31, (8, 19)) # unused variable in lib.sol
 | 
						|
 | 
						|
        # import directive
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(3, 9), # symbol `"./lib.sol"` in `import "./lib.sol"`
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=0,
 | 
						|
            expected_startEndColumns=(0, 0),
 | 
						|
            description="import directive"
 | 
						|
        )
 | 
						|
 | 
						|
        # type symbol to jump to type defs (error, contract, enum, ...)
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(30, 19), # symbol `IA` in `new IA()`
 | 
						|
            expected_uri=FILE_URI,
 | 
						|
            expected_lineNo=10,
 | 
						|
            expected_startEndColumns=(9, 11),
 | 
						|
            description="type symbol to jump to definition"
 | 
						|
        )
 | 
						|
 | 
						|
        # virtual function lookup?
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(31, 12), # symbol `f`, jumps to interface definition
 | 
						|
            expected_uri=FILE_URI,
 | 
						|
            expected_lineNo=7,
 | 
						|
            expected_startEndColumns=(13, 14),
 | 
						|
            description="virtual function lookup"
 | 
						|
        )
 | 
						|
 | 
						|
        # using for
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(37, 10), # symbol `add` in `i.add(5)`
 | 
						|
            expected_uri=FILE_URI,
 | 
						|
            expected_lineNo=22,
 | 
						|
            expected_startEndColumns=(13, 16),
 | 
						|
            description="using for"
 | 
						|
        )
 | 
						|
 | 
						|
        # library
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(43, 15), # symbol `Lib` in `Lib.add(n, 1)`
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=22,
 | 
						|
            expected_startEndColumns=(8, 11),
 | 
						|
            description="Library symbol from different file"
 | 
						|
        )
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(43, 19), # symbol `add` in `Lib.add(n, 1)`
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=24,
 | 
						|
            expected_startEndColumns=(13, 16),
 | 
						|
            description="Library member symbol from different file"
 | 
						|
        )
 | 
						|
 | 
						|
        # enum type symbol and enum values
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(46, 19), # symbol `Color` in function signature's parameter
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=13,
 | 
						|
            expected_startEndColumns=(5, 10),
 | 
						|
            description="Enum type"
 | 
						|
        )
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(48, 24), # symbol `Red` in `Color.Red`
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=15,
 | 
						|
            expected_startEndColumns=(4, 7),
 | 
						|
            description="Enum value"
 | 
						|
        )
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(48, 24), # symbol `Red` in `Color.Red`
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=15,
 | 
						|
            expected_startEndColumns=(4, 7),
 | 
						|
            description="Enum value"
 | 
						|
        )
 | 
						|
 | 
						|
        # local variable declarations
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(49, 17), # symbol `e` in `(c == e)`
 | 
						|
            expected_uri=FILE_URI,
 | 
						|
            expected_lineNo=48,
 | 
						|
            expected_startEndColumns=(14, 15),
 | 
						|
            description="local variable declaration"
 | 
						|
        )
 | 
						|
 | 
						|
        # User defined type
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(58, 8), # symbol `Price` in `Price p ...`
 | 
						|
            expected_uri=FILE_URI,
 | 
						|
            expected_lineNo=55,
 | 
						|
            expected_startEndColumns=(9, 14),
 | 
						|
            description="User defined type on left hand side"
 | 
						|
        )
 | 
						|
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(58, 18), # symbol `Price` in `Price.wrap()` expected_uri=FILE_URI,
 | 
						|
            expected_uri=FILE_URI,
 | 
						|
            expected_lineNo=55,
 | 
						|
            expected_startEndColumns=(9, 14),
 | 
						|
            description="User defined type on right hand side."
 | 
						|
        )
 | 
						|
 | 
						|
        # struct constructor also properly jumps to the struct's declaration.
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(64, 33), # symbol `RGBColor` right hand side expression.
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=35,
 | 
						|
            expected_startEndColumns=(7, 15),
 | 
						|
            description="Struct constructor."
 | 
						|
        )
 | 
						|
 | 
						|
    def test_textDocument_definition_imports(self, solc: JsonRpcProcess) -> None:
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        FILE_NAME = 'goto_definition_imports'
 | 
						|
        FILE_URI = self.get_test_file_uri(FILE_NAME)
 | 
						|
        LIB_URI = self.get_test_file_uri('lib')
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text': self.get_test_file_contents(FILE_NAME)
 | 
						|
            }
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files")
 | 
						|
        self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0)
 | 
						|
        self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1)
 | 
						|
        self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 31, (8, 19)) # unused variable in lib.sol
 | 
						|
 | 
						|
        # import directive: test symbol alias
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(3, 9), #  in `Weather` of `import {Weather as Wetter} from "./lib.sol"`
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=6,
 | 
						|
            expected_startEndColumns=(5, 12),
 | 
						|
            description="goto definition of symbol in symbol alias import directive"
 | 
						|
        )
 | 
						|
 | 
						|
        # import directive: test symbol alias
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(8, 55), # `Wetter` in return type declaration
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=6,
 | 
						|
            expected_startEndColumns=(5, 12),
 | 
						|
            description="goto definition of symbol in symbol alias import directive"
 | 
						|
        )
 | 
						|
 | 
						|
        # That.Color tests with `That` being the aliased library to be imported.
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(13, 55), # `That` in return type declaration
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=13,
 | 
						|
            expected_startEndColumns=(5, 10),
 | 
						|
            description="goto definition of symbol in symbol alias import directive"
 | 
						|
        )
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(15, 8),
 | 
						|
            expected_uri=LIB_URI,
 | 
						|
            expected_lineNo=13,
 | 
						|
            expected_startEndColumns=(5, 10),
 | 
						|
            description="`That` in LHS variable assignment"
 | 
						|
        )
 | 
						|
        self.expect_goto_definition_location(
 | 
						|
            solc=solc,
 | 
						|
            document_uri=FILE_URI,
 | 
						|
            document_position=(15, 27),
 | 
						|
            expected_uri=FILE_URI,
 | 
						|
            expected_lineNo=4,
 | 
						|
            expected_startEndColumns=(22, 26),
 | 
						|
            description="`That` in expression"
 | 
						|
        )
 | 
						|
 | 
						|
    def test_textDocument_didChange_empty_file(self, solc: JsonRpcProcess) -> None:
 | 
						|
        """
 | 
						|
        Starts with an empty file and changes it to look like
 | 
						|
        the didOpen_with_import test case. Then we can use
 | 
						|
        the same verification calls to ensure it worked as expected.
 | 
						|
        """
 | 
						|
        # This FILE_NAME must be alphabetically before lib.sol to not over-complify
 | 
						|
        # the test logic in verify_didOpen_with_import_diagnostics.
 | 
						|
        FILE_NAME = 'a_new_file'
 | 
						|
        FILE_URI = self.get_test_file_uri(FILE_NAME)
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text': ''
 | 
						|
            }
 | 
						|
        })
 | 
						|
        reports = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(reports), 1)
 | 
						|
        report = reports[0]
 | 
						|
        published_diagnostics = report['diagnostics']
 | 
						|
        self.expect_equal(len(published_diagnostics), 2)
 | 
						|
        self.expect_diagnostic(published_diagnostics[0], code=1878, lineNo=0, startEndColumns=(0, 0))
 | 
						|
        self.expect_diagnostic(published_diagnostics[1], code=3420, lineNo=0, startEndColumns=(0, 0))
 | 
						|
        solc.send_message('textDocument/didChange', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': self.get_test_file_uri('a_new_file')
 | 
						|
            },
 | 
						|
            'contentChanges': [
 | 
						|
                {
 | 
						|
                    'range': {
 | 
						|
                        'start': { 'line': 0, 'character': 0 },
 | 
						|
                        'end': { 'line': 0, 'character': 0 }
 | 
						|
                    },
 | 
						|
                    'text': self.get_test_file_contents('didOpen_with_import')
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 2)
 | 
						|
        self.verify_didOpen_with_import_diagnostics(published_diagnostics, 'a_new_file')
 | 
						|
 | 
						|
    def test_textDocument_didChange_multi_line(self, solc: JsonRpcProcess) -> None:
 | 
						|
        """
 | 
						|
        Starts with an empty file and changes it to multiple times, changing
 | 
						|
        content across lines.
 | 
						|
        """
 | 
						|
        self.setup_lsp(solc)
 | 
						|
        FILE_NAME = 'didChange_template'
 | 
						|
        FILE_URI = self.get_test_file_uri(FILE_NAME)
 | 
						|
        solc.send_message('textDocument/didOpen', {
 | 
						|
            'textDocument': {
 | 
						|
                'uri': FILE_URI,
 | 
						|
                'languageId': 'Solidity',
 | 
						|
                'version': 1,
 | 
						|
                'text': self.get_test_file_contents(FILE_NAME)
 | 
						|
            }
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
 | 
						|
        self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "no diagnostics")
 | 
						|
        solc.send_message('textDocument/didChange', {
 | 
						|
            'textDocument': { 'uri': FILE_URI },
 | 
						|
            'contentChanges': [
 | 
						|
                {
 | 
						|
                    'range': {
 | 
						|
                        'start': { 'line': 3, 'character': 3 },
 | 
						|
                        'end': { 'line': 4, 'character': 1 }
 | 
						|
                    },
 | 
						|
                    'text': "tract D {\n\n  uint x\n = -1; \n "
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
 | 
						|
        report2 = published_diagnostics[0]
 | 
						|
        self.expect_equal(report2['uri'], FILE_URI, "Correct file URI")
 | 
						|
        self.expect_equal(len(report2['diagnostics']), 1, "one diagnostic")
 | 
						|
        self.expect_diagnostic(report2['diagnostics'][0], 7407, 6, (3, 5))
 | 
						|
 | 
						|
        # Now we are changing the part "x\n = -" of "uint x\n = -1;"
 | 
						|
        solc.send_message('textDocument/didChange', {
 | 
						|
            'textDocument': { 'uri': FILE_URI },
 | 
						|
            'contentChanges': [
 | 
						|
                {
 | 
						|
                    'range': {
 | 
						|
                        'start': { 'line': 5, 'character': 7 },
 | 
						|
                        'end': { 'line': 6, 'character': 4 }
 | 
						|
                    },
 | 
						|
                    'text': "y\n = [\nuint(1),\n3,4]+"
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        })
 | 
						|
        published_diagnostics = self.wait_for_diagnostics(solc, 1)
 | 
						|
        self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification")
 | 
						|
        report3 = published_diagnostics[0]
 | 
						|
        self.expect_equal(report3['uri'], FILE_URI, "Correct file URI")
 | 
						|
        self.expect_equal(len(report3['diagnostics']), 2, "two diagnostics")
 | 
						|
        diagnostic = report3['diagnostics'][0]
 | 
						|
        self.expect_equal(diagnostic['code'], 2271, 'diagnostic: 2271')
 | 
						|
        # check multi-line error code
 | 
						|
        self.expect_equal(
 | 
						|
            diagnostic['range'],
 | 
						|
            {
 | 
						|
                'end': {'character': 6, 'line': 8},
 | 
						|
                'start': {'character': 3, 'line': 6}
 | 
						|
            },
 | 
						|
            "diagnostic: check range"
 | 
						|
        )
 | 
						|
        diagnostic = report3['diagnostics'][1]
 | 
						|
        self.expect_equal(diagnostic['code'], 7407, 'diagnostic: 7407')
 | 
						|
        # check multi-line error code
 | 
						|
        self.expect_equal(
 | 
						|
            diagnostic['range'],
 | 
						|
            {
 | 
						|
                'end': {'character': 6, 'line': 8},
 | 
						|
                'start': {'character': 3, 'line': 6}
 | 
						|
            },
 | 
						|
            "diagnostic: check range"
 | 
						|
        )
 | 
						|
 | 
						|
    # }}}
 | 
						|
    # }}}
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    suite = SolidityLSPTestSuite()
 | 
						|
    exit_code = suite.main()
 | 
						|
    sys.exit(exit_code)
 |