Repository: tefra/xsdata
Pull request #996 (2024-03-24 15:23:11)

feat: Add src code excerpt on ruff errors
## 📒 Description

Really helpful to see exactly what the error was, when we update the generator

```
Error: Ruff failed
details: Failed to parse netex/models/entity_in_version_structure.py:378:58: Unexpected token ']'
source:

   class AlternativeText(AlternativeTextVersionedChildStructure):
       class Meta:
           namespace = "http://www.netex.org.uk/netex"

--->    validity_conditions_or_valid_between: Optional[Union[]] = field(
           init=False,
           metadata={
               "type": "Ignore",
```

Resolves #xxxx

## 🔗 What I've Done

> Write a description of the steps taken to resolve the issue

## 💬 Comments

> A place to write any comments to the reviewer.

## 🛫 Checklist

- [ ] Updated docs
- [ ] Added unit-tests
- [ ] [Sample tests](https://github.com/tefra/xsdata-samples) pass
- [ ] [W3C tests](https://github.com/tefra/xsdata-w3c-tests) pass


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=996) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [1 Security Hotspot](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=996&resolved=false&inNewCodePeriod=true)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=996)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/996?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.94%. Comparing base [(`cd65e54`)](https://app.codecov.io/gh/tefra/xsdata/commit/cd65e54b2a66db6ec963fc33d0fe0923aa793c6b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) to head [(`0ab7520`)](https://app.codecov.io/gh/tefra/xsdata/pull/996?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #996   +/-   ##
=======================================
  Coverage   99.94%   99.94%           
=======================================
  Files         117      117           
  Lines        9140     9156   +16     
  Branches     2172     2175    +3     
=======================================
+ Hits         9135     9151   +16     
  Misses          4        4           
  Partials        1        1           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tefra/xsdata/pull/996?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


Patch:
diff --git a/xsdata/formats/dataclass/generator.py b/xsdata/formats/dataclass/generator.py
index eaf23f8d..f33d2210 100644
--- a/xsdata/formats/dataclass/generator.py
+++ b/xsdata/formats/dataclass/generator.py
@@ -1,3 +1,4 @@
+import re
 import subprocess
 from pathlib import Path
 from textwrap import indent
@@ -255,5 +256,25 @@ class DataclassGenerator(AbstractGenerator):
 
             return src_code_encoded.decode()
         except subprocess.CalledProcessError as e:
-            error = indent(e.stderr.decode(), "  ")
-            raise CodegenError("Ruff failed", details=error)
+            details = e.stderr.decode().replace("error: ", "").strip()
+            source = self.code_excerpt(details, src_code_encoded.decode())
+            raise CodegenError("Ruff failed", details=details, source=source)
+
+    @classmethod
+    def code_excerpt(cls, details: str, src_code: str) -> str:
+        """Extract source code excerpt from the error details message."""
+        match = re.search(r"(\d+):(\d+)", details)
+        if match:
+            line_number = int(match.group(1)) - 1
+            lines = src_code.split("\n")
+            start = max(0, line_number - 4)
+            end = min(len(lines), line_number + 4)
+
+            excerpt = ["\n"]
+            for index in range(start, end):
+                prepend = "--->" if index == line_number else "   "
+                excerpt.append(f"{prepend}{lines[index]}")
+
+            return "\n".join(excerpt)
+
+        return "NA"


Test patch:
diff --git a/tests/formats/dataclass/test_generator.py b/tests/formats/dataclass/test_generator.py
index bceabdfd..ffecd747 100644
--- a/tests/formats/dataclass/test_generator.py
+++ b/tests/formats/dataclass/test_generator.py
@@ -212,10 +212,39 @@ class DataclassGeneratorTests(FactoryTestCase):
 
         self.assertEqual("", self.generator.package_name(""))
 
-    def test_format_with_invalid_code(self):
-        src_code = """a = "1"""
+    def test_ruff_code_with_invalid_code(self):
+        src_code = (
+            "class AlternativeText:\n"
+            "    class Meta:\n"
+            '        namespace = "xsdata"\n'
+            "\n"
+            "    foo: Optional[Union[]] = field(\n"
+            "           init=False,\n"
+            '           metadata={"type": "Ignore"}\n'
+            "    )\n"
+            "    bar: str\n"
+            "    thug: str"
+        )
         file_path = Path(__file__)
 
         self.generator.config.output.max_line_length = 55
-        with self.assertRaises(CodegenError):
+        with self.assertRaises(CodegenError) as cm:
             self.generator.ruff_code(src_code, file_path)
+
+        expected = (
+            "\n"
+            "\n"
+            "   class AlternativeText:\n"
+            "       class Meta:\n"
+            '           namespace = "xsdata"\n'
+            "   \n"
+            "--->    foo: Optional[Union[]] = field(\n"
+            "              init=False,\n"
+            '              metadata={"type": "Ignore"}\n'
+            "       )"
+        )
+        self.assertEqual(expected, cm.exception.meta.get("source"))
+
+    def test_code_excerpt_with_no_line_number(self):
+        actual = self.generator.code_excerpt("foobar", "")
+        self.assertEqual("NA", actual)