codehealthanalyzer 1.2.2__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {codehealthanalyzer-1.2.2/codehealthanalyzer.egg-info → codehealthanalyzer-1.2.4}/PKG-INFO +1 -1
  2. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/analyzers/violations.py +3 -3
  3. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/cli/main.py +165 -2
  4. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/version.py +1 -1
  5. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4/codehealthanalyzer.egg-info}/PKG-INFO +1 -1
  6. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_cli.py +63 -3
  7. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_cli_contract.py +1 -1
  8. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_violations_analyzer.py +12 -0
  9. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/LICENSE +0 -0
  10. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/MANIFEST.in +0 -0
  11. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/README.md +0 -0
  12. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/README_EN.md +0 -0
  13. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/__init__.py +0 -0
  14. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/__main__.py +0 -0
  15. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/analyzers/__init__.py +0 -0
  16. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/analyzers/base.py +0 -0
  17. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/analyzers/errors.py +0 -0
  18. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/analyzers/templates.py +0 -0
  19. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/cli/__init__.py +0 -0
  20. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/config.py +0 -0
  21. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/exceptions.py +0 -0
  22. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/i18n.py +0 -0
  23. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/reports/__init__.py +0 -0
  24. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/reports/formatter.py +0 -0
  25. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/reports/generator.py +0 -0
  26. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/schemas.py +0 -0
  27. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/utils/__init__.py +0 -0
  28. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/utils/categorizer.py +0 -0
  29. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/utils/helpers.py +0 -0
  30. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/utils/validators.py +0 -0
  31. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/web/__init__.py +0 -0
  32. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/web/server.py +0 -0
  33. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/web/static/css/dashboard.css +0 -0
  34. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/web/static/js/dashboard.js +0 -0
  35. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer/web/templates/dashboard.html +0 -0
  36. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer.egg-info/SOURCES.txt +0 -0
  37. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer.egg-info/dependency_links.txt +0 -0
  38. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer.egg-info/entry_points.txt +0 -0
  39. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer.egg-info/requires.txt +0 -0
  40. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/codehealthanalyzer.egg-info/top_level.txt +0 -0
  41. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/example.py +0 -0
  42. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/locale/en/LC_MESSAGES/codehealthanalyzer.po +0 -0
  43. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/locale/pt_BR/LC_MESSAGES/codehealthanalyzer.po +0 -0
  44. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/pyproject.toml +0 -0
  45. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/requirements.txt +0 -0
  46. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/setup.cfg +0 -0
  47. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/setup.py +0 -0
  48. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_analyzers.py +0 -0
  49. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_base_analyzer.py +0 -0
  50. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_categorizer.py +0 -0
  51. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_config.py +0 -0
  52. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_contracts.py +0 -0
  53. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_dashboard.py +0 -0
  54. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_errors_analyzer.py +0 -0
  55. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_exceptions.py +0 -0
  56. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_helpers.py +0 -0
  57. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_quality_score.py +0 -0
  58. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_report_formatter.py +0 -0
  59. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_smoke.py +0 -0
  60. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_templates_analyzer.py +0 -0
  61. {codehealthanalyzer-1.2.2 → codehealthanalyzer-1.2.4}/tests/test_validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codehealthanalyzer
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: Biblioteca Python para análise de qualidade e saúde de código
5
5
  Home-page: https://github.com/imparcialista/codehealthanalyzer
6
6
  Author: Luarco Team
@@ -121,7 +121,7 @@ class ViolationsAnalyzer(BaseAnalyzer):
121
121
  doc_lines = self._python_docstring_lines(tree)
122
122
  count = 0
123
123
  try:
124
- with open(file_path, "r", encoding="utf-8") as fh:
124
+ with open(file_path, "r", encoding="utf-8-sig") as fh:
125
125
  for idx, raw in enumerate(fh, 1):
126
126
  stripped = raw.strip()
127
127
  if not stripped or stripped.startswith("#"):
@@ -150,7 +150,7 @@ class ViolationsAnalyzer(BaseAnalyzer):
150
150
 
151
151
  def _count_html_lines(self, file_path: Path) -> int:
152
152
  try:
153
- with open(file_path, "r", encoding="utf-8") as fh:
153
+ with open(file_path, "r", encoding="utf-8-sig") as fh:
154
154
  return sum(1 for line in fh if line.strip())
155
155
  except OSError as exc:
156
156
  logger.warning("Falha ao ler template %s: %s", file_path, exc)
@@ -192,7 +192,7 @@ class ViolationsAnalyzer(BaseAnalyzer):
192
192
  if file_path.suffix == ".py":
193
193
  result["type"] = "Python"
194
194
  try:
195
- with open(file_path, "r", encoding="utf-8") as fh:
195
+ with open(file_path, "r", encoding="utf-8-sig") as fh:
196
196
  source = fh.read()
