diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3d7fc9c..3c4d0d2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ updates: - package-ecosystem: "gomod" directory: "/" schedule: - interval: "weekly" + interval: "monthly" open-pull-requests-limit: 10 labels: - "dependencies" @@ -16,4 +16,5 @@ updates: open-pull-requests-limit: 10 labels: - "dependencies" - - "github-actions" \ No newline at end of file + - "github-actions" + diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 15760fe..8d44d04 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -8,8 +8,8 @@ on: workflow_dispatch: jobs: - test: - name: Run Tests + unit-tests: + name: Unit Tests and Code Quality runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -21,14 +21,23 @@ jobs: check-latest: true cache: true + - name: Build + run: go build -o mcp-language-server + + - name: Install just + run: curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin + - name: Install gopls run: go install golang.org/x/tools/gopls@latest - - name: Run tests - run: go test ./... + - name: Run unit tests + run: go test ./internal/... + + - name: Run code quality checks + run: just check - check: - name: Build and Code Quality Checks + go-integration-tests: + name: Go Integration Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -40,14 +49,86 @@ jobs: check-latest: true cache: true - - name: Build - run: go build -o mcp-language-server - - - name: Install just - run: curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin - - name: Install gopls run: go install golang.org/x/tools/gopls@latest - - name: Run code quality checks - run: just check + - name: Run Go integration tests + run: go test ./integrationtests/languages/go/... + + python-integration-tests: + name: Python Integration Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + check-latest: true + cache: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install pyright + run: npm install -g pyright + + - name: Run Python integration tests + run: go test ./integrationtests/languages/python/... + + rust-integration-tests: + name: Rust Integration Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + check-latest: true + cache: true + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rust-src + override: true + + - name: Install rust-analyzer + run: | + mkdir -p ~/.local/bin + curl -L https://github.com/rust-analyzer/rust-analyzer/releases/latest/download/rust-analyzer-x86_64-unknown-linux-gnu.gz | gunzip -c - > ~/.local/bin/rust-analyzer + chmod +x ~/.local/bin/rust-analyzer + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Run Rust integration tests + run: go test ./integrationtests/languages/rust/... + + typescript-integration-tests: + name: TypeScript Integration Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + check-latest: true + cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install typescript-language-server + run: npm install -g typescript typescript-language-server + + - name: Run TypeScript integration tests + run: go test ./integrationtests/languages/typescript/... diff --git a/integrationtests/fixtures/snapshots/go/diagnostics/unreachable.snap b/integrationtests/fixtures/snapshots/go/diagnostics/unreachable.snap index d0e850a..7a76560 100644 --- a/integrationtests/fixtures/snapshots/go/diagnostics/unreachable.snap +++ b/integrationtests/fixtures/snapshots/go/diagnostics/unreachable.snap @@ -10,7 +10,6 @@ Code: default 8| fmt.Println("Unreachable code") // This is unreachable code 9|} - === /TEST_OUTPUT/workspace/main.go Location: Line 9, Column 1 diff --git a/integrationtests/fixtures/snapshots/go/references/foobar-function.snap b/integrationtests/fixtures/snapshots/go/references/foobar-function.snap index e3f8e35..84c084c 100644 --- a/integrationtests/fixtures/snapshots/go/references/foobar-function.snap +++ b/integrationtests/fixtures/snapshots/go/references/foobar-function.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/main.go References in File: 1 === - Reference at Line 12, Column 14: 11|func main() { 12| fmt.Println(FooBar()) diff --git a/integrationtests/fixtures/snapshots/go/references/helper-function.snap b/integrationtests/fixtures/snapshots/go/references/helper-function.snap index f634a7b..df75f90 100644 --- a/integrationtests/fixtures/snapshots/go/references/helper-function.snap +++ b/integrationtests/fixtures/snapshots/go/references/helper-function.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/another_consumer.go References in File: 1 === - Reference at Line 8, Column 34: 6|func AnotherConsumer() { 7| // Use helper function @@ -41,12 +40,10 @@ Reference at Line 8, Column 34: 40| } 41|} - === /TEST_OUTPUT/workspace/consumer.go References in File: 1 === - Reference at Line 7, Column 13: 6|func ConsumerFunction() { 7| message := HelperFunction() diff --git a/integrationtests/fixtures/snapshots/go/references/interface-method.snap b/integrationtests/fixtures/snapshots/go/references/interface-method.snap index 2846f3e..2ce4395 100644 --- a/integrationtests/fixtures/snapshots/go/references/interface-method.snap +++ b/integrationtests/fixtures/snapshots/go/references/interface-method.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/another_consumer.go References in File: 1 === - Reference at Line 19, Column 15: 6|func AnotherConsumer() { 7| // Use helper function @@ -41,12 +40,10 @@ Reference at Line 19, Column 15: 40| } 41|} - === /TEST_OUTPUT/workspace/consumer.go References in File: 1 === - Reference at Line 24, Column 20: 6|func ConsumerFunction() { 7| message := HelperFunction() diff --git a/integrationtests/fixtures/snapshots/go/references/shared-constant.snap b/integrationtests/fixtures/snapshots/go/references/shared-constant.snap index 0efef39..7a11b84 100644 --- a/integrationtests/fixtures/snapshots/go/references/shared-constant.snap +++ b/integrationtests/fixtures/snapshots/go/references/shared-constant.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/another_consumer.go References in File: 1 === - Reference at Line 15, Column 23: 6|func AnotherConsumer() { 7| // Use helper function @@ -41,12 +40,10 @@ Reference at Line 15, Column 23: 40| } 41|} - === /TEST_OUTPUT/workspace/consumer.go References in File: 1 === - Reference at Line 15, Column 23: 6|func ConsumerFunction() { 7| message := HelperFunction() diff --git a/integrationtests/fixtures/snapshots/go/references/shared-interface.snap b/integrationtests/fixtures/snapshots/go/references/shared-interface.snap index b390353..7c17a89 100644 --- a/integrationtests/fixtures/snapshots/go/references/shared-interface.snap +++ b/integrationtests/fixtures/snapshots/go/references/shared-interface.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/another_consumer.go References in File: 1 === - Reference at Line 33, Column 12: 6|func AnotherConsumer() { 7| // Use helper function @@ -41,12 +40,10 @@ Reference at Line 33, Column 12: 40| } 41|} - === /TEST_OUTPUT/workspace/consumer.go References in File: 1 === - Reference at Line 23, Column 12: 6|func ConsumerFunction() { 7| message := HelperFunction() diff --git a/integrationtests/fixtures/snapshots/go/references/shared-struct.snap b/integrationtests/fixtures/snapshots/go/references/shared-struct.snap index 6aaf25c..59f8805 100644 --- a/integrationtests/fixtures/snapshots/go/references/shared-struct.snap +++ b/integrationtests/fixtures/snapshots/go/references/shared-struct.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/another_consumer.go References in File: 2 === - Reference at Line 11, Column 8: 6|func AnotherConsumer() { 7| // Use helper function @@ -80,12 +79,10 @@ Reference at Line 25, Column 3: 40| } 41|} - === /TEST_OUTPUT/workspace/consumer.go References in File: 1 === - Reference at Line 11, Column 8: 6|func ConsumerFunction() { 7| message := HelperFunction() @@ -112,12 +109,10 @@ Reference at Line 11, Column 8: 28| fmt.Println(t) 29|} - === /TEST_OUTPUT/workspace/types.go References in File: 3 === - Reference at Line 14, Column 10: 14|func (s *SharedStruct) Method() string { 15| return s.Name diff --git a/integrationtests/fixtures/snapshots/go/references/shared-type.snap b/integrationtests/fixtures/snapshots/go/references/shared-type.snap index 73cf5ec..2b6206e 100644 --- a/integrationtests/fixtures/snapshots/go/references/shared-type.snap +++ b/integrationtests/fixtures/snapshots/go/references/shared-type.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/another_consumer.go References in File: 1 === - Reference at Line 37, Column 14: 6|func AnotherConsumer() { 7| // Use helper function @@ -41,12 +40,10 @@ Reference at Line 37, Column 14: 40| } 41|} - === /TEST_OUTPUT/workspace/consumer.go References in File: 1 === - Reference at Line 27, Column 8: 6|func ConsumerFunction() { 7| message := HelperFunction() diff --git a/integrationtests/fixtures/snapshots/go/references/struct-method.snap b/integrationtests/fixtures/snapshots/go/references/struct-method.snap index 1378308..f906778 100644 --- a/integrationtests/fixtures/snapshots/go/references/struct-method.snap +++ b/integrationtests/fixtures/snapshots/go/references/struct-method.snap @@ -2,7 +2,6 @@ /TEST_OUTPUT/workspace/consumer.go References in File: 1 === - Reference at Line 19, Column 16: 6|func ConsumerFunction() { 7| message := HelperFunction() diff --git a/integrationtests/fixtures/snapshots/python/definition/class.snap b/integrationtests/fixtures/snapshots/python/definition/class.snap new file mode 100644 index 0000000..dd53c50 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/definition/class.snap @@ -0,0 +1,50 @@ +=== +Symbol: TestClass +/TEST_OUTPUT/workspace/main.py +Kind: Class +Start Position: Line 18, Column 1 +End Position: Line 59, Column 22 +=== +18|class TestClass: +19| """A test class with methods and attributes.""" +20| +21| class_variable: str = "class variable" +22| +23| def __init__(self, value: int = 0): +24| """Initialize the TestClass. +25| +26| Args: +27| value: The initial value +28| """ +29| self.value: int = value +30| +31| def test_method(self, increment: int) -> int: +32| """Increment the value by the given amount. +33| +34| Args: +35| increment: The amount to increment by +36| +37| Returns: +38| The new value +39| """ +40| self.value += increment +41| return self.value +42| +43| @staticmethod +44| def static_method(items: List[str]) -> Dict[str, int]: +45| """Convert a list of items to a dictionary with item counts. +46| +47| Args: +48| items: A list of strings +49| +50| Returns: +51| A dictionary mapping items to their counts +52| """ +53| result: Dict[str, int] = {} +54| for item in items: +55| if item in result: +56| result[item] += 1 +57| else: +58| result[item] = 1 +59| return result + diff --git a/integrationtests/fixtures/snapshots/python/definition/constant.snap b/integrationtests/fixtures/snapshots/python/definition/constant.snap new file mode 100644 index 0000000..0d9f2bb --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/definition/constant.snap @@ -0,0 +1,9 @@ +=== +Symbol: TEST_CONSTANT +/TEST_OUTPUT/workspace/main.py +Kind: Constant +Start Position: Line 79, Column 1 +End Position: Line 79, Column 14 +=== +79|TEST_CONSTANT: str = "test constant" + diff --git a/integrationtests/fixtures/snapshots/python/definition/derived-class.snap b/integrationtests/fixtures/snapshots/python/definition/derived-class.snap new file mode 100644 index 0000000..61ca818 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/definition/derived-class.snap @@ -0,0 +1,14 @@ +=== +Symbol: DerivedClass +/TEST_OUTPUT/workspace/main.py +Kind: Class +Start Position: Line 70, Column 1 +End Position: Line 75, Column 13 +=== +70|class DerivedClass(BaseClass): +71| """A class that inherits from BaseClass.""" +72| +73| def derived_method(self) -> None: +74| """A method defined in the derived class.""" +75| pass + diff --git a/integrationtests/fixtures/snapshots/python/definition/function.snap b/integrationtests/fixtures/snapshots/python/definition/function.snap new file mode 100644 index 0000000..46da10c --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/definition/function.snap @@ -0,0 +1,18 @@ +=== +Symbol: test_function +/TEST_OUTPUT/workspace/main.py +Kind: Function +Start Position: Line 6, Column 1 +End Position: Line 15, Column 29 +=== + 6|def test_function(name: str) -> str: + 7| """A simple test function that returns a greeting message. + 8| + 9| Args: +10| name: The name to greet +11| +12| Returns: +13| A greeting message +14| """ +15| return f"Hello, {name}!" + diff --git a/integrationtests/fixtures/snapshots/python/definition/method.snap b/integrationtests/fixtures/snapshots/python/definition/method.snap new file mode 100644 index 0000000..f80dcf7 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/definition/method.snap @@ -0,0 +1,51 @@ +=== +Symbol: test_method +/TEST_OUTPUT/workspace/main.py +Kind: Method +Container Name: TestClass +Start Position: Line 18, Column 1 +End Position: Line 59, Column 22 +=== +18|class TestClass: +19| """A test class with methods and attributes.""" +20| +21| class_variable: str = "class variable" +22| +23| def __init__(self, value: int = 0): +24| """Initialize the TestClass. +25| +26| Args: +27| value: The initial value +28| """ +29| self.value: int = value +30| +31| def test_method(self, increment: int) -> int: +32| """Increment the value by the given amount. +33| +34| Args: +35| increment: The amount to increment by +36| +37| Returns: +38| The new value +39| """ +40| self.value += increment +41| return self.value +42| +43| @staticmethod +44| def static_method(items: List[str]) -> Dict[str, int]: +45| """Convert a list of items to a dictionary with item counts. +46| +47| Args: +48| items: A list of strings +49| +50| Returns: +51| A dictionary mapping items to their counts +52| """ +53| result: Dict[str, int] = {} +54| for item in items: +55| if item in result: +56| result[item] += 1 +57| else: +58| result[item] = 1 +59| return result + diff --git a/integrationtests/fixtures/snapshots/python/definition/static-method.snap b/integrationtests/fixtures/snapshots/python/definition/static-method.snap new file mode 100644 index 0000000..34d9d21 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/definition/static-method.snap @@ -0,0 +1,51 @@ +=== +Symbol: static_method +/TEST_OUTPUT/workspace/main.py +Kind: Method +Container Name: TestClass +Start Position: Line 18, Column 1 +End Position: Line 59, Column 22 +=== +18|class TestClass: +19| """A test class with methods and attributes.""" +20| +21| class_variable: str = "class variable" +22| +23| def __init__(self, value: int = 0): +24| """Initialize the TestClass. +25| +26| Args: +27| value: The initial value +28| """ +29| self.value: int = value +30| +31| def test_method(self, increment: int) -> int: +32| """Increment the value by the given amount. +33| +34| Args: +35| increment: The amount to increment by +36| +37| Returns: +38| The new value +39| """ +40| self.value += increment +41| return self.value +42| +43| @staticmethod +44| def static_method(items: List[str]) -> Dict[str, int]: +45| """Convert a list of items to a dictionary with item counts. +46| +47| Args: +48| items: A list of strings +49| +50| Returns: +51| A dictionary mapping items to their counts +52| """ +53| result: Dict[str, int] = {} +54| for item in items: +55| if item in result: +56| result[item] += 1 +57| else: +58| result[item] = 1 +59| return result + diff --git a/integrationtests/fixtures/snapshots/python/definition/variable.snap b/integrationtests/fixtures/snapshots/python/definition/variable.snap new file mode 100644 index 0000000..3b4cf40 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/definition/variable.snap @@ -0,0 +1,9 @@ +=== +Symbol: test_variable +/TEST_OUTPUT/workspace/main.py +Kind: Variable +Start Position: Line 83, Column 1 +End Position: Line 83, Column 14 +=== +83|test_variable: List[int] = [1, 2, 3, 4, 5] + diff --git a/integrationtests/fixtures/snapshots/python/diagnostics/clean.snap b/integrationtests/fixtures/snapshots/python/diagnostics/clean.snap new file mode 100644 index 0000000..c13154c --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/diagnostics/clean.snap @@ -0,0 +1 @@ +/TEST_OUTPUT/workspace/clean.py \ No newline at end of file diff --git a/integrationtests/fixtures/snapshots/python/diagnostics/dependency.snap b/integrationtests/fixtures/snapshots/python/diagnostics/dependency.snap new file mode 100644 index 0000000..5ceab54 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/diagnostics/dependency.snap @@ -0,0 +1,18 @@ +=== +/TEST_OUTPUT/workspace/consumer_clean.py +Location: Line 10, Column 15 +Message: Argument missing for parameter "age" +Source: Pyright +Code: reportCallIssue +=== + 7|def consumer_function() -> None: + 8| """Function that consumes the helper functions.""" + 9| # Use the helper function +10| message = helper_function("World") +11| print(message) +12| +13| # Get and process items from the helper +14| items = get_items() +15| for item in items: +16| print(f"Processing {item}") + diff --git a/integrationtests/fixtures/snapshots/python/diagnostics/errors.snap b/integrationtests/fixtures/snapshots/python/diagnostics/errors.snap new file mode 100644 index 0000000..7f4a061 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/diagnostics/errors.snap @@ -0,0 +1,46 @@ +=== +/TEST_OUTPUT/workspace/error_file.py +Location: Line 31, Column 12 +Message: Type "Literal[42]" is not assignable to return type "str" +  "Literal[42]" is not assignable to "str" +Source: Pyright +Code: reportReturnType +=== +25|def function_with_type_error() -> str: +26| """A function with a type error. +27| +28| Returns: +29| Should return a string but actually returns an int +30| """ +31| return 42 # Type error: Incompatible return value type (got "int", expected "str") + +=== +/TEST_OUTPUT/workspace/error_file.py +Location: Line 47, Column 15 +Message: "undefined_variable" is not defined +Source: Pyright +Code: reportUndefinedVariable +=== +34|class ErrorClass: +35| """A class with errors.""" +36| +37| def __init__(self, value: Dict[str, Any]): +38| """Initialize with errors. +39| +40| Args: +41| value: A dictionary +42| """ +43| self.value = value +44| +45| def method_with_undefined_variable(self) -> None: +46| """A method that uses an undefined variable.""" +47| print(undefined_variable) # Error: undefined_variable is not defined + +=== +/TEST_OUTPUT/workspace/error_file.py +Location: Line 51, Column 19 +Message: Type "Literal[123]" is not assignable to declared type "str" +  "Literal[123]" is not assignable to "str" +Source: Pyright +Code: reportAssignmentType +=== \ No newline at end of file diff --git a/integrationtests/fixtures/snapshots/python/references/class-method.snap b/integrationtests/fixtures/snapshots/python/references/class-method.snap new file mode 100644 index 0000000..b0bb256 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/references/class-method.snap @@ -0,0 +1,60 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.py +References in File: 1 +=== +Reference at Line 40, Column 19: +31|def another_consumer_function() -> None: +32| """Another function that uses various shared components.""" +33| # Use shared constants +34| print(f"Using constant: {SHARED_CONSTANT}") +35| +36| # Use shared class with a different type parameter +37| shared = SharedClass[float]("another example", 3.14) +38| +39| # Use methods from shared class +40| name = shared.get_name() +41| value = shared.get_value() +42| print(f"Name: {name}, Value: {value}") +43| +44| # Use our own implementation +45| impl = AnotherImplementation() +46| result = impl.do_something() +47| print(f"Implementation result: {result}") +48| +49| # Use helper function +50| output = helper_function("another direct call") +51| print(f"Helper output: {output}") +52| +53| # Use enum-like class with a different color +54| color = Color.GREEN +55| print(f"Selected color: {color}") + +=== +/TEST_OUTPUT/workspace/consumer.py +References in File: 1 +=== +Reference at Line 48, Column 41: +35|def consumer_function() -> None: +36| """Function that consumes the helper functions.""" +37| # Use the helper function +38| message = helper_function("World") +39| print(message) +40| +41| # Get and process items from the helper +42| items = get_items() +43| for item in items: +44| print(f"Processing {item}") +45| +46| # Use the shared class +47| shared = SharedClass[str]("consumer", SHARED_CONSTANT) +48| print(f"Using shared class: {shared.get_name()} - {shared.get_value()}") +49| +50| # Use our implementation of the shared interface +51| impl = MyImplementation() +52| result = impl.process(items) +53| print(f"Processed items: {result}") +54| +55| # Use the enum +56| color = Color.RED +57| print(f"Selected color: {color}") + diff --git a/integrationtests/fixtures/snapshots/python/references/color-enum.snap b/integrationtests/fixtures/snapshots/python/references/color-enum.snap new file mode 100644 index 0000000..8aec17b --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/references/color-enum.snap @@ -0,0 +1,60 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.py +References in File: 2 +=== +Reference at Line 54, Column 13: +31|def another_consumer_function() -> None: +32| """Another function that uses various shared components.""" +33| # Use shared constants +34| print(f"Using constant: {SHARED_CONSTANT}") +35| +36| # Use shared class with a different type parameter +37| shared = SharedClass[float]("another example", 3.14) +38| +39| # Use methods from shared class +40| name = shared.get_name() +41| value = shared.get_value() +42| print(f"Name: {name}, Value: {value}") +43| +44| # Use our own implementation +45| impl = AnotherImplementation() +46| result = impl.do_something() +47| print(f"Implementation result: {result}") +48| +49| # Use helper function +50| output = helper_function("another direct call") +51| print(f"Helper output: {output}") +52| +53| # Use enum-like class with a different color +54| color = Color.GREEN +55| print(f"Selected color: {color}") + +=== +/TEST_OUTPUT/workspace/consumer.py +References in File: 2 +=== +Reference at Line 56, Column 13: +35|def consumer_function() -> None: +36| """Function that consumes the helper functions.""" +37| # Use the helper function +38| message = helper_function("World") +39| print(message) +40| +41| # Get and process items from the helper +42| items = get_items() +43| for item in items: +44| print(f"Processing {item}") +45| +46| # Use the shared class +47| shared = SharedClass[str]("consumer", SHARED_CONSTANT) +48| print(f"Using shared class: {shared.get_name()} - {shared.get_value()}") +49| +50| # Use our implementation of the shared interface +51| impl = MyImplementation() +52| result = impl.process(items) +53| print(f"Processed items: {result}") +54| +55| # Use the enum +56| color = Color.RED +57| print(f"Selected color: {color}") + diff --git a/integrationtests/fixtures/snapshots/python/references/helper-function.snap b/integrationtests/fixtures/snapshots/python/references/helper-function.snap new file mode 100644 index 0000000..35d32d5 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/references/helper-function.snap @@ -0,0 +1,97 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.py +References in File: 3 +=== +Reference at Line 28, Column 16: +11|class AnotherImplementation: +12| """A class that uses shared components but doesn't implement interfaces.""" +13| +14| def __init__(self): +15| """Initialize the implementation.""" +16| self.shared = SharedClass[str]("another", SHARED_CONSTANT) +17| +18| def do_something(self) -> str: +19| """Do something with the shared components. +20| +21| Returns: +22| The processed result +23| """ +24| # Get the value from shared class +25| value = self.shared.get_value() +26| +27| # Process it using the helper function +28| return helper_function(value) + + +Reference at Line 50, Column 14: +31|def another_consumer_function() -> None: +32| """Another function that uses various shared components.""" +33| # Use shared constants +34| print(f"Using constant: {SHARED_CONSTANT}") +35| +36| # Use shared class with a different type parameter +37| shared = SharedClass[float]("another example", 3.14) +38| +39| # Use methods from shared class +40| name = shared.get_name() +41| value = shared.get_value() +42| print(f"Name: {name}, Value: {value}") +43| +44| # Use our own implementation +45| impl = AnotherImplementation() +46| result = impl.do_something() +47| print(f"Implementation result: {result}") +48| +49| # Use helper function +50| output = helper_function("another direct call") +51| print(f"Helper output: {output}") +52| +53| # Use enum-like class with a different color +54| color = Color.GREEN +55| print(f"Selected color: {color}") + +=== +/TEST_OUTPUT/workspace/consumer.py +References in File: 2 +=== +Reference at Line 38, Column 15: +35|def consumer_function() -> None: +36| """Function that consumes the helper functions.""" +37| # Use the helper function +38| message = helper_function("World") +39| print(message) +40| +41| # Get and process items from the helper +42| items = get_items() +43| for item in items: +44| print(f"Processing {item}") +45| +46| # Use the shared class +47| shared = SharedClass[str]("consumer", SHARED_CONSTANT) +48| print(f"Using shared class: {shared.get_name()} - {shared.get_value()}") +49| +50| # Use our implementation of the shared interface +51| impl = MyImplementation() +52| result = impl.process(items) +53| print(f"Processed items: {result}") +54| +55| # Use the enum +56| color = Color.RED +57| print(f"Selected color: {color}") + +=== +/TEST_OUTPUT/workspace/consumer_clean.py +References in File: 2 +=== +Reference at Line 10, Column 15: + 7|def consumer_function() -> None: + 8| """Function that consumes the helper functions.""" + 9| # Use the helper function +10| message = helper_function("World") +11| print(message) +12| +13| # Get and process items from the helper +14| items = get_items() +15| for item in items: +16| print(f"Processing {item}") + diff --git a/integrationtests/fixtures/snapshots/python/references/interface-method.snap b/integrationtests/fixtures/snapshots/python/references/interface-method.snap new file mode 100644 index 0000000..9873d65 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/references/interface-method.snap @@ -0,0 +1,29 @@ +=== +/TEST_OUTPUT/workspace/consumer.py +References in File: 1 +=== +Reference at Line 52, Column 19: +35|def consumer_function() -> None: +36| """Function that consumes the helper functions.""" +37| # Use the helper function +38| message = helper_function("World") +39| print(message) +40| +41| # Get and process items from the helper +42| items = get_items() +43| for item in items: +44| print(f"Processing {item}") +45| +46| # Use the shared class +47| shared = SharedClass[str]("consumer", SHARED_CONSTANT) +48| print(f"Using shared class: {shared.get_name()} - {shared.get_value()}") +49| +50| # Use our implementation of the shared interface +51| impl = MyImplementation() +52| result = impl.process(items) +53| print(f"Processed items: {result}") +54| +55| # Use the enum +56| color = Color.RED +57| print(f"Selected color: {color}") + diff --git a/integrationtests/fixtures/snapshots/python/references/shared-class.snap b/integrationtests/fixtures/snapshots/python/references/shared-class.snap new file mode 100644 index 0000000..d82821b --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/references/shared-class.snap @@ -0,0 +1,81 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.py +References in File: 3 +=== +Reference at Line 16, Column 23: +11|class AnotherImplementation: +12| """A class that uses shared components but doesn't implement interfaces.""" +13| +14| def __init__(self): +15| """Initialize the implementation.""" +16| self.shared = SharedClass[str]("another", SHARED_CONSTANT) +17| +18| def do_something(self) -> str: +19| """Do something with the shared components. +20| +21| Returns: +22| The processed result +23| """ +24| # Get the value from shared class +25| value = self.shared.get_value() +26| +27| # Process it using the helper function +28| return helper_function(value) + + +Reference at Line 37, Column 14: +31|def another_consumer_function() -> None: +32| """Another function that uses various shared components.""" +33| # Use shared constants +34| print(f"Using constant: {SHARED_CONSTANT}") +35| +36| # Use shared class with a different type parameter +37| shared = SharedClass[float]("another example", 3.14) +38| +39| # Use methods from shared class +40| name = shared.get_name() +41| value = shared.get_value() +42| print(f"Name: {name}, Value: {value}") +43| +44| # Use our own implementation +45| impl = AnotherImplementation() +46| result = impl.do_something() +47| print(f"Implementation result: {result}") +48| +49| # Use helper function +50| output = helper_function("another direct call") +51| print(f"Helper output: {output}") +52| +53| # Use enum-like class with a different color +54| color = Color.GREEN +55| print(f"Selected color: {color}") + +=== +/TEST_OUTPUT/workspace/consumer.py +References in File: 2 +=== +Reference at Line 47, Column 14: +35|def consumer_function() -> None: +36| """Function that consumes the helper functions.""" +37| # Use the helper function +38| message = helper_function("World") +39| print(message) +40| +41| # Get and process items from the helper +42| items = get_items() +43| for item in items: +44| print(f"Processing {item}") +45| +46| # Use the shared class +47| shared = SharedClass[str]("consumer", SHARED_CONSTANT) +48| print(f"Using shared class: {shared.get_name()} - {shared.get_value()}") +49| +50| # Use our implementation of the shared interface +51| impl = MyImplementation() +52| result = impl.process(items) +53| print(f"Processed items: {result}") +54| +55| # Use the enum +56| color = Color.RED +57| print(f"Selected color: {color}") + diff --git a/integrationtests/fixtures/snapshots/python/references/shared-constant.snap b/integrationtests/fixtures/snapshots/python/references/shared-constant.snap new file mode 100644 index 0000000..9c679d4 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/references/shared-constant.snap @@ -0,0 +1,81 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.py +References in File: 3 +=== +Reference at Line 16, Column 51: +11|class AnotherImplementation: +12| """A class that uses shared components but doesn't implement interfaces.""" +13| +14| def __init__(self): +15| """Initialize the implementation.""" +16| self.shared = SharedClass[str]("another", SHARED_CONSTANT) +17| +18| def do_something(self) -> str: +19| """Do something with the shared components. +20| +21| Returns: +22| The processed result +23| """ +24| # Get the value from shared class +25| value = self.shared.get_value() +26| +27| # Process it using the helper function +28| return helper_function(value) + + +Reference at Line 34, Column 30: +31|def another_consumer_function() -> None: +32| """Another function that uses various shared components.""" +33| # Use shared constants +34| print(f"Using constant: {SHARED_CONSTANT}") +35| +36| # Use shared class with a different type parameter +37| shared = SharedClass[float]("another example", 3.14) +38| +39| # Use methods from shared class +40| name = shared.get_name() +41| value = shared.get_value() +42| print(f"Name: {name}, Value: {value}") +43| +44| # Use our own implementation +45| impl = AnotherImplementation() +46| result = impl.do_something() +47| print(f"Implementation result: {result}") +48| +49| # Use helper function +50| output = helper_function("another direct call") +51| print(f"Helper output: {output}") +52| +53| # Use enum-like class with a different color +54| color = Color.GREEN +55| print(f"Selected color: {color}") + +=== +/TEST_OUTPUT/workspace/consumer.py +References in File: 2 +=== +Reference at Line 47, Column 43: +35|def consumer_function() -> None: +36| """Function that consumes the helper functions.""" +37| # Use the helper function +38| message = helper_function("World") +39| print(message) +40| +41| # Get and process items from the helper +42| items = get_items() +43| for item in items: +44| print(f"Processing {item}") +45| +46| # Use the shared class +47| shared = SharedClass[str]("consumer", SHARED_CONSTANT) +48| print(f"Using shared class: {shared.get_name()} - {shared.get_value()}") +49| +50| # Use our implementation of the shared interface +51| impl = MyImplementation() +52| result = impl.process(items) +53| print(f"Processed items: {result}") +54| +55| # Use the enum +56| color = Color.RED +57| print(f"Selected color: {color}") + diff --git a/integrationtests/fixtures/snapshots/python/references/shared-interface.snap b/integrationtests/fixtures/snapshots/python/references/shared-interface.snap new file mode 100644 index 0000000..cc76489 --- /dev/null +++ b/integrationtests/fixtures/snapshots/python/references/shared-interface.snap @@ -0,0 +1,25 @@ +=== +/TEST_OUTPUT/workspace/consumer.py +References in File: 2 +=== +Reference at Line 14, Column 24: +14|class MyImplementation(SharedInterface): +15| """An implementation of the SharedInterface.""" +16| +17| def process(self, data: List[str]) -> Dict[str, int]: +18| """Process the given data by counting occurrences. +19| +20| Args: +21| data: List of strings to process +22| +23| Returns: +24| Dictionary with counts of each item +25| """ +26| result = {} +27| for item in data: +28| if item in result: +29| result[item] += 1 +30| else: +31| result[item] = 1 +32| return result + diff --git a/integrationtests/fixtures/snapshots/rust/definition/constant.snap b/integrationtests/fixtures/snapshots/rust/definition/constant.snap new file mode 100644 index 0000000..29fc8a3 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/constant.snap @@ -0,0 +1,10 @@ +=== +Symbol: TEST_CONSTANT +/TEST_OUTPUT/workspace/src/types.rs +Kind: Constant +Start Position: Line 3, Column 1 +End Position: Line 4, Column 55 +=== +3|// A simple constant +4|pub const TEST_CONSTANT: &str = "test constant value"; + diff --git a/integrationtests/fixtures/snapshots/rust/definition/foobar.snap b/integrationtests/fixtures/snapshots/rust/definition/foobar.snap new file mode 100644 index 0000000..876a14d --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/foobar.snap @@ -0,0 +1,13 @@ +=== +Symbol: foo_bar +/TEST_OUTPUT/workspace/src/main.rs +Kind: Function +Start Position: Line 8, Column 1 +End Position: Line 12, Column 2 +=== + 8|// FooBar is a simple function for testing + 9|fn foo_bar() -> String { +10| String::from("Hello, World!") +11| println!("Unreachable code"); // This is unreachable code +12|} + diff --git a/integrationtests/fixtures/snapshots/rust/definition/function.snap b/integrationtests/fixtures/snapshots/rust/definition/function.snap new file mode 100644 index 0000000..411961a --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/function.snap @@ -0,0 +1,12 @@ +=== +Symbol: test_function +/TEST_OUTPUT/workspace/src/types.rs +Kind: Function +Start Position: Line 80, Column 1 +End Position: Line 83, Column 2 +=== +80|// A simple function for testing +81|pub fn test_function() -> String { +82| String::from("test function") +83|} + diff --git a/integrationtests/fixtures/snapshots/rust/definition/interface.snap b/integrationtests/fixtures/snapshots/rust/definition/interface.snap new file mode 100644 index 0000000..7f68a37 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/interface.snap @@ -0,0 +1,13 @@ +=== +Symbol: TestInterface +/TEST_OUTPUT/workspace/src/types.rs +Kind: Interface +Start Position: Line 32, Column 1 +End Position: Line 36, Column 2 +=== +32|// An interface (trait) for testing +33|pub trait TestInterface { +34| fn get_name(&self) -> String; +35| fn get_value(&self) -> i32; +36|} + diff --git a/integrationtests/fixtures/snapshots/rust/definition/method.snap b/integrationtests/fixtures/snapshots/rust/definition/method.snap new file mode 100644 index 0000000..d84ac14 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/method.snap @@ -0,0 +1,42 @@ +=== +Symbol: method +/TEST_OUTPUT/workspace/src/types.rs +Kind: Function +Container Name: TestStruct +Start Position: Line 18, Column 1 +End Position: Line 30, Column 2 +=== +18|// Implementation for TestStruct +19|impl TestStruct { +20| pub fn new(name: &str, value: i32) -> Self { +21| TestStruct { +22| name: String::from(name), +23| value, +24| } +25| } +26| +27| pub fn method(&self) -> String { +28| format!("{}: {}", self.name, self.value) +29| } +30|} + +=== +Symbol: method +/TEST_OUTPUT/workspace/src/types.rs +Kind: Function +Container Name: SharedStruct +Start Position: Line 54, Column 1 +End Position: Line 64, Column 2 +=== +54|impl SharedStruct { +55| pub fn new(name: &str) -> Self { +56| SharedStruct { +57| name: String::from(name), +58| } +59| } +60| +61| pub fn method(&self) -> String { +62| format!("SharedStruct: {}", self.name) +63| } +64|} + diff --git a/integrationtests/fixtures/snapshots/rust/definition/struct.snap b/integrationtests/fixtures/snapshots/rust/definition/struct.snap new file mode 100644 index 0000000..a8f241b --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/struct.snap @@ -0,0 +1,13 @@ +=== +Symbol: TestStruct +/TEST_OUTPUT/workspace/src/types.rs +Kind: Struct +Start Position: Line 12, Column 1 +End Position: Line 16, Column 2 +=== +12|// A struct for testing +13|pub struct TestStruct { +14| pub name: String, +15| pub value: i32, +16|} + diff --git a/integrationtests/fixtures/snapshots/rust/definition/type.snap b/integrationtests/fixtures/snapshots/rust/definition/type.snap new file mode 100644 index 0000000..970ec4e --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/type.snap @@ -0,0 +1,10 @@ +=== +Symbol: TestType +/TEST_OUTPUT/workspace/src/types.rs +Kind: TypeParameter +Start Position: Line 9, Column 1 +End Position: Line 10, Column 28 +=== + 9|// A simple type alias +10|pub type TestType = String; + diff --git a/integrationtests/fixtures/snapshots/rust/definition/variable.snap b/integrationtests/fixtures/snapshots/rust/definition/variable.snap new file mode 100644 index 0000000..b0e0ca3 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/definition/variable.snap @@ -0,0 +1,10 @@ +=== +Symbol: TEST_VARIABLE +/TEST_OUTPUT/workspace/src/types.rs +Kind: Constant +Start Position: Line 6, Column 1 +End Position: Line 7, Column 56 +=== +6|// A simple variable +7|pub static TEST_VARIABLE: &str = "test variable value"; + diff --git a/integrationtests/fixtures/snapshots/rust/diagnostics/clean.snap b/integrationtests/fixtures/snapshots/rust/diagnostics/clean.snap new file mode 100644 index 0000000..3a26952 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/diagnostics/clean.snap @@ -0,0 +1 @@ +/TEST_OUTPUT/workspace/src/clean.rs \ No newline at end of file diff --git a/integrationtests/fixtures/snapshots/rust/diagnostics/dependency.snap b/integrationtests/fixtures/snapshots/rust/diagnostics/dependency.snap new file mode 100644 index 0000000..f3c233a --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/diagnostics/dependency.snap @@ -0,0 +1,28 @@ +=== +/TEST_OUTPUT/workspace/src/consumer.rs +Location: Line 9, Column 33 +Message: expected 1 argument, found 0 +Source: rust-analyzer +Code: E0107 +=== + 7|pub fn consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("test"); +14| println!("Struct method: {}", s.method()); +15| +16| // Use shared interface +17| let iface: &dyn SharedInterface = &s; +18| println!("Interface method: {}", iface.get_name()); +19| +20| // Use shared constant +21| println!("Constant: {}", SHARED_CONSTANT); +22| +23| // Use shared type +24| let t: SharedType = String::from("test"); +25| println!("Type: {}", t); +26|} + diff --git a/integrationtests/fixtures/snapshots/rust/diagnostics/unreachable.snap b/integrationtests/fixtures/snapshots/rust/diagnostics/unreachable.snap new file mode 100644 index 0000000..1c5fa33 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/diagnostics/unreachable.snap @@ -0,0 +1,76 @@ +=== +/TEST_OUTPUT/workspace/src/main.rs +Location: Line 10, Column 34 +Message: Syntax Error: expected SEMICOLON +Source: rust-analyzer +Code: syntax-error +=== + 8|// FooBar is a simple function for testing + 9|fn foo_bar() -> String { +10| String::from("Hello, World!") +11| println!("Unreachable code"); // This is unreachable code +12|} + +=== +/TEST_OUTPUT/workspace/src/main.rs +Location: Line 10, Column 34 +Message: expected `;`, found `println` +Source: rustc +=== + 8|// FooBar is a simple function for testing + 9|fn foo_bar() -> String { +10| String::from("Hello, World!") +11| println!("Unreachable code"); // This is unreachable code +12|} + +=== +/TEST_OUTPUT/workspace/src/main.rs +Location: Line 11, Column 5 +Message: unexpected token +Source: rustc +=== + 8|// FooBar is a simple function for testing + 9|fn foo_bar() -> String { +10| String::from("Hello, World!") +11| println!("Unreachable code"); // This is unreachable code +12|} + +=== +/TEST_OUTPUT/workspace/src/main.rs +Location: Line 10, Column 34 +Message: add `;` here: `;` +Source: rustc +=== + 8|// FooBar is a simple function for testing + 9|fn foo_bar() -> String { +10| String::from("Hello, World!") +11| println!("Unreachable code"); // This is unreachable code +12|} + +=== +/TEST_OUTPUT/workspace/src/main.rs +Location: Line 9, Column 17 +Message: mismatched types +expected `String`, found `()` +Source: rustc +Code: E0308 +=== + 8|// FooBar is a simple function for testing + 9|fn foo_bar() -> String { +10| String::from("Hello, World!") +11| println!("Unreachable code"); // This is unreachable code +12|} + +=== +/TEST_OUTPUT/workspace/src/main.rs +Location: Line 9, Column 4 +Message: implicitly returns `()` as its body has no tail or `return` expression +Source: rustc +Code: E0308 +=== + 8|// FooBar is a simple function for testing + 9|fn foo_bar() -> String { +10| String::from("Hello, World!") +11| println!("Unreachable code"); // This is unreachable code +12|} + diff --git a/integrationtests/fixtures/snapshots/rust/references/foobar-function.snap b/integrationtests/fixtures/snapshots/rust/references/foobar-function.snap new file mode 100644 index 0000000..372d465 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/foobar-function.snap @@ -0,0 +1,9 @@ +=== +/TEST_OUTPUT/workspace/src/main.rs +References in File: 1 +=== +Reference at Line 15, Column 20: +14|fn main() { +15| println!("{}", foo_bar()); +16|} + diff --git a/integrationtests/fixtures/snapshots/rust/references/helper-function.snap b/integrationtests/fixtures/snapshots/rust/references/helper-function.snap new file mode 100644 index 0000000..82aca75 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/helper-function.snap @@ -0,0 +1,50 @@ +=== +/TEST_OUTPUT/workspace/src/another_consumer.rs +References in File: 2 +=== +Reference at Line 9, Column 18: + 7|pub fn another_consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result from another consumer: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("another test"); +14| println!("Struct in another consumer: {}", s.name); +15| +16| // Use shared interface +17| let _iface: &dyn SharedInterface = &s; +18| +19| // Use shared constant +20| println!("Constant in another consumer: {}", SHARED_CONSTANT); +21| +22| // Use shared type +23| let _t: SharedType = String::from("another test"); +24|} + +=== +/TEST_OUTPUT/workspace/src/consumer.rs +References in File: 2 +=== +Reference at Line 9, Column 18: + 7|pub fn consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("test"); +14| println!("Struct method: {}", s.method()); +15| +16| // Use shared interface +17| let iface: &dyn SharedInterface = &s; +18| println!("Interface method: {}", iface.get_name()); +19| +20| // Use shared constant +21| println!("Constant: {}", SHARED_CONSTANT); +22| +23| // Use shared type +24| let t: SharedType = String::from("test"); +25| println!("Type: {}", t); +26|} + diff --git a/integrationtests/fixtures/snapshots/rust/references/interface-method.snap b/integrationtests/fixtures/snapshots/rust/references/interface-method.snap new file mode 100644 index 0000000..99d793e --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/interface-method.snap @@ -0,0 +1,3 @@ +=== +No references found for symbol: SharedInterface::get_name +=== diff --git a/integrationtests/fixtures/snapshots/rust/references/shared-constant.snap b/integrationtests/fixtures/snapshots/rust/references/shared-constant.snap new file mode 100644 index 0000000..22d76c0 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/shared-constant.snap @@ -0,0 +1,50 @@ +=== +/TEST_OUTPUT/workspace/src/another_consumer.rs +References in File: 2 +=== +Reference at Line 20, Column 50: + 7|pub fn another_consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result from another consumer: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("another test"); +14| println!("Struct in another consumer: {}", s.name); +15| +16| // Use shared interface +17| let _iface: &dyn SharedInterface = &s; +18| +19| // Use shared constant +20| println!("Constant in another consumer: {}", SHARED_CONSTANT); +21| +22| // Use shared type +23| let _t: SharedType = String::from("another test"); +24|} + +=== +/TEST_OUTPUT/workspace/src/consumer.rs +References in File: 2 +=== +Reference at Line 21, Column 30: + 7|pub fn consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("test"); +14| println!("Struct method: {}", s.method()); +15| +16| // Use shared interface +17| let iface: &dyn SharedInterface = &s; +18| println!("Interface method: {}", iface.get_name()); +19| +20| // Use shared constant +21| println!("Constant: {}", SHARED_CONSTANT); +22| +23| // Use shared type +24| let t: SharedType = String::from("test"); +25| println!("Type: {}", t); +26|} + diff --git a/integrationtests/fixtures/snapshots/rust/references/shared-interface.snap b/integrationtests/fixtures/snapshots/rust/references/shared-interface.snap new file mode 100644 index 0000000..438e440 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/shared-interface.snap @@ -0,0 +1,61 @@ +=== +/TEST_OUTPUT/workspace/src/another_consumer.rs +References in File: 2 +=== +Reference at Line 17, Column 22: + 7|pub fn another_consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result from another consumer: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("another test"); +14| println!("Struct in another consumer: {}", s.name); +15| +16| // Use shared interface +17| let _iface: &dyn SharedInterface = &s; +18| +19| // Use shared constant +20| println!("Constant in another consumer: {}", SHARED_CONSTANT); +21| +22| // Use shared type +23| let _t: SharedType = String::from("another test"); +24|} + +=== +/TEST_OUTPUT/workspace/src/consumer.rs +References in File: 2 +=== +Reference at Line 17, Column 21: + 7|pub fn consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("test"); +14| println!("Struct method: {}", s.method()); +15| +16| // Use shared interface +17| let iface: &dyn SharedInterface = &s; +18| println!("Interface method: {}", iface.get_name()); +19| +20| // Use shared constant +21| println!("Constant: {}", SHARED_CONSTANT); +22| +23| // Use shared type +24| let t: SharedType = String::from("test"); +25| println!("Type: {}", t); +26|} + +=== +/TEST_OUTPUT/workspace/src/types.rs +References in File: 1 +=== +Reference at Line 70, Column 6: +70|impl SharedInterface for SharedStruct { +71| fn get_name(&self) -> String { +72| self.name.clone() +73| } +74|} + diff --git a/integrationtests/fixtures/snapshots/rust/references/shared-struct.snap b/integrationtests/fixtures/snapshots/rust/references/shared-struct.snap new file mode 100644 index 0000000..e07b4a2 --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/shared-struct.snap @@ -0,0 +1,103 @@ +=== +/TEST_OUTPUT/workspace/src/another_consumer.rs +References in File: 2 +=== +Reference at Line 13, Column 13: + 7|pub fn another_consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result from another consumer: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("another test"); +14| println!("Struct in another consumer: {}", s.name); +15| +16| // Use shared interface +17| let _iface: &dyn SharedInterface = &s; +18| +19| // Use shared constant +20| println!("Constant in another consumer: {}", SHARED_CONSTANT); +21| +22| // Use shared type +23| let _t: SharedType = String::from("another test"); +24|} + +=== +/TEST_OUTPUT/workspace/src/consumer.rs +References in File: 2 +=== +Reference at Line 13, Column 13: + 7|pub fn consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("test"); +14| println!("Struct method: {}", s.method()); +15| +16| // Use shared interface +17| let iface: &dyn SharedInterface = &s; +18| println!("Interface method: {}", iface.get_name()); +19| +20| // Use shared constant +21| println!("Constant: {}", SHARED_CONSTANT); +22| +23| // Use shared type +24| let t: SharedType = String::from("test"); +25| println!("Type: {}", t); +26|} + +=== +/TEST_OUTPUT/workspace/src/types.rs +References in File: 4 +=== +Reference at Line 54, Column 6: +54|impl SharedStruct { +55| pub fn new(name: &str) -> Self { +56| SharedStruct { +57| name: String::from(name), +58| } +59| } +60| +61| pub fn method(&self) -> String { +62| format!("SharedStruct: {}", self.name) +63| } +64|} + + +Reference at Line 56, Column 9: +54|impl SharedStruct { +55| pub fn new(name: &str) -> Self { +56| SharedStruct { +57| name: String::from(name), +58| } +59| } +60| +61| pub fn method(&self) -> String { +62| format!("SharedStruct: {}", self.name) +63| } +64|} + + +Reference at Line 70, Column 26: +70|impl SharedInterface for SharedStruct { +71| fn get_name(&self) -> String { +72| self.name.clone() +73| } +74|} + + +Reference at Line 55, Column 31: +54|impl SharedStruct { +55| pub fn new(name: &str) -> Self { +56| SharedStruct { +57| name: String::from(name), +58| } +59| } +60| +61| pub fn method(&self) -> String { +62| format!("SharedStruct: {}", self.name) +63| } +64|} + diff --git a/integrationtests/fixtures/snapshots/rust/references/shared-type.snap b/integrationtests/fixtures/snapshots/rust/references/shared-type.snap new file mode 100644 index 0000000..acac77a --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/shared-type.snap @@ -0,0 +1,50 @@ +=== +/TEST_OUTPUT/workspace/src/another_consumer.rs +References in File: 2 +=== +Reference at Line 23, Column 13: + 7|pub fn another_consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result from another consumer: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("another test"); +14| println!("Struct in another consumer: {}", s.name); +15| +16| // Use shared interface +17| let _iface: &dyn SharedInterface = &s; +18| +19| // Use shared constant +20| println!("Constant in another consumer: {}", SHARED_CONSTANT); +21| +22| // Use shared type +23| let _t: SharedType = String::from("another test"); +24|} + +=== +/TEST_OUTPUT/workspace/src/consumer.rs +References in File: 2 +=== +Reference at Line 24, Column 12: + 7|pub fn consumer_function() { + 8| // Use the helper function + 9| let result = helper_function(); +10| println!("Helper result: {}", result); +11| +12| // Use shared struct +13| let s = SharedStruct::new("test"); +14| println!("Struct method: {}", s.method()); +15| +16| // Use shared interface +17| let iface: &dyn SharedInterface = &s; +18| println!("Interface method: {}", iface.get_name()); +19| +20| // Use shared constant +21| println!("Constant: {}", SHARED_CONSTANT); +22| +23| // Use shared type +24| let t: SharedType = String::from("test"); +25| println!("Type: {}", t); +26|} + diff --git a/integrationtests/fixtures/snapshots/rust/references/struct-method.snap b/integrationtests/fixtures/snapshots/rust/references/struct-method.snap new file mode 100644 index 0000000..b700ace --- /dev/null +++ b/integrationtests/fixtures/snapshots/rust/references/struct-method.snap @@ -0,0 +1,3 @@ +=== +No references found for symbol: SharedStruct::method +=== diff --git a/integrationtests/fixtures/snapshots/typescript/definition/class.snap b/integrationtests/fixtures/snapshots/typescript/definition/class.snap new file mode 100644 index 0000000..fadcfdf --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/definition/class.snap @@ -0,0 +1,19 @@ +=== +Symbol: TestClass +/TEST_OUTPUT/workspace/main.ts +Kind: Class +Start Position: Line 14, Column 1 +End Position: Line 24, Column 2 +=== +14|export class TestClass implements TestInterface { +15| property: string; +16| +17| constructor() { +18| this.property = "test"; +19| } +20| +21| method(): void { +22| console.log("Method called"); +23| } +24|} + diff --git a/integrationtests/fixtures/snapshots/typescript/definition/constant.snap b/integrationtests/fixtures/snapshots/typescript/definition/constant.snap new file mode 100644 index 0000000..1d35994 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/definition/constant.snap @@ -0,0 +1,9 @@ +=== +Symbol: TestConstant +/TEST_OUTPUT/workspace/main.ts +Kind: Constant +Start Position: Line 33, Column 1 +End Position: Line 33, Column 31 +=== +33|export const TestConstant = 42; + diff --git a/integrationtests/fixtures/snapshots/typescript/definition/function.snap b/integrationtests/fixtures/snapshots/typescript/definition/function.snap new file mode 100644 index 0000000..1051942 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/definition/function.snap @@ -0,0 +1,12 @@ +=== +Symbol: TestFunction +/TEST_OUTPUT/workspace/main.ts +Kind: Function +Start Position: Line 2, Column 1 +End Position: Line 5, Column 2 +=== +2|export function TestFunction(): string { +3| return "Hello, World!"; +4| console.log("Unreachable code"); // This is unreachable code +5|} + diff --git a/integrationtests/fixtures/snapshots/typescript/definition/interface.snap b/integrationtests/fixtures/snapshots/typescript/definition/interface.snap new file mode 100644 index 0000000..3992e86 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/definition/interface.snap @@ -0,0 +1,12 @@ +=== +Symbol: TestInterface +/TEST_OUTPUT/workspace/main.ts +Kind: Interface +Start Position: Line 8, Column 1 +End Position: Line 11, Column 2 +=== + 8|export interface TestInterface { + 9| method(): void; +10| property: string; +11|} + diff --git a/integrationtests/fixtures/snapshots/typescript/definition/type.snap b/integrationtests/fixtures/snapshots/typescript/definition/type.snap new file mode 100644 index 0000000..4406c38 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/definition/type.snap @@ -0,0 +1,9 @@ +=== +Symbol: TestType +/TEST_OUTPUT/workspace/main.ts +Kind: Variable +Start Position: Line 27, Column 1 +End Position: Line 27, Column 40 +=== +27|export type TestType = string | number; + diff --git a/integrationtests/fixtures/snapshots/typescript/definition/variable.snap b/integrationtests/fixtures/snapshots/typescript/definition/variable.snap new file mode 100644 index 0000000..ff4f996 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/definition/variable.snap @@ -0,0 +1,9 @@ +=== +Symbol: TestVariable +/TEST_OUTPUT/workspace/main.ts +Kind: Constant +Start Position: Line 30, Column 1 +End Position: Line 30, Column 43 +=== +30|export const TestVariable: string = "Test"; + diff --git a/integrationtests/fixtures/snapshots/typescript/diagnostics/clean.snap b/integrationtests/fixtures/snapshots/typescript/diagnostics/clean.snap new file mode 100644 index 0000000..e56dbc7 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/diagnostics/clean.snap @@ -0,0 +1 @@ +/TEST_OUTPUT/workspace/clean.ts \ No newline at end of file diff --git a/integrationtests/fixtures/snapshots/typescript/diagnostics/dependency.snap b/integrationtests/fixtures/snapshots/typescript/diagnostics/dependency.snap new file mode 100644 index 0000000..525c505 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/diagnostics/dependency.snap @@ -0,0 +1,32 @@ +=== +/TEST_OUTPUT/workspace/consumer.ts +Location: Line 13, Column 36 +Message: Expected 1 arguments, but got 0. +Source: typescript +Code: 2554 +=== +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + diff --git a/integrationtests/fixtures/snapshots/typescript/diagnostics/type-error.snap b/integrationtests/fixtures/snapshots/typescript/diagnostics/type-error.snap new file mode 100644 index 0000000..8199ef5 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/diagnostics/type-error.snap @@ -0,0 +1,11 @@ +=== +/TEST_OUTPUT/workspace/error.ts +Location: Line 4, Column 3 +Message: Type 'number' is not assignable to type 'string'. +Source: typescript +Code: 2322 +=== +3|function errorFunction(x: number): string { +4| return x; // Error: Type 'number' is not assignable to type 'string' +5|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/class-method.snap b/integrationtests/fixtures/snapshots/typescript/references/class-method.snap new file mode 100644 index 0000000..7f43222 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/class-method.snap @@ -0,0 +1,30 @@ +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 1 +=== +Reference at Line 18, Column 12: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/interface-method.snap b/integrationtests/fixtures/snapshots/typescript/references/interface-method.snap new file mode 100644 index 0000000..14c3830 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/interface-method.snap @@ -0,0 +1,209 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 1 +=== +Reference at Line 21, Column 5: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 2 +=== +Reference at Line 22, Column 21: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + + +Reference at Line 17, Column 24: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + +=== +/TEST_OUTPUT/workspace/helper.ts +References in File: 1 +=== +Reference at Line 22, Column 3: +15|export class SharedClass implements SharedInterface { +16| private name: string; +17| +18| constructor(name: string) { +19| this.name = name; +20| } +21| +22| getName(): string { +23| return this.name; +24| } +25| +26| getValue(): number { +27| return 42; +28| } +29| +30| helperMethod(): void { +31| console.log("Helper method called"); +32| } +33|} + +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 1 +=== +Reference at Line 21, Column 5: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 2 +=== +Reference at Line 22, Column 21: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + + +Reference at Line 17, Column 24: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + +=== +/TEST_OUTPUT/workspace/helper.ts +References in File: 1 +=== +Reference at Line 10, Column 3: + 9|export interface SharedInterface { +10| getName(): string; +11| getValue(): number; +12|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/shared-class.snap b/integrationtests/fixtures/snapshots/typescript/references/shared-class.snap new file mode 100644 index 0000000..9f4162f --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/shared-class.snap @@ -0,0 +1,60 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 2 +=== +Reference at Line 17, Column 24: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 2 +=== +Reference at Line 16, Column 24: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/shared-constant.snap b/integrationtests/fixtures/snapshots/typescript/references/shared-constant.snap new file mode 100644 index 0000000..73b42dd --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/shared-constant.snap @@ -0,0 +1,60 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 2 +=== +Reference at Line 29, Column 30: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 2 +=== +Reference at Line 31, Column 15: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/shared-enum.snap b/integrationtests/fixtures/snapshots/typescript/references/shared-enum.snap new file mode 100644 index 0000000..ba42341 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/shared-enum.snap @@ -0,0 +1,114 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 4 +=== +Reference at Line 32, Column 23: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + + +Reference at Line 32, Column 39: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + + +Reference at Line 32, Column 55: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 2 +=== +Reference at Line 34, Column 15: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/shared-function.snap b/integrationtests/fixtures/snapshots/typescript/references/shared-function.snap new file mode 100644 index 0000000..35a5b45 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/shared-function.snap @@ -0,0 +1,60 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 2 +=== +Reference at Line 13, Column 18: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 2 +=== +Reference at Line 13, Column 36: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/shared-interface.snap b/integrationtests/fixtures/snapshots/typescript/references/shared-interface.snap new file mode 100644 index 0000000..b21642b --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/shared-interface.snap @@ -0,0 +1,85 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 2 +=== +Reference at Line 20, Column 16: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 2 +=== +Reference at Line 21, Column 16: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + +=== +/TEST_OUTPUT/workspace/helper.ts +References in File: 1 +=== +Reference at Line 15, Column 37: +15|export class SharedClass implements SharedInterface { +16| private name: string; +17| +18| constructor(name: string) { +19| this.name = name; +20| } +21| +22| getName(): string { +23| return this.name; +24| } +25| +26| getValue(): number { +27| return 42; +28| } +29| +30| helperMethod(): void { +31| console.log("Helper method called"); +32| } +33|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/shared-type.snap b/integrationtests/fixtures/snapshots/typescript/references/shared-type.snap new file mode 100644 index 0000000..4ed0143 --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/shared-type.snap @@ -0,0 +1,87 @@ +=== +/TEST_OUTPUT/workspace/another_consumer.ts +References in File: 2 +=== +Reference at Line 26, Column 21: +12|export function AnotherConsumerFunction(): void { +13| const result = SharedFunction(); +14| console.log(`Result from shared function: ${result}`); +15| +16| // Using SharedClass differently +17| const instance = new SharedClass("another instance"); +18| +19| // Using SharedInterface +20| const iface: SharedInterface = { +21| getName: () => "custom implementation", +22| getValue: () => 100 +23| }; +24| +25| // Using SharedType +26| const mixedArray: SharedType[] = ["string", 42, "another"]; +27| +28| // Using SharedConstant +29| const prefixed = `PREFIX_${SharedConstant}`; +30| +31| // Using SharedEnum +32| const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; +33| +34| console.log(instance, iface, mixedArray, prefixed, enumValues); +35|} + +=== +/TEST_OUTPUT/workspace/consumer.ts +References in File: 3 +=== +Reference at Line 26, Column 16: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + + +Reference at Line 27, Column 19: +12|export function ConsumerFunction(): void { +13| console.log("Consumer calling:", SharedFunction()); +14| +15| // Using SharedClass +16| const instance = new SharedClass("test instance"); +17| console.log(instance.getName()); +18| instance.helperMethod(); +19| +20| // Using SharedInterface +21| const iface: SharedInterface = instance; +22| console.log(iface.getName()); +23| console.log(iface.getValue()); +24| +25| // Using SharedType +26| const value: SharedType = "string value"; +27| const numValue: SharedType = 42; +28| console.log(value, numValue); +29| +30| // Using SharedConstant +31| console.log(SharedConstant); +32| +33| // Using SharedEnum +34| console.log(SharedEnum.ONE); +35|} + diff --git a/integrationtests/fixtures/snapshots/typescript/references/test-function.snap b/integrationtests/fixtures/snapshots/typescript/references/test-function.snap new file mode 100644 index 0000000..6ac1c7d --- /dev/null +++ b/integrationtests/fixtures/snapshots/typescript/references/test-function.snap @@ -0,0 +1,11 @@ +=== +/TEST_OUTPUT/workspace/main.ts +References in File: 1 +=== +Reference at Line 37, Column 15: +36|function main() { +37| console.log(TestFunction()); +38| const instance = new TestClass(); +39| instance.method(); +40|} + diff --git a/integrationtests/languages/common/framework.go b/integrationtests/languages/common/framework.go index ba4932d..0d6eb1f 100644 --- a/integrationtests/languages/common/framework.go +++ b/integrationtests/languages/common/framework.go @@ -156,7 +156,6 @@ func (ts *TestSuite) Setup() error { ts.t.Logf("Copied workspace from %s to %s", ts.Config.WorkspaceDir, workspaceDir) // Create and initialize LSP client - // TODO: Extend lsp.Client to support custom IO for capturing logs client, err := lsp.NewClient(ts.Config.Command, ts.Config.Args...) if err != nil { return fmt.Errorf("failed to create LSP client: %w", err) diff --git a/integrationtests/languages/python/definition/definition_test.go b/integrationtests/languages/python/definition/definition_test.go new file mode 100644 index 0000000..f32da2c --- /dev/null +++ b/integrationtests/languages/python/definition/definition_test.go @@ -0,0 +1,97 @@ +package definition_test + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/python/internal" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestReadDefinition tests the ReadDefinition tool with various Python type definitions +func TestReadDefinition(t *testing.T) { + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + tests := []struct { + name string + symbolName string + expectedText string + snapshotName string + mayFail bool // Some symbols like methods might not be found by pyright + }{ + { + name: "Function", + symbolName: "test_function", + expectedText: "def test_function", + snapshotName: "function", + }, + { + name: "Class", + symbolName: "TestClass", + expectedText: "class TestClass", + snapshotName: "class", + }, + { + name: "Method", + symbolName: "test_method", // Try just the method name + expectedText: "def test_method", + snapshotName: "method", + mayFail: true, // This may fail as pyright might not find methods directly + }, + { + name: "StaticMethod", + symbolName: "static_method", // Try just the method name + expectedText: "def static_method", + snapshotName: "static-method", + mayFail: true, // This may fail as pyright might not find static methods directly + }, + { + name: "Constant", + symbolName: "TEST_CONSTANT", + expectedText: "TEST_CONSTANT", + snapshotName: "constant", + }, + { + name: "Variable", + symbolName: "test_variable", + expectedText: "test_variable", + snapshotName: "variable", + }, + { + name: "DerivedClass", + symbolName: "DerivedClass", + expectedText: "class DerivedClass", + snapshotName: "derived-class", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Call the ReadDefinition tool + result, err := tools.ReadDefinition(ctx, suite.Client, tc.symbolName, true) + if err != nil { + t.Fatalf("Failed to read definition: %v", err) + } + + // Check that the result contains relevant information + if !strings.Contains(result, tc.expectedText) && !tc.mayFail { + t.Errorf("Definition does not contain expected text: %s", tc.expectedText) + } + + // Skip further validation if we know this test might fail but + // continue with snapshot testing for future reference + if tc.mayFail && strings.Contains(result, "not found") { + t.Logf("Symbol might not be directly findable by pyright: %s", tc.symbolName) + } + + // Use snapshot testing to verify exact output + common.SnapshotTest(t, "python", "definition", tc.snapshotName, result) + }) + } +} diff --git a/integrationtests/languages/python/diagnostics/diagnostics_test.go b/integrationtests/languages/python/diagnostics/diagnostics_test.go new file mode 100644 index 0000000..b13c8e7 --- /dev/null +++ b/integrationtests/languages/python/diagnostics/diagnostics_test.go @@ -0,0 +1,209 @@ +package diagnostics_test + +import ( + "context" + "fmt" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/python/internal" + "github.com/isaacphi/mcp-language-server/internal/protocol" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestDiagnostics tests diagnostics functionality with the Python language server +func TestDiagnostics(t *testing.T) { + // Test with a clean file + t.Run("CleanFile", func(t *testing.T) { + // Get a test suite with clean code + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 5*time.Second) + defer cancel() + + // Check diagnostics for clean.py, which shouldn't have any errors + filePath := filepath.Join(suite.WorkspaceDir, "clean.py") + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, filePath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Verify we have no diagnostics + if !strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected no diagnostics but got: %s", result) + } + + common.SnapshotTest(t, "python", "diagnostics", "clean", result) + }) + + // Test with a file containing errors + t.Run("FileWithErrors", func(t *testing.T) { + // Get a test suite with code that contains errors + suite := internal.GetTestSuite(t) + + // Wait for diagnostics to be generated + time.Sleep(2 * time.Second) + + ctx, cancel := context.WithTimeout(suite.Context, 5*time.Second) + defer cancel() + + // Check diagnostics for error_file.py, which contains deliberate errors + filePath := filepath.Join(suite.WorkspaceDir, "error_file.py") + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, filePath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Verify we have diagnostics + if strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected diagnostics but got none") + } + + // Check for specific error types that should be detected + if !strings.Contains(result, "Type") && !strings.Contains(result, "type") && + !strings.Contains(result, "undefined") && !strings.Contains(result, "Undefined") { + t.Errorf("Expected type errors or undefined variable errors but got: %s", result) + } + + common.SnapshotTest(t, "python", "diagnostics", "errors", result) + }) + + // Test file dependency: helper.py provides a function, + // consumer_clean.py uses it, then modify helper.py to break consumer_clean.py + t.Run("FileDependency", func(t *testing.T) { + // Get a test suite with clean code + suite := internal.GetTestSuite(t) + + // Wait for initial diagnostics to be generated + time.Sleep(2 * time.Second) + + // Create context + ctx, cancel := context.WithTimeout(suite.Context, 5*time.Second) + defer cancel() + + // Ensure both helper.py and consumer_clean.py are open in the LSP + helperPath := filepath.Join(suite.WorkspaceDir, "helper.py") + consumerPath := filepath.Join(suite.WorkspaceDir, "consumer_clean.py") + + err := suite.Client.OpenFile(ctx, helperPath) + if err != nil { + t.Fatalf("Failed to open helper.py: %v", err) + } + + err = suite.Client.OpenFile(ctx, consumerPath) + if err != nil { + t.Fatalf("Failed to open consumer_clean.py: %v", err) + } + + // Wait for files to be processed + time.Sleep(2 * time.Second) + + // Get initial diagnostics for consumer_clean.py + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, consumerPath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Should have no diagnostics initially + if !strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected no diagnostics initially but got: %s", result) + } + + // Now modify the helper function to cause an error in the consumer + modifiedHelperContent := `"""Helper module that provides utility functions.""" + +from typing import List, Dict + + +def helper_function(name: str, age: int) -> str: + """A helper function that formats a greeting message. + + Args: + name: The name to greet + age: The age of the person + + Returns: + A formatted greeting message + """ + return f"Hello, {name}! You are {age} years old." + + +def get_items() -> List[str]: + """Get a list of sample items. + + Returns: + A list of sample strings + """ + return ["apple", "banana", "orange", "grape"]` + + // Write the modified content to the file + err = suite.WriteFile("helper.py", modifiedHelperContent) + if err != nil { + t.Fatalf("Failed to update helper.py: %v", err) + } + + // Explicitly notify the LSP server about the change + helperURI := fmt.Sprintf("file://%s", helperPath) + + // Notify the LSP server about the file change + err = suite.Client.NotifyChange(ctx, helperPath) + if err != nil { + t.Fatalf("Failed to notify change to helper.py: %v", err) + } + + // Also send a didChangeWatchedFiles notification for coverage + // This simulates what the watcher would do + fileChangeParams := protocol.DidChangeWatchedFilesParams{ + Changes: []protocol.FileEvent{ + { + URI: protocol.DocumentUri(helperURI), + Type: protocol.FileChangeType(protocol.Changed), + }, + }, + } + + err = suite.Client.DidChangeWatchedFiles(ctx, fileChangeParams) + if err != nil { + t.Fatalf("Failed to send DidChangeWatchedFiles: %v", err) + } + + // Wait for LSP to process the change + time.Sleep(3 * time.Second) + + // Force reopen the consumer file to ensure LSP reevaluates it + err = suite.Client.CloseFile(ctx, consumerPath) + if err != nil { + t.Fatalf("Failed to close consumer_clean.py: %v", err) + } + + err = suite.Client.OpenFile(ctx, consumerPath) + if err != nil { + t.Fatalf("Failed to reopen consumer_clean.py: %v", err) + } + + // Wait for diagnostics to be generated + time.Sleep(3 * time.Second) + + // Check diagnostics again on consumer file - should now have an error + result, err = tools.GetDiagnosticsForFile(ctx, suite.Client, consumerPath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed after dependency change: %v", err) + } + + // Should have diagnostics now + if strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected diagnostics after dependency change but got none") + } + + // Should contain an error about function arguments + if !strings.Contains(result, "argument") && !strings.Contains(result, "parameter") && + !strings.Contains(result, "missing") && !strings.Contains(result, "required") { + t.Errorf("Expected error about wrong arguments but got: %s", result) + } + + common.SnapshotTest(t, "python", "diagnostics", "dependency", result) + }) +} diff --git a/integrationtests/languages/python/internal/helpers.go b/integrationtests/languages/python/internal/helpers.go new file mode 100644 index 0000000..78806f5 --- /dev/null +++ b/integrationtests/languages/python/internal/helpers.go @@ -0,0 +1,42 @@ +// Package internal contains shared helpers for Python tests +package internal + +import ( + "path/filepath" + "testing" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" +) + +// GetTestSuite returns a test suite for Python language server tests +func GetTestSuite(t *testing.T) *common.TestSuite { + // Configure Python LSP (pyright) + repoRoot, err := filepath.Abs("../../../..") + if err != nil { + t.Fatalf("Failed to get repo root: %v", err) + } + + config := common.LSPTestConfig{ + Name: "python", + Command: "pyright-langserver", + Args: []string{"--stdio"}, + WorkspaceDir: filepath.Join(repoRoot, "integrationtests/workspaces/python"), + InitializeTimeMs: 2000, // 2 seconds + } + + // Create a test suite + suite := common.NewTestSuite(t, config) + + // Set up the suite + err = suite.Setup() + if err != nil { + t.Fatalf("Failed to set up test suite: %v", err) + } + + // Register cleanup + t.Cleanup(func() { + suite.Cleanup() + }) + + return suite +} diff --git a/integrationtests/languages/python/references/references_test.go b/integrationtests/languages/python/references/references_test.go new file mode 100644 index 0000000..954969e --- /dev/null +++ b/integrationtests/languages/python/references/references_test.go @@ -0,0 +1,120 @@ +package references_test + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/python/internal" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestFindReferences tests the FindReferences tool with Python symbols +// that have references across different files +func TestFindReferences(t *testing.T) { + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + tests := []struct { + name string + symbolName string + expectedText string + expectedFiles int // Number of files where references should be found + snapshotName string + }{ + { + name: "Function with references across files", + symbolName: "helper_function", + expectedText: "helper_function", + expectedFiles: 2, // consumer.py and another_consumer.py + snapshotName: "helper-function", + }, + { + name: "Class with references across files", + symbolName: "SharedClass", + expectedText: "SharedClass", + expectedFiles: 2, // consumer.py and another_consumer.py + snapshotName: "shared-class", + }, + { + name: "Method with references across files", + symbolName: "get_name", // Use the unqualified method name for Python + expectedText: "get_name", + expectedFiles: 2, // consumer.py and another_consumer.py + snapshotName: "class-method", + }, + { + name: "Interface with references across files", + symbolName: "SharedInterface", + expectedText: "SharedInterface", + expectedFiles: 1, // consumer.py + snapshotName: "shared-interface", + }, + { + name: "Interface method with references", + symbolName: "process", // Use the unqualified method name for Python + expectedText: "process", + expectedFiles: 1, // consumer.py + snapshotName: "interface-method", + }, + { + name: "Constant with references across files", + symbolName: "SHARED_CONSTANT", + expectedText: "SHARED_CONSTANT", + expectedFiles: 2, // consumer.py and another_consumer.py + snapshotName: "shared-constant", + }, + { + name: "Enum-like class with references across files", + symbolName: "Color", + expectedText: "Color", + expectedFiles: 2, // consumer.py and another_consumer.py + snapshotName: "color-enum", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Call the FindReferences tool + result, err := tools.FindReferences(ctx, suite.Client, tc.symbolName, true) + if err != nil { + t.Fatalf("Failed to find references: %v", err) + } + + // Check that the result contains relevant information + if !strings.Contains(result, tc.expectedText) { + t.Errorf("References do not contain expected text: %s", tc.expectedText) + } + + // Count how many different files are mentioned in the result + fileCount := countFilesInResult(result) + if fileCount < tc.expectedFiles { + t.Errorf("Expected references in at least %d files, but found in %d files", + tc.expectedFiles, fileCount) + } + + // Use snapshot testing to verify exact output + common.SnapshotTest(t, "python", "references", tc.snapshotName, result) + }) + } +} + +// countFilesInResult counts the number of unique files mentioned in the result +func countFilesInResult(result string) int { + fileMap := make(map[string]bool) + + // Any line containing "workspace" and ".py" is a file path + for line := range strings.SplitSeq(result, "\n") { + if strings.Contains(line, "workspace") && strings.Contains(line, ".py") { + if !strings.Contains(line, "References in File") { + fileMap[line] = true + } + } + } + + return len(fileMap) +} diff --git a/integrationtests/languages/rust/definition/definition_test.go b/integrationtests/languages/rust/definition/definition_test.go new file mode 100644 index 0000000..478a255 --- /dev/null +++ b/integrationtests/languages/rust/definition/definition_test.go @@ -0,0 +1,120 @@ +package definition_test + +import ( + "context" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/rust/internal" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestReadDefinition tests the ReadDefinition tool with various Rust type definitions +func TestReadDefinition(t *testing.T) { + // Helper function to open all files and wait for indexing + openAllFilesAndWait := func(suite *common.TestSuite, ctx context.Context) { + // Open all files to ensure rust-analyzer indexes everything + filesToOpen := []string{ + "src/main.rs", + "src/types.rs", + "src/helper.rs", + "src/consumer.rs", + "src/another_consumer.rs", + "src/clean.rs", + } + + for _, file := range filesToOpen { + filePath := filepath.Join(suite.WorkspaceDir, file) + err := suite.Client.OpenFile(ctx, filePath) + if err != nil { + // Don't fail the test, some files might not exist in certain tests + t.Logf("Note: Failed to open %s: %v", file, err) + } + } + } + + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + // Open all files and wait for rust-analyzer to index them + openAllFilesAndWait(suite, ctx) + + tests := []struct { + name string + symbolName string + expectedText string + snapshotName string + }{ + { + name: "Function", + symbolName: "foo_bar", + expectedText: "fn foo_bar()", + snapshotName: "foobar", + }, + { + name: "Struct", + symbolName: "TestStruct", + expectedText: "struct TestStruct", + snapshotName: "struct", + }, + { + name: "Method", + symbolName: "method", + expectedText: "fn method(&self)", + snapshotName: "method", + }, + { + name: "Trait", + symbolName: "TestInterface", + expectedText: "trait TestInterface", + snapshotName: "interface", + }, + { + name: "Type", + symbolName: "TestType", + expectedText: "type TestType", + snapshotName: "type", + }, + { + name: "Constant", + symbolName: "TEST_CONSTANT", + expectedText: "const TEST_CONSTANT", + snapshotName: "constant", + }, + { + name: "Variable", + symbolName: "TEST_VARIABLE", + expectedText: "static TEST_VARIABLE", + snapshotName: "variable", + }, + { + name: "Function", + symbolName: "test_function", + expectedText: "fn test_function()", + snapshotName: "function", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Call the ReadDefinition tool + result, err := tools.ReadDefinition(ctx, suite.Client, tc.symbolName, true) + if err != nil { + t.Fatalf("Failed to read definition: %v", err) + } + + // Check that the result contains relevant information + if !strings.Contains(result, tc.expectedText) { + t.Errorf("Definition does not contain expected text: %s", tc.expectedText) + } + + // Use snapshot testing to verify exact output + common.SnapshotTest(t, "rust", "definition", tc.snapshotName, result) + }) + } +} diff --git a/integrationtests/languages/rust/diagnostics/diagnostics_test.go b/integrationtests/languages/rust/diagnostics/diagnostics_test.go new file mode 100644 index 0000000..a0754f5 --- /dev/null +++ b/integrationtests/languages/rust/diagnostics/diagnostics_test.go @@ -0,0 +1,195 @@ +package diagnostics_test + +import ( + "context" + "fmt" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/rust/internal" + "github.com/isaacphi/mcp-language-server/internal/protocol" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestDiagnostics tests diagnostics functionality with the Rust language server +func TestDiagnostics(t *testing.T) { + // Helper function to open all files and wait for indexing + openAllFilesAndWait := func(suite *common.TestSuite, ctx context.Context) { + // Open all files to ensure rust-analyzer indexes everything + filesToOpen := []string{ + "src/main.rs", + "src/types.rs", + "src/helper.rs", + "src/consumer.rs", + "src/another_consumer.rs", + "src/clean.rs", + } + + for _, file := range filesToOpen { + filePath := filepath.Join(suite.WorkspaceDir, file) + err := suite.Client.OpenFile(ctx, filePath) + if err != nil { + // Don't fail the test, some files might not exist in certain tests + t.Logf("Note: Failed to open %s: %v", file, err) + } + } + } + // Test with a clean file + t.Run("CleanFile", func(t *testing.T) { + // Get a test suite with clean code + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + // Open all files and wait for rust-analyzer to index them + openAllFilesAndWait(suite, ctx) + + filePath := filepath.Join(suite.WorkspaceDir, "src/clean.rs") + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, filePath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Verify we have no diagnostics + if !strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected no diagnostics but got: %s", result) + } + + common.SnapshotTest(t, "rust", "diagnostics", "clean", result) + }) + + // Test with a file containing an error + t.Run("FileWithError", func(t *testing.T) { + // Get a test suite with code that contains errors + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + // Open all files and wait for rust-analyzer to index them + openAllFilesAndWait(suite, ctx) + + filePath := filepath.Join(suite.WorkspaceDir, "src/main.rs") + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, filePath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Verify we have diagnostics about unreachable code + if strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected diagnostics but got none") + } + + if !strings.Contains(result, "unreachable") { + t.Errorf("Expected unreachable code error but got: %s", result) + } + + common.SnapshotTest(t, "rust", "diagnostics", "unreachable", result) + }) + + // Test file dependency: file A (helper.rs) provides a function, + // file B (consumer.rs) uses it, then modify A to break B + t.Run("FileDependency", func(t *testing.T) { + // Get a test suite with clean code + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + // Open all files and wait for rust-analyzer to index them + openAllFilesAndWait(suite, ctx) + + // Ensure the relevant paths are accessible + helperPath := filepath.Join(suite.WorkspaceDir, "src/helper.rs") + consumerPath := filepath.Join(suite.WorkspaceDir, "src/consumer.rs") + + // Get initial diagnostics for consumer.rs + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, consumerPath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Should have no diagnostics initially + if !strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected no diagnostics initially but got: %s", result) + } + + // Now modify the helper function to cause an error in the consumer + modifiedHelperContent := `// Helper functions for testing + +// A function that will be referenced from other files +pub fn helper_function(value: i32) -> String { + String::from("hello world") +} +` + // Write the modified content to the file + err = suite.WriteFile("src/helper.rs", modifiedHelperContent) + if err != nil { + t.Fatalf("Failed to update helper.rs: %v", err) + } + + // Explicitly notify the LSP server about the change + helperURI := fmt.Sprintf("file://%s", helperPath) + + // Notify the LSP server about the file change + err = suite.Client.NotifyChange(ctx, helperPath) + if err != nil { + t.Fatalf("Failed to notify change to helper.rs: %v", err) + } + + // Also send a didChangeWatchedFiles notification for coverage + // This simulates what the watcher would do + fileChangeParams := protocol.DidChangeWatchedFilesParams{ + Changes: []protocol.FileEvent{ + { + URI: protocol.DocumentUri(helperURI), + Type: protocol.FileChangeType(protocol.Changed), + }, + }, + } + + err = suite.Client.DidChangeWatchedFiles(ctx, fileChangeParams) + if err != nil { + t.Fatalf("Failed to send DidChangeWatchedFiles: %v", err) + } + + // Wait for LSP to process the change + time.Sleep(3 * time.Second) + + // Force reopen the consumer file to ensure LSP reevaluates it + err = suite.Client.CloseFile(ctx, consumerPath) + if err != nil { + t.Fatalf("Failed to close consumer.rs: %v", err) + } + + err = suite.Client.OpenFile(ctx, consumerPath) + if err != nil { + t.Fatalf("Failed to reopen consumer.rs: %v", err) + } + + // Wait for diagnostics to be generated + time.Sleep(3 * time.Second) + + // Check diagnostics again on consumer file - should now have an error + result, err = tools.GetDiagnosticsForFile(ctx, suite.Client, consumerPath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed after dependency change: %v", err) + } + + // Should have diagnostics now + if strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected diagnostics after dependency change but got none") + } + + // Should contain an error about function arguments + if !strings.Contains(result, "argument") && !strings.Contains(result, "parameter") && !strings.Contains(result, "expected") { + t.Errorf("Expected error about wrong arguments but got: %s", result) + } + + common.SnapshotTest(t, "rust", "diagnostics", "dependency", result) + }) +} diff --git a/integrationtests/languages/rust/internal/helpers.go b/integrationtests/languages/rust/internal/helpers.go new file mode 100644 index 0000000..1e075bd --- /dev/null +++ b/integrationtests/languages/rust/internal/helpers.go @@ -0,0 +1,42 @@ +// Package internal contains shared helpers for Rust tests +package internal + +import ( + "path/filepath" + "testing" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" +) + +// GetTestSuite returns a test suite for Rust language server tests +func GetTestSuite(t *testing.T) *common.TestSuite { + // Configure Rust LSP (rust-analyzer) + repoRoot, err := filepath.Abs("../../../..") + if err != nil { + t.Fatalf("Failed to get repo root: %v", err) + } + + config := common.LSPTestConfig{ + Name: "rust", + Command: "rust-analyzer", + Args: []string{}, + WorkspaceDir: filepath.Join(repoRoot, "integrationtests/workspaces/rust"), + InitializeTimeMs: 5000, + } + + // Create a test suite + suite := common.NewTestSuite(t, config) + + // Set up the suite + err = suite.Setup() + if err != nil { + t.Fatalf("Failed to set up test suite: %v", err) + } + + // Register cleanup + t.Cleanup(func() { + suite.Cleanup() + }) + + return suite +} diff --git a/integrationtests/languages/rust/references/references_test.go b/integrationtests/languages/rust/references/references_test.go new file mode 100644 index 0000000..7a0cd3a --- /dev/null +++ b/integrationtests/languages/rust/references/references_test.go @@ -0,0 +1,153 @@ +package references_test + +import ( + "context" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/rust/internal" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestFindReferences tests the FindReferences tool with Rust symbols +// that have references across different files +func TestFindReferences(t *testing.T) { + // Helper function to open all files and wait for indexing + openAllFilesAndWait := func(suite *common.TestSuite, ctx context.Context) { + // Open all files to ensure rust-analyzer indexes everything + filesToOpen := []string{ + "src/main.rs", + "src/types.rs", + "src/helper.rs", + "src/consumer.rs", + "src/another_consumer.rs", + "src/clean.rs", + } + + for _, file := range filesToOpen { + filePath := filepath.Join(suite.WorkspaceDir, file) + err := suite.Client.OpenFile(ctx, filePath) + if err != nil { + // Don't fail the test, some files might not exist in certain tests + t.Logf("Note: Failed to open %s: %v", file, err) + } + } + } + + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + // Open all files and wait for rust-analyzer to index them + openAllFilesAndWait(suite, ctx) + + tests := []struct { + name string + symbolName string + expectedText string + expectedFiles int // Number of files where references should be found + snapshotName string + }{ + { + name: "Function with references across files", + symbolName: "helper_function", + expectedText: "helper_function", + expectedFiles: 1, // rust-analyzer might not report references across all files initially + snapshotName: "helper-function", + }, + { + name: "Function with reference in same file", + symbolName: "foo_bar", + expectedText: "main()", + expectedFiles: 1, // main.rs + snapshotName: "foobar-function", + }, + { + name: "Struct with references across files", + symbolName: "SharedStruct", + expectedText: "consumer_function", + expectedFiles: 2, // consumer.rs and another_consumer.rs + snapshotName: "shared-struct", + }, + { + name: "Method with references across files", + symbolName: "SharedStruct::method", + expectedText: "method", + expectedFiles: 0, // rust-analyzer might format differently or not include method references + snapshotName: "struct-method", + }, + { + name: "Interface with references across files", + symbolName: "SharedInterface", + expectedText: "iface", + expectedFiles: 2, // consumer.rs and another_consumer.rs + snapshotName: "shared-interface", + }, + { + name: "Interface method with references", + symbolName: "SharedInterface::get_name", + expectedText: "get_name", + expectedFiles: 0, // rust-analyzer might format differently or not include trait method references + snapshotName: "interface-method", + }, + { + name: "Constant with references across files", + symbolName: "SHARED_CONSTANT", + expectedText: "SHARED_CONSTANT", + expectedFiles: 1, // rust-analyzer might not report references across all files initially + snapshotName: "shared-constant", + }, + { + name: "Type with references across files", + symbolName: "SharedType", + expectedText: "SharedType", + expectedFiles: 1, // rust-analyzer might not report references across all files initially + snapshotName: "shared-type", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Call the FindReferences tool + result, err := tools.FindReferences(ctx, suite.Client, tc.symbolName, true) + if err != nil { + t.Fatalf("Failed to find references: %v", err) + } + + // Check that the result contains relevant information + if !strings.Contains(result, tc.expectedText) { + t.Errorf("References do not contain expected text: %s", tc.expectedText) + } + + // Count how many different files are mentioned in the result + fileCount := countFilesInResult(result) + if fileCount < tc.expectedFiles { + t.Errorf("Expected references in at least %d files, but found in %d files", + tc.expectedFiles, fileCount) + } + + // Use snapshot testing to verify exact output + common.SnapshotTest(t, "rust", "references", tc.snapshotName, result) + }) + } +} + +// countFilesInResult counts the number of unique files mentioned in the result +func countFilesInResult(result string) int { + fileMap := make(map[string]bool) + + // Any line containing "workspace" and ".rs" is a file path + for line := range strings.SplitSeq(result, "\n") { + if strings.Contains(line, "workspace") && strings.Contains(line, ".rs") { + if !strings.Contains(line, "References in File") { + fileMap[line] = true + } + } + } + + return len(fileMap) +} diff --git a/integrationtests/languages/typescript/definition/definition_test.go b/integrationtests/languages/typescript/definition/definition_test.go new file mode 100644 index 0000000..8f1da69 --- /dev/null +++ b/integrationtests/languages/typescript/definition/definition_test.go @@ -0,0 +1,88 @@ +package definition_test + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/typescript/internal" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestReadDefinition tests the ReadDefinition tool with various TypeScript type definitions +func TestReadDefinition(t *testing.T) { + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + // Open the main.ts file to help the TypeScript server recognize the project + err := suite.Client.OpenFile(ctx, suite.WorkspaceDir+"/main.ts") + if err != nil { + t.Fatalf("Failed to open main.ts: %v", err) + } + + tests := []struct { + name string + symbolName string + expectedText string + snapshotName string + }{ + { + name: "Function", + symbolName: "TestFunction", + expectedText: "function TestFunction()", + snapshotName: "function", + }, + { + name: "Interface", + symbolName: "TestInterface", + expectedText: "interface TestInterface", + snapshotName: "interface", + }, + { + name: "Class", + symbolName: "TestClass", + expectedText: "class TestClass", + snapshotName: "class", + }, + { + name: "Type", + symbolName: "TestType", + expectedText: "type TestType", + snapshotName: "type", + }, + { + name: "Constant", + symbolName: "TestConstant", + expectedText: "const TestConstant", + snapshotName: "constant", + }, + { + name: "Variable", + symbolName: "TestVariable", + expectedText: "const TestVariable", + snapshotName: "variable", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Call the ReadDefinition tool + result, err := tools.ReadDefinition(ctx, suite.Client, tc.symbolName, true) + if err != nil { + t.Fatalf("Failed to read definition: %v", err) + } + + // Check that the result contains relevant information + if !strings.Contains(result, tc.expectedText) { + t.Errorf("Definition does not contain expected text: %s", tc.expectedText) + } + + // Use snapshot testing to verify exact output + common.SnapshotTest(t, "typescript", "definition", tc.snapshotName, result) + }) + } +} diff --git a/integrationtests/languages/typescript/diagnostics/diagnostics_test.go b/integrationtests/languages/typescript/diagnostics/diagnostics_test.go new file mode 100644 index 0000000..9a1e3de --- /dev/null +++ b/integrationtests/languages/typescript/diagnostics/diagnostics_test.go @@ -0,0 +1,275 @@ +package diagnostics_test + +import ( + "context" + "fmt" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/typescript/internal" + "github.com/isaacphi/mcp-language-server/internal/protocol" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestDiagnostics tests diagnostics functionality with the TypeScript language server +func TestDiagnostics(t *testing.T) { + // Helper function to open all files and wait for indexing + openAllFilesAndWait := func(suite *common.TestSuite, ctx context.Context) { + // Open all files to ensure TypeScript server indexes everything + filesToOpen := []string{ + "main.ts", + "helper.ts", + "consumer.ts", + "another_consumer.ts", + "clean.ts", + } + + for _, file := range filesToOpen { + filePath := filepath.Join(suite.WorkspaceDir, file) + err := suite.Client.OpenFile(ctx, filePath) + if err != nil { + // Don't fail the test, some files might not exist in certain tests + t.Logf("Note: Failed to open %s: %v", file, err) + } + } + + // Give TypeScript server time to process files + time.Sleep(3 * time.Second) + } + // Test with a clean file + t.Run("CleanFile", func(t *testing.T) { + // Get a test suite with clean code + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 5*time.Second) + defer cancel() + + // Open all files and wait for TypeScript server to index them + openAllFilesAndWait(suite, ctx) + + // Target the clean file + filePath := filepath.Join(suite.WorkspaceDir, "clean.ts") + + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, filePath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Verify we have no diagnostics + if !strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected no diagnostics but got: %s", result) + } + + common.SnapshotTest(t, "typescript", "diagnostics", "clean", result) + }) + + // Test with a file containing an error + t.Run("FileWithError", func(t *testing.T) { + // Get a test suite with code that contains errors + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 5*time.Second) + defer cancel() + + // Open all files and wait for TypeScript server to index them + openAllFilesAndWait(suite, ctx) + + // Create a file with an error + fileContent := ` +// File with a type error +function errorFunction(x: number): string { + return x; // Error: Type 'number' is not assignable to type 'string' +} + +const result = errorFunction(42); +console.log(result); +` + testFilePath := filepath.Join(suite.WorkspaceDir, "error.ts") + err := suite.WriteFile("error.ts", fileContent) + if err != nil { + t.Fatalf("Failed to create error.ts: %v", err) + } + + // Open the file to trigger diagnostics + err = suite.Client.OpenFile(ctx, testFilePath) + if err != nil { + t.Fatalf("Failed to open error.ts: %v", err) + } + + // Wait for diagnostics to be generated + time.Sleep(3 * time.Second) + + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, testFilePath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Verify we have diagnostics about the type error + if strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected diagnostics but got none") + } + + if !strings.Contains(result, "Type 'number' is not assignable to type 'string'") { + t.Errorf("Expected type error but got: %s", result) + } + + common.SnapshotTest(t, "typescript", "diagnostics", "type-error", result) + }) + + // Test file dependency: file A (helper.ts) provides a function, + // file B (consumer.ts) uses it, then modify A to break B + t.Run("FileDependency", func(t *testing.T) { + // Get a test suite with clean code + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 5*time.Second) + defer cancel() + + // Open all files and wait for TypeScript server to index them + openAllFilesAndWait(suite, ctx) + + // Ensure the relevant paths are accessible + helperPath := filepath.Join(suite.WorkspaceDir, "helper.ts") + consumerPath := filepath.Join(suite.WorkspaceDir, "consumer.ts") + + // Get initial diagnostics for consumer.ts + result, err := tools.GetDiagnosticsForFile(ctx, suite.Client, consumerPath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed: %v", err) + } + + // Should have no diagnostics initially + if !strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected no diagnostics initially but got: %s", result) + } + + // Now modify the helper function to cause an error in the consumer + modifiedHelperContent := `// Helper functions and types that are used across files + +// SharedFunction with references across files - now requires a parameter +export function SharedFunction(required: string): string { + return "helper function: " + required; +} + +// SharedInterface with methods +export interface SharedInterface { + getName(): string; + getValue(): number; +} + +// SharedClass implementing the interface +export class SharedClass implements SharedInterface { + private name: string; + + constructor(name: string) { + this.name = name; + } + + getName(): string { + return this.name; + } + + getValue(): number { + return 42; + } + + helperMethod(): void { + console.log("Helper method called"); + } +} + +// SharedType referenced across files +export type SharedType = string | number; + +// SharedConstant referenced across files +export const SharedConstant = "SHARED_VALUE"; + +// SharedEnum referenced across files +export enum SharedEnum { + ONE = "one", + TWO = "two", + THREE = "three" +}` + + // Write the modified content to the file + err = suite.WriteFile("helper.ts", modifiedHelperContent) + if err != nil { + t.Fatalf("Failed to update helper.ts: %v", err) + } + + // Explicitly notify the LSP server about the change + helperURI := fmt.Sprintf("file://%s", helperPath) + + // Notify the LSP server about the file change + err = suite.Client.NotifyChange(ctx, helperPath) + if err != nil { + t.Fatalf("Failed to notify change to helper.ts: %v", err) + } + + // Also send a didChangeWatchedFiles notification for coverage + // This simulates what the watcher would do + fileChangeParams := protocol.DidChangeWatchedFilesParams{ + Changes: []protocol.FileEvent{ + { + URI: protocol.DocumentUri(helperURI), + Type: protocol.FileChangeType(protocol.Changed), + }, + }, + } + + err = suite.Client.DidChangeWatchedFiles(ctx, fileChangeParams) + if err != nil { + t.Fatalf("Failed to send DidChangeWatchedFiles: %v", err) + } + + // Wait for LSP to process the change + time.Sleep(3 * time.Second) + + // Force reopen the consumer file to ensure LSP reevaluates it + err = suite.Client.CloseFile(ctx, consumerPath) + if err != nil { + t.Fatalf("Failed to close consumer.ts: %v", err) + } + + err = suite.Client.OpenFile(ctx, consumerPath) + if err != nil { + t.Fatalf("Failed to reopen consumer.ts: %v", err) + } + + // Wait for diagnostics to be generated + time.Sleep(3 * time.Second) + + // Check diagnostics again on consumer file - should now have an error + result, err = tools.GetDiagnosticsForFile(ctx, suite.Client, consumerPath, true, true) + if err != nil { + t.Fatalf("GetDiagnosticsForFile failed after dependency change: %v", err) + } + + // Should have diagnostics now + if strings.Contains(result, "No diagnostics found") { + t.Errorf("Expected diagnostics after dependency change but got none") + } + + // Should contain an error about function arguments or expected parameters + expectedErrorPhrases := []string{ + "argument", "parameter", "expected", "required", "call", + } + + found := false + for _, phrase := range expectedErrorPhrases { + if strings.Contains(strings.ToLower(result), phrase) { + found = true + break + } + } + + if !found { + t.Errorf("Expected error about arguments/parameters but got: %s", result) + } + + common.SnapshotTest(t, "typescript", "diagnostics", "dependency", result) + }) +} diff --git a/integrationtests/languages/typescript/internal/helpers.go b/integrationtests/languages/typescript/internal/helpers.go new file mode 100644 index 0000000..5d15cb0 --- /dev/null +++ b/integrationtests/languages/typescript/internal/helpers.go @@ -0,0 +1,42 @@ +// Package internal contains shared helpers for TypeScript tests +package internal + +import ( + "path/filepath" + "testing" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" +) + +// GetTestSuite returns a test suite for TypeScript language server tests +func GetTestSuite(t *testing.T) *common.TestSuite { + // Configure TypeScript LSP + repoRoot, err := filepath.Abs("../../../..") + if err != nil { + t.Fatalf("Failed to get repo root: %v", err) + } + + config := common.LSPTestConfig{ + Name: "typescript", + Command: "typescript-language-server", + Args: []string{"--stdio"}, + WorkspaceDir: filepath.Join(repoRoot, "integrationtests/workspaces/typescript"), + InitializeTimeMs: 2000, // 2 seconds + } + + // Create a test suite + suite := common.NewTestSuite(t, config) + + // Set up the suite + err = suite.Setup() + if err != nil { + t.Fatalf("Failed to set up test suite: %v", err) + } + + // Register cleanup + t.Cleanup(func() { + suite.Cleanup() + }) + + return suite +} diff --git a/integrationtests/languages/typescript/references/references_test.go b/integrationtests/languages/typescript/references/references_test.go new file mode 100644 index 0000000..cee539e --- /dev/null +++ b/integrationtests/languages/typescript/references/references_test.go @@ -0,0 +1,157 @@ +package references_test + +import ( + "context" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/isaacphi/mcp-language-server/integrationtests/languages/common" + "github.com/isaacphi/mcp-language-server/integrationtests/languages/typescript/internal" + "github.com/isaacphi/mcp-language-server/internal/tools" +) + +// TestFindReferences tests the FindReferences tool with TypeScript symbols +// that have references across different files +func TestFindReferences(t *testing.T) { + suite := internal.GetTestSuite(t) + + ctx, cancel := context.WithTimeout(suite.Context, 10*time.Second) + defer cancel() + + // First open all files to ensure TypeScript server indexes everything + filesToOpen := []string{ + "main.ts", + "helper.ts", + "consumer.ts", + "another_consumer.ts", + } + + for _, file := range filesToOpen { + filePath := filepath.Join(suite.WorkspaceDir, file) + err := suite.Client.OpenFile(ctx, filePath) + if err != nil { + // Don't fail the test, just log it + t.Logf("Note: Failed to open %s: %v", file, err) + } + } + + // Give TypeScript server time to process files + time.Sleep(3 * time.Second) + + tests := []struct { + name string + symbolName string + expectedText string + expectedFiles int // Number of files where references should be found + snapshotName string + }{ + { + name: "Function with references across files", + symbolName: "SharedFunction", + expectedText: "ConsumerFunction", + expectedFiles: 2, // consumer.ts and another_consumer.ts + snapshotName: "shared-function", + }, + { + name: "Function with reference in same file", + symbolName: "TestFunction", + expectedText: "main()", + expectedFiles: 1, // main.ts + snapshotName: "test-function", + }, + { + name: "Class with references across files", + symbolName: "SharedClass", + expectedText: "SharedClass", + expectedFiles: 2, // consumer.ts and another_consumer.ts + snapshotName: "shared-class", + }, + { + name: "Method with references across files", + symbolName: "helperMethod", // Method name only + expectedText: "helperMethod", + expectedFiles: 1, // consumer.ts + snapshotName: "class-method", + }, + { + name: "Interface with references across files", + symbolName: "SharedInterface", + expectedText: "SharedInterface", + expectedFiles: 2, // consumer.ts and another_consumer.ts + snapshotName: "shared-interface", + }, + { + name: "Interface method with references", + symbolName: "getName", // Method name only + expectedText: "getName", + expectedFiles: 2, // Helper file defines it, consumer uses it + snapshotName: "interface-method", + }, + { + name: "Constant with references across files", + symbolName: "SharedConstant", + expectedText: "SharedConstant", + expectedFiles: 2, // consumer.ts and another_consumer.ts + snapshotName: "shared-constant", + }, + { + name: "Type with references across files", + symbolName: "SharedType", + expectedText: "SharedType", + expectedFiles: 2, // consumer.ts and another_consumer.ts + snapshotName: "shared-type", + }, + { + name: "Enum with references across files", + symbolName: "SharedEnum", + expectedText: "SharedEnum", + expectedFiles: 2, // consumer.ts and another_consumer.ts + snapshotName: "shared-enum", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Call the FindReferences tool + result, err := tools.FindReferences(ctx, suite.Client, tc.symbolName, true) + if err != nil { + t.Fatalf("Failed to find references: %v", err) + } + + // Check that the result contains relevant information + if !strings.Contains(result, tc.expectedText) { + t.Errorf("References do not contain expected text: %s", tc.expectedText) + } + + // Count how many different files are mentioned in the result + fileCount := countFilesInResult(result) + if fileCount < tc.expectedFiles { + t.Errorf("Expected references in at least %d files, but found in %d files", + tc.expectedFiles, fileCount) + } + + // Use snapshot testing to verify exact output + common.SnapshotTest(t, "typescript", "references", tc.snapshotName, result) + }) + } +} + +// countFilesInResult counts the number of unique files mentioned in the result +func countFilesInResult(result string) int { + fileMap := make(map[string]bool) + + // Any line containing "workspace" and ".ts" is a file path + // but filter out lines that are just headers + for line := range strings.SplitSeq(result, "\n") { + if strings.Contains(line, "workspace") && strings.Contains(line, ".ts") { + // Avoid counting section headers and focus on actual file paths + if !strings.Contains(line, "References in File") && !strings.Contains(line, "Symbol:") { + fileMap[line] = true + } + } + } + + return len(fileMap) +} diff --git a/integrationtests/workspaces/python/another_consumer.py b/integrationtests/workspaces/python/another_consumer.py new file mode 100644 index 0000000..99eba8e --- /dev/null +++ b/integrationtests/workspaces/python/another_consumer.py @@ -0,0 +1,59 @@ +"""Another module that uses helpers and shared components.""" + +from helper import ( + SHARED_CONSTANT, + SharedClass, + helper_function, + Color, +) + + +class AnotherImplementation: + """A class that uses shared components but doesn't implement interfaces.""" + + def __init__(self): + """Initialize the implementation.""" + self.shared = SharedClass[str]("another", SHARED_CONSTANT) + + def do_something(self) -> str: + """Do something with the shared components. + + Returns: + The processed result + """ + # Get the value from shared class + value = self.shared.get_value() + + # Process it using the helper function + return helper_function(value) + + +def another_consumer_function() -> None: + """Another function that uses various shared components.""" + # Use shared constants + print(f"Using constant: {SHARED_CONSTANT}") + + # Use shared class with a different type parameter + shared = SharedClass[float]("another example", 3.14) + + # Use methods from shared class + name = shared.get_name() + value = shared.get_value() + print(f"Name: {name}, Value: {value}") + + # Use our own implementation + impl = AnotherImplementation() + result = impl.do_something() + print(f"Implementation result: {result}") + + # Use helper function + output = helper_function("another direct call") + print(f"Helper output: {output}") + + # Use enum-like class with a different color + color = Color.GREEN + print(f"Selected color: {color}") + + +if __name__ == "__main__": + another_consumer_function() \ No newline at end of file diff --git a/integrationtests/workspaces/python/clean.py b/integrationtests/workspaces/python/clean.py new file mode 100644 index 0000000..82c265e --- /dev/null +++ b/integrationtests/workspaces/python/clean.py @@ -0,0 +1,52 @@ +"""A clean Python module without any errors or warnings.""" + +from typing import List, Dict, Optional, Tuple + + +def clean_function(param: str) -> str: + """A clean function without errors. + + Args: + param: The input parameter + + Returns: + The processed result + """ + return f"Processed: {param}" + + +class CleanClass: + """A clean class without errors.""" + + def __init__(self, name: str): + """Initialize a CleanClass instance. + + Args: + name: The name of this instance + """ + self.name = name + + def get_name(self) -> str: + """Get the name of this instance. + + Returns: + The name of this instance + """ + return self.name + + @staticmethod + def utility_method(items: List[int]) -> int: + """Calculate the sum of a list of integers. + + Args: + items: A list of integers + + Returns: + The sum of the integers + """ + return sum(items) + + +# Clean constants and variables +CLEAN_CONSTANT: str = "This is a clean constant" +clean_variable: List[int] = [10, 20, 30, 40, 50] \ No newline at end of file diff --git a/integrationtests/workspaces/python/consumer.py b/integrationtests/workspaces/python/consumer.py new file mode 100644 index 0000000..75a00b4 --- /dev/null +++ b/integrationtests/workspaces/python/consumer.py @@ -0,0 +1,82 @@ +"""Consumer module that uses the helper module.""" + +from typing import List, Dict +from helper import ( + helper_function, + get_items, + SharedClass, + SharedInterface, + SHARED_CONSTANT, + Color +) + + +class MyImplementation(SharedInterface): + """An implementation of the SharedInterface.""" + + def process(self, data: List[str]) -> Dict[str, int]: + """Process the given data by counting occurrences. + + Args: + data: List of strings to process + + Returns: + Dictionary with counts of each item + """ + result = {} + for item in data: + if item in result: + result[item] += 1 + else: + result[item] = 1 + return result + + +def consumer_function() -> None: + """Function that consumes the helper functions.""" + # Use the helper function + message = helper_function("World") + print(message) + + # Get and process items from the helper + items = get_items() + for item in items: + print(f"Processing {item}") + + # Use the shared class + shared = SharedClass[str]("consumer", SHARED_CONSTANT) + print(f"Using shared class: {shared.get_name()} - {shared.get_value()}") + + # Use our implementation of the shared interface + impl = MyImplementation() + result = impl.process(items) + print(f"Processed items: {result}") + + # Use the enum + color = Color.RED + print(f"Selected color: {color}") + + +def process_data() -> None: + """Process some sample data.""" + data = get_items() + print(f"Found {len(data)} items") + + # Sort and display the data + sorted_data = sorted(data) + print(f"Sorted data: {sorted_data}") + + # Count the items + counts = {} + for item in data: + if item in counts: + counts[item] += 1 + else: + counts[item] = 1 + + print(f"Item counts: {counts}") + + +if __name__ == "__main__": + consumer_function() + process_data() \ No newline at end of file diff --git a/integrationtests/workspaces/python/consumer_clean.py b/integrationtests/workspaces/python/consumer_clean.py new file mode 100644 index 0000000..5822005 --- /dev/null +++ b/integrationtests/workspaces/python/consumer_clean.py @@ -0,0 +1,41 @@ +"""Consumer module that uses the helper module.""" + +from typing import List +from helper import helper_function, get_items + + +def consumer_function() -> None: + """Function that consumes the helper functions.""" + # Use the helper function + message = helper_function("World") + print(message) + + # Get and process items from the helper + items = get_items() + for item in items: + print(f"Processing {item}") + + +def process_data() -> None: + """Process some sample data.""" + data = get_items() + print(f"Found {len(data)} items") + + # Sort and display the data + sorted_data = sorted(data) + print(f"Sorted data: {sorted_data}") + + # Count the items + counts = {} + for item in data: + if item in counts: + counts[item] += 1 + else: + counts[item] = 1 + + print(f"Item counts: {counts}") + + +if __name__ == "__main__": + consumer_function() + process_data() \ No newline at end of file diff --git a/integrationtests/workspaces/python/error_file.py b/integrationtests/workspaces/python/error_file.py new file mode 100644 index 0000000..689042e --- /dev/null +++ b/integrationtests/workspaces/python/error_file.py @@ -0,0 +1,51 @@ +"""A Python module with deliberate errors for testing diagnostics.""" + +from typing import List, Dict, Any + + +def function_with_unreachable_code(value: int) -> str: + """A function with unreachable code. + + Args: + value: An integer value + + Returns: + A string result + """ + if value > 0: + return "Positive" + elif value < 0: + return "Negative" + else: + return "Zero" + # This is unreachable code + print("This will never be executed") + + +def function_with_type_error() -> str: + """A function with a type error. + + Returns: + Should return a string but actually returns an int + """ + return 42 # Type error: Incompatible return value type (got "int", expected "str") + + +class ErrorClass: + """A class with errors.""" + + def __init__(self, value: Dict[str, Any]): + """Initialize with errors. + + Args: + value: A dictionary + """ + self.value = value + + def method_with_undefined_variable(self) -> None: + """A method that uses an undefined variable.""" + print(undefined_variable) # Error: undefined_variable is not defined + + +# Variable with incompatible type annotation +wrong_type: str = 123 # Type error: Incompatible types in assignment \ No newline at end of file diff --git a/integrationtests/workspaces/python/helper.py b/integrationtests/workspaces/python/helper.py new file mode 100644 index 0000000..07675e5 --- /dev/null +++ b/integrationtests/workspaces/python/helper.py @@ -0,0 +1,88 @@ +"""Helper module that provides utility functions.""" + +from typing import List, Dict, TypeVar, Generic +from enum import Enum + + +# Shared constant used across files +SHARED_CONSTANT = "SHARED_VALUE" + + +# Enum-like class that will be referenced across files +class Color(Enum): + """Color enumeration used across files.""" + RED = "red" + GREEN = "green" + BLUE = "blue" + + +# Generic type variable for SharedClass +T = TypeVar('T') + + +# Shared class that will be referenced across files +class SharedClass(Generic[T]): + """A shared class that is used across multiple files.""" + + def __init__(self, name: str, value: T): + """Initialize with a name and value. + + Args: + name: The name of this instance + value: The value to store + """ + self.name = name + self.value = value + + def get_name(self) -> str: + """Get the name of this instance. + + Returns: + The name string + """ + return self.name + + def get_value(self) -> T: + """Get the stored value. + + Returns: + The stored value + """ + return self.value + + +# Interface-like class (abstract base class in Python) +class SharedInterface: + """An interface-like class that defines a contract.""" + + def process(self, data: List[str]) -> Dict[str, int]: + """Process the given data. + + Args: + data: List of strings to process + + Returns: + Dictionary with processing results + """ + raise NotImplementedError("Implementations must override process") + + +def helper_function(name: str) -> str: + """A helper function that formats a greeting message. + + Args: + name: The name to greet + + Returns: + A formatted greeting message + """ + return f"Hello, {name}!" + + +def get_items() -> List[str]: + """Get a list of sample items. + + Returns: + A list of sample strings + """ + return ["apple", "banana", "orange", "grape"] \ No newline at end of file diff --git a/integrationtests/workspaces/python/main.py b/integrationtests/workspaces/python/main.py new file mode 100644 index 0000000..8a2433b --- /dev/null +++ b/integrationtests/workspaces/python/main.py @@ -0,0 +1,106 @@ +"""Module containing test definitions for Python LSP integration tests.""" + +from typing import List, Dict, Optional, Union + + +def test_function(name: str) -> str: + """A simple test function that returns a greeting message. + + Args: + name: The name to greet + + Returns: + A greeting message + """ + return f"Hello, {name}!" + + +class TestClass: + """A test class with methods and attributes.""" + + class_variable: str = "class variable" + + def __init__(self, value: int = 0): + """Initialize the TestClass. + + Args: + value: The initial value + """ + self.value: int = value + + def test_method(self, increment: int) -> int: + """Increment the value by the given amount. + + Args: + increment: The amount to increment by + + Returns: + The new value + """ + self.value += increment + return self.value + + @staticmethod + def static_method(items: List[str]) -> Dict[str, int]: + """Convert a list of items to a dictionary with item counts. + + Args: + items: A list of strings + + Returns: + A dictionary mapping items to their counts + """ + result: Dict[str, int] = {} + for item in items: + if item in result: + result[item] += 1 + else: + result[item] = 1 + return result + + +class BaseClass: + """A base class for inheritance testing.""" + + def base_method(self) -> None: + """A method defined in the base class.""" + pass + + +class DerivedClass(BaseClass): + """A class that inherits from BaseClass.""" + + def derived_method(self) -> None: + """A method defined in the derived class.""" + pass + + +# Constants +TEST_CONSTANT: str = "test constant" +PI: float = 3.14159 + +# Variables +test_variable: List[int] = [1, 2, 3, 4, 5] +optional_var: Optional[str] = None +union_var: Union[int, str] = "test" + + +def main() -> None: + """Main function that demonstrates usage of the defined symbols.""" + result = test_function("World") + print(result) + + obj = TestClass(10) + new_value = obj.test_method(5) + print(f"New value: {new_value}") + + counts = TestClass.static_method(["apple", "banana", "apple", "orange"]) + print(f"Counts: {counts}") + + print(f"Constants - TEST_CONSTANT: {TEST_CONSTANT}, PI: {PI}") + print(f"Variables - test_variable: {test_variable}") + + +if __name__ == "__main__": + main() + diff --git a/integrationtests/workspaces/python/pyproject.toml b/integrationtests/workspaces/python/pyproject.toml new file mode 100644 index 0000000..6fc8a0b --- /dev/null +++ b/integrationtests/workspaces/python/pyproject.toml @@ -0,0 +1,12 @@ +[tool.poetry] +name = "test-project" +version = "0.1.0" +description = "A test project for Python LSP integration tests" +authors = ["Test "] + +[tool.poetry.dependencies] +python = "^3.9" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/integrationtests/workspaces/rust/Cargo.toml b/integrationtests/workspaces/rust/Cargo.toml new file mode 100644 index 0000000..40be1fe --- /dev/null +++ b/integrationtests/workspaces/rust/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "test-workspace" +version = "0.1.0" +edition = "2021" + +[dependencies] \ No newline at end of file diff --git a/integrationtests/workspaces/rust/src/another_consumer.rs b/integrationtests/workspaces/rust/src/another_consumer.rs new file mode 100644 index 0000000..f51e2a9 --- /dev/null +++ b/integrationtests/workspaces/rust/src/another_consumer.rs @@ -0,0 +1,24 @@ +// Another consumer module for testing references +use crate::helper::helper_function; +use crate::types::{ + SharedInterface, SharedStruct, SharedType, SHARED_CONSTANT, +}; + +pub fn another_consumer_function() { + // Use the helper function + let result = helper_function(); + println!("Helper result from another consumer: {}", result); + + // Use shared struct + let s = SharedStruct::new("another test"); + println!("Struct in another consumer: {}", s.name); + + // Use shared interface + let _iface: &dyn SharedInterface = &s; + + // Use shared constant + println!("Constant in another consumer: {}", SHARED_CONSTANT); + + // Use shared type + let _t: SharedType = String::from("another test"); +} \ No newline at end of file diff --git a/integrationtests/workspaces/rust/src/clean.rs b/integrationtests/workspaces/rust/src/clean.rs new file mode 100644 index 0000000..5ae057c --- /dev/null +++ b/integrationtests/workspaces/rust/src/clean.rs @@ -0,0 +1,4 @@ +// A clean file with no errors for testing +pub fn clean_function() -> String { + String::from("This file has no errors") +} \ No newline at end of file diff --git a/integrationtests/workspaces/rust/src/consumer.rs b/integrationtests/workspaces/rust/src/consumer.rs new file mode 100644 index 0000000..c1bca6f --- /dev/null +++ b/integrationtests/workspaces/rust/src/consumer.rs @@ -0,0 +1,26 @@ +// Consumer module for testing references +use crate::helper::helper_function; +use crate::types::{ + SharedInterface, SharedStruct, SharedType, SHARED_CONSTANT, +}; + +pub fn consumer_function() { + // Use the helper function + let result = helper_function(); + println!("Helper result: {}", result); + + // Use shared struct + let s = SharedStruct::new("test"); + println!("Struct method: {}", s.method()); + + // Use shared interface + let iface: &dyn SharedInterface = &s; + println!("Interface method: {}", iface.get_name()); + + // Use shared constant + println!("Constant: {}", SHARED_CONSTANT); + + // Use shared type + let t: SharedType = String::from("test"); + println!("Type: {}", t); +} diff --git a/integrationtests/workspaces/rust/src/helper.rs b/integrationtests/workspaces/rust/src/helper.rs new file mode 100644 index 0000000..6495b27 --- /dev/null +++ b/integrationtests/workspaces/rust/src/helper.rs @@ -0,0 +1,6 @@ +// Helper functions for testing + +// A function that will be referenced from other files +pub fn helper_function() -> String { + String::from("hello world") +} \ No newline at end of file diff --git a/integrationtests/workspaces/rust/src/main.rs b/integrationtests/workspaces/rust/src/main.rs new file mode 100644 index 0000000..cfe66cb --- /dev/null +++ b/integrationtests/workspaces/rust/src/main.rs @@ -0,0 +1,16 @@ +// Main module for testing Rust integration +mod types; +mod helper; +mod consumer; +mod another_consumer; +mod clean; + +// FooBar is a simple function for testing +fn foo_bar() -> String { + String::from("Hello, World!") + println!("Unreachable code"); // This is unreachable code +} + +fn main() { + println!("{}", foo_bar()); +} \ No newline at end of file diff --git a/integrationtests/workspaces/rust/src/types.rs b/integrationtests/workspaces/rust/src/types.rs new file mode 100644 index 0000000..6d099a6 --- /dev/null +++ b/integrationtests/workspaces/rust/src/types.rs @@ -0,0 +1,83 @@ +// Types for testing + +// A simple constant +pub const TEST_CONSTANT: &str = "test constant value"; + +// A simple variable +pub static TEST_VARIABLE: &str = "test variable value"; + +// A simple type alias +pub type TestType = String; + +// A struct for testing +pub struct TestStruct { + pub name: String, + pub value: i32, +} + +// Implementation for TestStruct +impl TestStruct { + pub fn new(name: &str, value: i32) -> Self { + TestStruct { + name: String::from(name), + value, + } + } + + pub fn method(&self) -> String { + format!("{}: {}", self.name, self.value) + } +} + +// An interface (trait) for testing +pub trait TestInterface { + fn get_name(&self) -> String; + fn get_value(&self) -> i32; +} + +// Implementation of TestInterface for TestStruct +impl TestInterface for TestStruct { + fn get_name(&self) -> String { + self.name.clone() + } + + fn get_value(&self) -> i32 { + self.value + } +} + +// Shared types for reference testing +pub struct SharedStruct { + pub name: String, +} + +impl SharedStruct { + pub fn new(name: &str) -> Self { + SharedStruct { + name: String::from(name), + } + } + + pub fn method(&self) -> String { + format!("SharedStruct: {}", self.name) + } +} + +pub trait SharedInterface { + fn get_name(&self) -> String; +} + +impl SharedInterface for SharedStruct { + fn get_name(&self) -> String { + self.name.clone() + } +} + +pub type SharedType = String; + +pub const SHARED_CONSTANT: &str = "shared constant value"; + +// A simple function for testing +pub fn test_function() -> String { + String::from("test function") +} \ No newline at end of file diff --git a/integrationtests/workspaces/typescript/another_consumer.ts b/integrationtests/workspaces/typescript/another_consumer.ts new file mode 100644 index 0000000..2e8b262 --- /dev/null +++ b/integrationtests/workspaces/typescript/another_consumer.ts @@ -0,0 +1,35 @@ +// Another consumer file that uses elements from the helper file +import { + SharedFunction, + SharedInterface, + SharedClass, + SharedType, + SharedConstant, + SharedEnum +} from './helper'; + +// AnotherConsumerFunction uses SharedFunction in a different way +export function AnotherConsumerFunction(): void { + const result = SharedFunction(); + console.log(`Result from shared function: ${result}`); + + // Using SharedClass differently + const instance = new SharedClass("another instance"); + + // Using SharedInterface + const iface: SharedInterface = { + getName: () => "custom implementation", + getValue: () => 100 + }; + + // Using SharedType + const mixedArray: SharedType[] = ["string", 42, "another"]; + + // Using SharedConstant + const prefixed = `PREFIX_${SharedConstant}`; + + // Using SharedEnum + const enumValues = [SharedEnum.ONE, SharedEnum.TWO, SharedEnum.THREE]; + + console.log(instance, iface, mixedArray, prefixed, enumValues); +} \ No newline at end of file diff --git a/integrationtests/workspaces/typescript/clean.ts b/integrationtests/workspaces/typescript/clean.ts new file mode 100644 index 0000000..4eb94d6 --- /dev/null +++ b/integrationtests/workspaces/typescript/clean.ts @@ -0,0 +1,24 @@ +// This file has no errors or diagnostics +export function cleanFunction(): string { + return "This is a clean function"; +} + +export class CleanClass { + private value: string; + + constructor(initialValue: string) { + this.value = initialValue; + } + + getValue(): string { + return this.value; + } +} + +function runClean(): void { + const instance = new CleanClass("test"); + console.log(instance.getValue()); + console.log(cleanFunction()); +} + +export default runClean; \ No newline at end of file diff --git a/integrationtests/workspaces/typescript/consumer.ts b/integrationtests/workspaces/typescript/consumer.ts new file mode 100644 index 0000000..9ba16ef --- /dev/null +++ b/integrationtests/workspaces/typescript/consumer.ts @@ -0,0 +1,38 @@ +// Consumer file that uses elements from the helper file +import { + SharedFunction, + SharedInterface, + SharedClass, + SharedType, + SharedConstant, + SharedEnum +} from './helper'; + +// ConsumerFunction uses SharedFunction +export function ConsumerFunction(): void { + console.log("Consumer calling:", SharedFunction()); + + // Using SharedClass + const instance = new SharedClass("test instance"); + console.log(instance.getName()); + instance.helperMethod(); + + // Using SharedInterface + const iface: SharedInterface = instance; + console.log(iface.getName()); + console.log(iface.getValue()); + + // Using SharedType + const value: SharedType = "string value"; + const numValue: SharedType = 42; + console.log(value, numValue); + + // Using SharedConstant + console.log(SharedConstant); + + // Using SharedEnum + console.log(SharedEnum.ONE); +} + +// Call the function +ConsumerFunction(); \ No newline at end of file diff --git a/integrationtests/workspaces/typescript/helper.ts b/integrationtests/workspaces/typescript/helper.ts new file mode 100644 index 0000000..e2522ea --- /dev/null +++ b/integrationtests/workspaces/typescript/helper.ts @@ -0,0 +1,46 @@ +// Helper functions and types that are used across files + +// SharedFunction with references across files +export function SharedFunction(): string { + return "helper function"; +} + +// SharedInterface with methods +export interface SharedInterface { + getName(): string; + getValue(): number; +} + +// SharedClass implementing the interface +export class SharedClass implements SharedInterface { + private name: string; + + constructor(name: string) { + this.name = name; + } + + getName(): string { + return this.name; + } + + getValue(): number { + return 42; + } + + helperMethod(): void { + console.log("Helper method called"); + } +} + +// SharedType referenced across files +export type SharedType = string | number; + +// SharedConstant referenced across files +export const SharedConstant = "SHARED_VALUE"; + +// SharedEnum referenced across files +export enum SharedEnum { + ONE = "one", + TWO = "two", + THREE = "three" +} \ No newline at end of file diff --git a/integrationtests/workspaces/typescript/main.ts b/integrationtests/workspaces/typescript/main.ts new file mode 100644 index 0000000..045e490 --- /dev/null +++ b/integrationtests/workspaces/typescript/main.ts @@ -0,0 +1,42 @@ +// TestFunction is a simple function for testing +export function TestFunction(): string { + return "Hello, World!"; + console.log("Unreachable code"); // This is unreachable code +} + +// TestInterface definition +export interface TestInterface { + method(): void; + property: string; +} + +// TestClass with method +export class TestClass implements TestInterface { + property: string; + + constructor() { + this.property = "test"; + } + + method(): void { + console.log("Method called"); + } +} + +// TestType definition +export type TestType = string | number; + +// TestVariable definition +export const TestVariable: string = "Test"; + +// TestConstant definition +export const TestConstant = 42; + +// Main function +function main() { + console.log(TestFunction()); + const instance = new TestClass(); + instance.method(); +} + +main(); \ No newline at end of file diff --git a/integrationtests/workspaces/typescript/package.json b/integrationtests/workspaces/typescript/package.json new file mode 100644 index 0000000..dfa961b --- /dev/null +++ b/integrationtests/workspaces/typescript/package.json @@ -0,0 +1,12 @@ +{ + "name": "typescript-test-workspace", + "version": "1.0.0", + "description": "TypeScript test workspace for MCP language server", + "main": "main.js", + "scripts": { + "build": "tsc" + }, + "devDependencies": { + "typescript": "^5.0.0" + } +} \ No newline at end of file diff --git a/integrationtests/workspaces/typescript/tsconfig.json b/integrationtests/workspaces/typescript/tsconfig.json new file mode 100644 index 0000000..0aeadaa --- /dev/null +++ b/integrationtests/workspaces/typescript/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/internal/lsp/transport.go b/internal/lsp/transport.go index 8a5a604..ce57cbd 100644 --- a/internal/lsp/transport.go +++ b/internal/lsp/transport.go @@ -67,8 +67,6 @@ func ReadMessage(r *bufio.Reader) (*Message, error) { } } - wireLogger.Debug("<- Reading content with length: %d", contentLength) - // Read content content := make([]byte, contentLength) _, err := io.ReadFull(r, content) diff --git a/internal/tools/diagnostics.go b/internal/tools/diagnostics.go index 3aed4f4..a5585b3 100644 --- a/internal/tools/diagnostics.go +++ b/internal/tools/diagnostics.go @@ -104,7 +104,7 @@ func GetDiagnosticsForFile(ctx context.Context, client *lsp.Client, filePath str formattedDiagnostics = append(formattedDiagnostics, formattedDiag) } - return strings.Join(formattedDiagnostics, "\n"), nil + return strings.Join(formattedDiagnostics, ""), nil } func getSeverityString(severity protocol.DiagnosticSeverity) string { diff --git a/internal/tools/find-references.go b/internal/tools/find-references.go index 00dc993..a89761e 100644 --- a/internal/tools/find-references.go +++ b/internal/tools/find-references.go @@ -26,7 +26,18 @@ func FindReferences(ctx context.Context, client *lsp.Client, symbolName string, var allReferences []string for _, symbol := range results { - if symbol.GetName() != symbolName { + // Handle different matching strategies based on the search term + if strings.Contains(symbolName, ".") { + // For qualified names like "Type.Method", check for various matches + parts := strings.Split(symbolName, ".") + methodName := parts[len(parts)-1] + + // Try matching the unqualified method name for languages that don't use qualified names in symbols + if symbol.GetName() != symbolName && symbol.GetName() != methodName { + continue + } + } else if symbol.GetName() != symbolName { + // For unqualified names, exact match only continue } @@ -70,7 +81,7 @@ func FindReferences(ctx context.Context, client *lsp.Client, symbolName string, fileRefs := refsByFile[uri] // Format file header similarly to ReadDefinition style - fileInfo := fmt.Sprintf("%s\nFile: %s\nReferences in File: %d\n%s\n", + fileInfo := fmt.Sprintf("%s\nFile: %s\nReferences in File: %d\n%s", strings.Repeat("=", 3), strings.TrimPrefix(uriStr, "file://"), len(fileRefs), @@ -89,7 +100,7 @@ func FindReferences(ctx context.Context, client *lsp.Client, symbolName string, } // Format reference location info - refInfo := fmt.Sprintf("Reference at Line %d, Column %d:\n%s\n", + refInfo := fmt.Sprintf("\nReference at Line %d, Column %d:\n%s\n", ref.Range.Start.Line+1, ref.Range.Start.Character+1, snippet) @@ -106,5 +117,5 @@ func FindReferences(ctx context.Context, client *lsp.Client, symbolName string, banner, symbolName, banner), nil } - return strings.Join(allReferences, "\n"), nil + return strings.Join(allReferences, ""), nil } diff --git a/internal/tools/read-definition.go b/internal/tools/read-definition.go index c29042c..c2bb5c7 100644 --- a/internal/tools/read-definition.go +++ b/internal/tools/read-definition.go @@ -46,8 +46,8 @@ func ReadDefinition(ctx context.Context, client *lsp.Client, symbolName string, } else { // For unqualified names like "Method" if v.Kind == protocol.Method { - // For methods, only match if the method name matches exactly Type.symbolName - if !strings.HasSuffix(symbol.GetName(), "."+symbolName) { + // For methods, only match if the method name matches exactly Type.symbolName or symbolName + if !strings.HasSuffix(symbol.GetName(), "::"+symbolName) && !strings.HasSuffix(symbol.GetName(), "."+symbolName) && symbol.GetName() != symbolName { continue } } else if symbol.GetName() != symbolName { @@ -98,5 +98,5 @@ func ReadDefinition(ctx context.Context, client *lsp.Client, symbolName string, return fmt.Sprintf("%s not found", symbolName), nil } - return strings.Join(definitions, "\n"), nil + return strings.Join(definitions, ""), nil } diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 402c713..6e7a0b8 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -452,14 +452,14 @@ func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPatt basePath := patternInfo.GetBasePath() patternText := patternInfo.GetPattern() - watcherLogger.Debug("Matching path %s against pattern %s (base: %s)", path, patternText, basePath) + // watcherLogger.Debug("Matching path %s against pattern %s (base: %s)", path, patternText, basePath) path = filepath.ToSlash(path) // Special handling for wildcard patterns like "**/*" if patternText == "**/*" { // This should match any file - watcherLogger.Debug("Using special matching for **/* pattern") + // watcherLogger.Debug("Using special matching for **/* pattern") return true } @@ -468,11 +468,11 @@ func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPatt if strings.HasPrefix(strings.TrimPrefix(patternText, "**/"), "*.") { // Extension pattern like **/*.go ext := strings.TrimPrefix(strings.TrimPrefix(patternText, "**/"), "*") - watcherLogger.Debug("Using extension matching for **/*.ext pattern: checking if %s ends with %s", path, ext) + // watcherLogger.Debug("Using extension matching for **/*.ext pattern: checking if %s ends with %s", path, ext) return strings.HasSuffix(path, ext) } else { // Any other pattern starting with **/ should match any path - watcherLogger.Debug("Using path substring matching for **/ pattern") + // watcherLogger.Debug("Using path substring matching for **/ pattern") return true } }