197
197
  tree = ast.parse(source, filename=str(file_path))
198
198
  except (OSError, SyntaxError) as exc:
@@ -129,11 +129,31 @@ def _write_report_files(
129
129
  base_name: str,
130
130
  format_name: str,
131
131
  no_json: bool,
132
+ detail: str = "full",
132
133
  ) -> None:
133
134
  formatter = ReportFormatter()
134
135
  output_path.mkdir(parents=True, exist_ok=True)
136
+
135
137
  if not no_json:
136
- formatter.to_json(report, str(output_path / f"{base_name}.json"))
138
+ summary_report = _build_summary_report(report)
139
+ if detail == "summary":
140
+ formatter.to_json(summary_report, str(output_path / f"{base_name}.json"))
141
+ formatter.to_json(
142
+ summary_report, str(output_path / f"{base_name}.summary.json")
143
+ )
144
+ elif detail == "standard":
145
+ formatter.to_json(
146
+ _build_standard_report(report), str(output_path / f"{base_name}.json")
147
+ )
148
+ formatter.to_json(
149
+ summary_report, str(output_path / f"{base_name}.summary.json")
150
+ )
151
+ else:
152
+ formatter.to_json(report, str(output_path / f"{base_name}.json"))
153
+ formatter.to_json(report, str(output_path / f"{base_name}.full.json"))
154
+ formatter.to_json(
155
+ summary_report, str(output_path / f"{base_name}.summary.json")
156
+ )
137
157
  if format_name in ["html", "all"]:
138
158
  ReportGenerator().generate_html_report(
139
159
  report, str(output_path / f"{base_name}.html")
@@ -144,6 +164,136 @@ def _write_report_files(
144
164
  formatter.to_csv(report, str(output_path / f"{base_name}.csv"))
145
165
 
146
166
 
167
+ def _write_analyze_json_files(
168
+ report: FullReport,
169
+ output_path: Path,
170
+ detail: str,
171
+ ) -> None:
172
+ formatter = ReportFormatter()
173
+ summary_report = _build_summary_report(report)
174
+
175
+ # Arquivos base amigaveis para consumo humano e integracoes simples.
176
+ formatter.to_json(summary_report, str(output_path / "summary_report.json"))
177
+ formatter.to_json(
178
+ report.get("violations", {}), str(output_path / "violations_report.json")
179
+ )
180
+ formatter.to_json(
181
+ report.get("templates", {}), str(output_path / "templates_report.json")
182
+ )
183
+ formatter.to_json(report.get("errors", {}), str(output_path / "errors_report.json"))
184
+
185
+ if detail in ["standard", "full"]:
186
+ formatter.to_json(
187
+ _build_standard_report(report), str(output_path / "analysis_report.json")
188
+ )
189
+
190
+ # Relatorio completo agora e opcional e so e gerado explicitamente.
191
+ if detail == "full":
192
+ formatter.to_json(report, str(output_path / "full_report.json"))
193
+
194
+
195
+ def _build_summary_report(report: FullReport, top_n: int = 10) -> dict[str, Any]:
196
+ violations = report.get("violations", {})
197
+ templates = report.get("templates", {})
198
+ errors = report.get("errors", {})
199
+ return {
200
+ "metadata": report.get("metadata", {}),
201
+ "summary": report.get("summary", {}),
202
+ "priorities": report.get("priorities", []),
203
+ "top_violations": (violations.get("violations", []) or [])[:top_n],
204
+ "top_warnings": (violations.get("warnings", []) or [])[:top_n],
205
+ "top_templates": (templates.get("templates", []) or [])[:top_n],
206
+ "top_errors": (errors.get("errors", []) or [])[:top_n],
207
+ }
208
+
209
+
210
+ def _standardize_violation_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
211
+ out: list[dict[str, Any]] = []
212
+ for item in items:
213
+ messages = item.get("violations", []) or []
214
+ out.append(
215
+ {
216
+ "file": item.get("file", ""),
217
+ "type": item.get("type", ""),
218
+ "lines": item.get("lines", 0),
219
+ "priority": item.get("priority", "low"),
220
+ "violation_count": len(messages),
221
+ "sample_violations": messages[:5],
222
+ }
223
+ )
224
+ return out
225
+
226
+
227
+ def _standardize_templates(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
228
+ out: list[dict[str, Any]] = []
229
+ for item in items:
230
+ out.append(
231
+ {
232
+ "file": item.get("file", ""),
233
+ "priority": item.get("priority", "low"),
234
+ "category": item.get("category", ""),
235
+ "total_css_chars": item.get("total_css_chars", item.get("css", 0)),
236
+ "total_js_chars": item.get("total_js_chars", item.get("js", 0)),
237
+ "css_inline_count": len(item.get("css_inline", []) or []),
238
+ "css_style_tags_count": len(item.get("css_style_tags", []) or []),
239
+ "js_inline_count": len(item.get("js_inline", []) or []),
240
+ "js_script_tags_count": len(item.get("js_script_tags", []) or []),
241
+ }
242
+ )
243
+ return out
244
+
245
+
246
+ def _standardize_errors(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
247
+ out: list[dict[str, Any]] = []
248
+ for item in items:
249
+ details = item.get("errors", []) or []
250
+ out.append(
251
+ {
252
+ "file": item.get("file", ""),
253
+ "priority": item.get("priority", "low"),
254
+ "category": item.get("category", ""),
255
+ "error_count": item.get("error_count", len(details)),
256
+ "sample_errors": details[:5],
257
+ }
258
+ )
259
+ return out
260
+
261
+
262
+ def _build_standard_report(report: FullReport) -> dict[str, Any]:
263
+ violations = report.get("violations", {})
264
+ templates = report.get("templates", {})
265
+ errors = report.get("errors", {})
266
+
267
+ # Compatibilidade com Python 3.8: evitar generics PEP585 em runtime dentro de cast().
268
+ violation_items = cast(list, violations.get("violations", []) or [])
269
+ warning_items = cast(list, violations.get("warnings", []) or [])
270
+ template_items = cast(list, templates.get("templates", []) or [])
271
+ error_items = cast(list, errors.get("errors", []) or [])
272
+
273
+ return {
274
+ "metadata": report.get("metadata", {}),
275
+ "summary": report.get("summary", {}),
276
+ "quality_score": report.get("quality_score", 0),
277
+ "priorities": report.get("priorities", []),
278
+ "violations": {
279
+ "metadata": violations.get("metadata", {}),
280
+ "statistics": violations.get("statistics", {}),
281
+ "violations": _standardize_violation_items(violation_items),
282
+ "warnings": _standardize_violation_items(warning_items),
283
+ },
284
+ "templates": {
285
+ "metadata": templates.get("metadata", {}),
286
+ "statistics": templates.get("statistics", {}),
287
+ "templates": _standardize_templates(template_items),
288
+ },
289
+ "errors": {
290
+ "metadata": errors.get("metadata", {}),
291
+ "statistics": errors.get("statistics", {}),
292
+ "errors": _standardize_errors(error_items),
293
+ },
294
+ }
295
+
296
+
147
297
  @click.group()
148
298
  @click.version_option(version=__version__)
149
299
  def cli():
@@ -177,6 +327,13 @@ def cli():
177
327
  help="Formato do relatório (além do JSON padrão)",
178
328
  )
179
329
  @click.option("--no-json", is_flag=True, help="Não gerar o relatório JSON padrão")
330
+ @click.option(
331
+ "--detail",
332
+ type=click.Choice(["summary", "standard", "full"]),
333
+ default="standard",
334
+ show_default=True,
335
+ help="Nível de detalhe (full_report.json é gerado apenas em 'full')",
336
+ )
180
337
  @click.option(
181
338
  "--config", "-c", type=click.Path(exists=True), help="Arquivo de configuração JSON"
182
339
  )
@@ -191,6 +348,7 @@ def analyze(
191
348
  output: Optional[str],
192
349
  format: str,
193
350
  no_json: bool,
351
+ detail: str,
194
352
  config: Optional[str],
195
353
  no_default_excludes: bool,
196
354
  verbose: bool,
@@ -274,7 +432,12 @@ def analyze(
274
432
 
275
433
  # Diretório de saída padrão
276
434
  output_path = Path(output or "reports")
277
- _write_report_files(report, output_path, "full_report", format, no_json)
435
+ output_path.mkdir(parents=True, exist_ok=True)
436
+ if not no_json:
437
+ _write_analyze_json_files(report, output_path, detail)
438
+ _write_report_files(
439
+ report, output_path, "analysis_report", format, no_json=True, detail=detail
440
+ )
278
441
 
279
442
  click.echo("\n" + ColorHelper.success("Análise concluída com sucesso!"))
280
443
 
@@ -1,3 +1,3 @@
1
1
  """Versionamento central do pacote."""
2
2
 
3
- __version__ = "1.2.2"
3
+ __version__ = "1.2.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codehealthanalyzer
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: Biblioteca Python para análise de qualidade e saúde de código
5
5
  Home-page: https://github.com/imparcialista/codehealthanalyzer
6
6
  Author: Luarco Team
@@ -60,7 +60,12 @@ def test_analyze_runs_on_valid_project(runner, project, tmp_path):
60
60
  ["analyze", str(project), "--output", str(out), "--no-default-excludes"],
61
61
  )
62
62
  assert result.exit_code == 0
63
- assert (out / "full_report.json").exists()
63
+ assert (out / "summary_report.json").exists()
64
+ assert (out / "analysis_report.json").exists()
65
+ assert (out / "violations_report.json").exists()
66
+ assert (out / "templates_report.json").exists()
67
+ assert (out / "errors_report.json").exists()
68
+ assert not (out / "full_report.json").exists()
64
69
 
65
70
 
66
71
  def test_analyze_json_report_has_summary(runner, project, tmp_path):
@@ -70,7 +75,7 @@ def test_analyze_json_report_has_summary(runner, project, tmp_path):
70
75
  cli,
71
76
  ["analyze", str(project), "--output", str(out), "--no-default-excludes"],
72
77
  )
73
- report = json.loads((out / "full_report.json").read_text(encoding="utf-8"))
78
+ report = json.loads((out / "summary_report.json").read_text(encoding="utf-8"))
74
79
  assert "summary" in report
75
80
  assert "quality_score" in report["summary"]
76
81
 
@@ -100,7 +105,7 @@ def test_analyze_markdown_format(runner, project, tmp_path):
100
105
  "--no-default-excludes",
101
106
  ],
102
107
  )
103
- assert (out / "full_report.md").exists()
108
+ assert (out / "analysis_report.md").exists()
104
109
 
105
110
 
106
111
  def test_analyze_no_json_flag(runner, project, tmp_path):
@@ -117,7 +122,62 @@ def test_analyze_no_json_flag(runner, project, tmp_path):
117
122
  "--no-default-excludes",
118
123
  ],
119
124
  )
125
+ assert not (out / "summary_report.json").exists()
126
+ assert not (out / "analysis_report.json").exists()
127
+ assert not (out / "violations_report.json").exists()
128
+ assert not (out / "templates_report.json").exists()
129
+ assert not (out / "errors_report.json").exists()
130
+
131
+
132
+ def test_analyze_detail_summary_generates_summary_files(runner, project, tmp_path):
133
+ out = tmp_path / "out"
134
+ with patch("shutil.which", return_value=None):
135
+ runner.invoke(
136
+ cli,
137
+ [
138
+ "analyze",
139
+ str(project),
140
+ "--output",
141
+ str(out),
142
+ "--detail",
143
+ "summary",
144
+ "--no-default-excludes",
145
+ ],
146
+ )
147
+
148
+ assert (out / "summary_report.json").exists()
149
+ assert (out / "violations_report.json").exists()
150
+ assert (out / "templates_report.json").exists()
151
+ assert (out / "errors_report.json").exists()
152
+ assert not (out / "analysis_report.json").exists()
120
153
  assert not (out / "full_report.json").exists()
154
+ data = json.loads((out / "summary_report.json").read_text(encoding="utf-8"))
155
+ assert "summary" in data
156
+ assert "top_violations" in data
157
+
158
+
159
+ def test_analyze_detail_full_generates_full_alias_file(runner, project, tmp_path):
160
+ out = tmp_path / "out"
161
+ with patch("shutil.which", return_value=None):
162
+ runner.invoke(
163
+ cli,
164
+ [
165
+ "analyze",
166
+ str(project),
167
+ "--output",
168
+ str(out),
169
+ "--detail",
170
+ "full",
171
+ "--no-default-excludes",
172
+ ],
173
+ )
174
+
175
+ assert (out / "summary_report.json").exists()
176
+ assert (out / "analysis_report.json").exists()
177
+ assert (out / "violations_report.json").exists()
178
+ assert (out / "templates_report.json").exists()
179
+ assert (out / "errors_report.json").exists()
180
+ assert (out / "full_report.json").exists()
121
181
 
122
182
 
123
183
  # ---------------------------------------------------------------------------
@@ -35,7 +35,7 @@ def test_cli_analyze_generates_json_report(tmp_path):
35
35
  )
36
36
 
37
37
  assert proc.returncode == 0
38
- report_file = output_dir / "full_report.json"
38
+ report_file = output_dir / "summary_report.json"
39
39
  assert report_file.exists()
40
40
  report = json.loads(report_file.read_text(encoding="utf-8"))
41
41
  assert "summary" in report
@@ -71,6 +71,18 @@ def test_check_file_python_syntax_error(tmp_path):
71
71
  assert len(result["violations"]) > 0
72
72
 
73
73
 
74
+ def test_check_file_python_with_utf8_bom(tmp_path):
75
+ f = tmp_path / "bom_module.py"
76
+ f.write_text("x = 1\n", encoding="utf-8-sig")
77
+
78
+ result = _make(tmp_path, {"no_default_excludes": True}).check_file(f)
79
+
80
+ assert result["violations"] == []
81
+ assert result["priority"] == "low"
82
+ assert result["type"] == "Python"
83
+ assert result["lines"] == 1
84
+
85
+
74
86
  # ---------------------------------------------------------------------------
75
87
  # check_file — HTML
76
88
  # ---------------------------------------------------------------------------