xbsllint 0.2.0__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 (45) hide show
  1. xbsllint-0.2.0/LICENSE +21 -0
  2. xbsllint-0.2.0/NOTICE +36 -0
  3. xbsllint-0.2.0/PKG-INFO +155 -0
  4. xbsllint-0.2.0/README.md +129 -0
  5. xbsllint-0.2.0/pyproject.toml +46 -0
  6. xbsllint-0.2.0/setup.cfg +4 -0
  7. xbsllint-0.2.0/tests/test_corpus.py +27 -0
  8. xbsllint-0.2.0/tests/test_dataset.py +27 -0
  9. xbsllint-0.2.0/tests/test_language.py +20 -0
  10. xbsllint-0.2.0/tests/test_lexer.py +42 -0
  11. xbsllint-0.2.0/tests/test_mcp.py +41 -0
  12. xbsllint-0.2.0/tests/test_rules.py +307 -0
  13. xbsllint-0.2.0/tests/test_smoke.py +20 -0
  14. xbsllint-0.2.0/tests/test_style_rules.py +407 -0
  15. xbsllint-0.2.0/xbsllint/__init__.py +3 -0
  16. xbsllint-0.2.0/xbsllint/__main__.py +4 -0
  17. xbsllint-0.2.0/xbsllint/cli.py +126 -0
  18. xbsllint-0.2.0/xbsllint/dataset.py +70 -0
  19. xbsllint-0.2.0/xbsllint/diagnostics.py +35 -0
  20. xbsllint-0.2.0/xbsllint/engine.py +180 -0
  21. xbsllint-0.2.0/xbsllint/lexer.py +222 -0
  22. xbsllint-0.2.0/xbsllint/mcp_server.py +104 -0
  23. xbsllint-0.2.0/xbsllint/rules/__init__.py +27 -0
  24. xbsllint-0.2.0/xbsllint/rules/_syntax.py +385 -0
  25. xbsllint-0.2.0/xbsllint/rules/code_structure.py +114 -0
  26. xbsllint-0.2.0/xbsllint/rules/handlers.py +73 -0
  27. xbsllint-0.2.0/xbsllint/rules/locals_usage.py +152 -0
  28. xbsllint-0.2.0/xbsllint/rules/semantics.py +258 -0
  29. xbsllint-0.2.0/xbsllint/rules/structure.py +35 -0
  30. xbsllint-0.2.0/xbsllint/rules/style_conditions.py +117 -0
  31. xbsllint-0.2.0/xbsllint/rules/style_layout.py +135 -0
  32. xbsllint-0.2.0/xbsllint/rules/style_naming.py +198 -0
  33. xbsllint-0.2.0/xbsllint/rules/style_strings.py +152 -0
  34. xbsllint-0.2.0/xbsllint/rules/style_types.py +191 -0
  35. xbsllint-0.2.0/xbsllint/rules/typography.py +90 -0
  36. xbsllint-0.2.0/xbsllint/rules/whitespace.py +41 -0
  37. xbsllint-0.2.0/xbsllint/rules/yaml_properties.py +87 -0
  38. xbsllint-0.2.0/xbsllint/rules/yaml_schema.py +143 -0
  39. xbsllint-0.2.0/xbsllint/web.py +383 -0
  40. xbsllint-0.2.0/xbsllint.egg-info/PKG-INFO +155 -0
  41. xbsllint-0.2.0/xbsllint.egg-info/SOURCES.txt +43 -0
  42. xbsllint-0.2.0/xbsllint.egg-info/dependency_links.txt +1 -0
  43. xbsllint-0.2.0/xbsllint.egg-info/entry_points.txt +4 -0
  44. xbsllint-0.2.0/xbsllint.egg-info/requires.txt +7 -0
  45. xbsllint-0.2.0/xbsllint.egg-info/top_level.txt +1 -0
xbsllint-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xbsl-lint contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
xbsllint-0.2.0/NOTICE ADDED
@@ -0,0 +1,36 @@
1
+ xbsl-lint
2
+ =========
3
+
4
+ (English below, русский ниже)
5
+
6
+ --- English ---
7
+
8
+ This project is not affiliated with 1C and is not an official 1C product.
9
+
10
+ "1C", "1C:Enterprise", "1C:Element", "1C:Fresh" and related names are trademarks and service marks
11
+ of their respective owners. They are used here only to indicate the tool's compatibility with the
12
+ platform.
13
+
14
+ The language and type data (xbsllint/data/element/...) is NOT included in this repository. It is
15
+ extracted by the user from their OWN, legally obtained 1C:Element distribution using the scripts
16
+ tools/extract_grammar.py and tools/extract_stdlib.py, and stays on the user's machine. The user is
17
+ responsible for holding the rights to the distribution and for the admissibility of extracting data
18
+ from it.
19
+
20
+ The project's source code is distributed under the MIT License (see the LICENSE file).
21
+
22
+ --- Русский ---
23
+
24
+ Проект не аффилирован с фирмой «1С» и не является её официальным продуктом.
25
+
26
+ «1С», «1С:Предприятие», «1С:Элемент», «1С:Фреш» и связанные названия – товарные знаки и знаки
27
+ обслуживания их правообладателей. Здесь они используются исключительно для указания
28
+ совместимости инструмента с платформой.
29
+
30
+ Данные о языке и типах (xbsllint/data/element/...) в этот репозиторий НЕ включены. Они
31
+ извлекаются пользователем из его СОБСТВЕННОГО, законно полученного дистрибутива 1С:Элемент
32
+ скриптами tools/extract_grammar.py и tools/extract_stdlib.py и остаются на локальной машине.
33
+ Ответственность за наличие прав на дистрибутив и за допустимость извлечения из него данных несёт
34
+ пользователь.
35
+
36
+ Исходный код проекта распространяется по лицензии MIT (см. файл LICENSE).
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: xbsllint
3
+ Version: 0.2.0
4
+ Summary: Линтер исходников 1С:Элемент (пары .yaml/.xbsl)
5
+ Author: xbsl-lint contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/keyfire/xbsl-lint
8
+ Project-URL: Repository, https://github.com/keyfire/xbsl-lint
9
+ Keywords: 1c,1c-element,xbsl,linter,static-analysis
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Natural Language :: Russian
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Quality Assurance
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ License-File: NOTICE
20
+ Requires-Dist: PyYAML>=6.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Provides-Extra: mcp
24
+ Requires-Dist: mcp>=1.2; extra == "mcp"
25
+ Dynamic: license-file
26
+
27
+ # xbsl-lint
28
+
29
+ **English** · [Русский](README.ru.md)
30
+
31
+ ![CI](https://github.com/keyfire/xbsl-lint/actions/workflows/ci.yml/badge.svg)
32
+
33
+ A linter for 1C:Element sources — it checks `Name.yaml` (element description) and `Name.xbsl`
34
+ (code module) pairs before the server-side compilation that happens on deploy.
35
+
36
+ > Not affiliated with 1C. "1C:Element", "1C:Fresh" and related names are trademarks of their
37
+ > respective owners. Language data is generated from your own distribution. See [NOTICE](NOTICE).
38
+
39
+ ## Why
40
+
41
+ 1C:Element has no external linter: the only code check is the server-side compilation on deploy —
42
+ it is slow and knows nothing about project conventions. xbsl-lint gives fast local feedback and
43
+ catches what the compiler does not check at all.
44
+
45
+ ## Step 1: generate the language data
46
+
47
+ The linter relies on language tables (bilingual keywords, operators), an stdlib type catalog, and
48
+ the configuration metamodel (element properties). XBSL is built on Eclipse Xtext + ANTLR; these are
49
+ extracted from **your** 1C:Element distribution (the `InternalBsl.g` grammar, the documentation, and
50
+ the `.xcore` metamodel) and are NOT bundled in this repository. Generate them locally:
51
+
52
+ ```sh
53
+ python tools/extract_grammar.py --dist "<path to the 1C:Element distribution>"
54
+ python tools/extract_stdlib.py --dist "<path to the 1C:Element distribution>"
55
+ python tools/extract_metamodel.py --dist "<path to the 1C:Element distribution>"
56
+ ```
57
+
58
+ The scripts auto-detect the platform version and place the data under
59
+ `xbsllint/data/element/<version>/` (this folder is gitignored). Without the data, the linter and
60
+ the tests will tell you to generate it.
61
+
62
+ ## Step 2: install and run
63
+
64
+ ```sh
65
+ pip install -e .
66
+ xbsllint path/to/sources # or: python -m xbsllint path/to/sources
67
+ ```
68
+
69
+ Flags: `--list-rules`, `--select`/`--ignore` (by rule id, rule group — the part of the id before
70
+ `/` — or tier letter), `--element-version`.
71
+
72
+ ## Rule tiers
73
+
74
+ - **A. Structure and YAML** — `.xbsl`/`.yaml` pairing, schema validity, `Ид` as a UUID,
75
+ `Ид` uniqueness, `Имя` matching the file name.
76
+ - **B. Text and conventions** — typography (en dash, straight quotes),
77
+ encoding/BOM/newlines/trailing whitespace, indentation and line length.
78
+ - **C. Code structure** — balance of blocks and `;`, brackets, unused local and loop variables,
79
+ plus the platform's code style conventions (the `style/` group, see below).
80
+ - **D. Semantics** — against platform data: type existence (in `новый`, `как` casts, annotations,
81
+ method signatures), form handlers (a yaml handler exists as a method in the paired module), and
82
+ top-level object properties against the configuration metamodel.
83
+
84
+ ## Code style conventions (the `style/` rules)
85
+
86
+ Twenty-one rules that follow the platform documentation ("Code style conventions" and "Language
87
+ idioms"): layout and expression wrapping, naming, type descriptions and signatures, collection
88
+ literals, string interpolation, and checks of boolean values and `Неопределено`.
89
+
90
+ Rules that clean code already satisfies are enabled by default (`warning`) — they guard against
91
+ regressions. Rules that typically fire on accumulated legacy debt are `info` and disabled; enable
92
+ them to measure the debt and pay it down:
93
+
94
+ ```sh
95
+ xbsllint path/to/sources --select style # all conventions, including the disabled ones
96
+ xbsllint path/to/sources --ignore style # none of them
97
+ ```
98
+
99
+ `Запрос{ ... }` blocks (the query DSL) and string literals (HTML/CSS/SVG in web views) are
100
+ excluded from these checks. Not covered, and left to the author and review: indentation being a
101
+ multiple of four, collection idioms, `Строки.Соединить()` for bulk concatenation, the `?.` / `??`
102
+ idioms, and `выбор` instead of an `иначе если` chain.
103
+
104
+ ## MCP server
105
+
106
+ A thin adapter over the same core: an agent (e.g. Claude Code) can call the checks as tools and
107
+ receive structured diagnostics.
108
+
109
+ ```sh
110
+ pip install -e ".[mcp]"
111
+ claude mcp add xbsllint -- xbsllint-mcp
112
+ ```
113
+
114
+ Tools: `lint_paths(paths)`, `lint_source(filename, content)`, `list_rules()`. The core and the CLI
115
+ do not require `mcp` — it lives only in the `[mcp]` extra.
116
+
117
+ ## Web interface
118
+
119
+ A local page: point it at a project folder and see the diagnostics. Standard library only (no
120
+ external dependencies), binds to `127.0.0.1` only.
121
+
122
+ ```sh
123
+ xbsllint-web # then open http://127.0.0.1:8771/
124
+ ```
125
+
126
+ Per-tier rule toggles, a data-version selector, severity/text filters, dark/light theme; clicking
127
+ a diagnostic opens the file in VS Code (`vscode://`).
128
+
129
+ ## Element versions
130
+
131
+ The data is versioned by platform version:
132
+
133
+ ```
134
+ xbsllint/data/element/
135
+ index.json # { available: [...], default: "<version>" }
136
+ <version>/{language.json, stdlib.json, metamodel.json}
137
+ ```
138
+
139
+ Pick a version with `--element-version` / the `XBSLLINT_ELEMENT_VERSION` env var / the index
140
+ `default`; `--version` shows what is available. Add a new version by re-running the extractors with
141
+ a new `--dist`.
142
+
143
+ ## Tests
144
+
145
+ ```sh
146
+ pip install -e ".[dev]"
147
+ pytest
148
+ ```
149
+
150
+ Data-dependent tests are skipped automatically when the data has not been generated.
151
+
152
+ ## License
153
+
154
+ MIT — see [LICENSE](LICENSE). Trademarks and data provenance — [NOTICE](NOTICE).
155
+ How to add a rule — [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,129 @@
1
+ # xbsl-lint
2
+
3
+ **English** · [Русский](README.ru.md)
4
+
5
+ ![CI](https://github.com/keyfire/xbsl-lint/actions/workflows/ci.yml/badge.svg)
6
+
7
+ A linter for 1C:Element sources — it checks `Name.yaml` (element description) and `Name.xbsl`
8
+ (code module) pairs before the server-side compilation that happens on deploy.
9
+
10
+ > Not affiliated with 1C. "1C:Element", "1C:Fresh" and related names are trademarks of their
11
+ > respective owners. Language data is generated from your own distribution. See [NOTICE](NOTICE).
12
+
13
+ ## Why
14
+
15
+ 1C:Element has no external linter: the only code check is the server-side compilation on deploy —
16
+ it is slow and knows nothing about project conventions. xbsl-lint gives fast local feedback and
17
+ catches what the compiler does not check at all.
18
+
19
+ ## Step 1: generate the language data
20
+
21
+ The linter relies on language tables (bilingual keywords, operators), an stdlib type catalog, and
22
+ the configuration metamodel (element properties). XBSL is built on Eclipse Xtext + ANTLR; these are
23
+ extracted from **your** 1C:Element distribution (the `InternalBsl.g` grammar, the documentation, and
24
+ the `.xcore` metamodel) and are NOT bundled in this repository. Generate them locally:
25
+
26
+ ```sh
27
+ python tools/extract_grammar.py --dist "<path to the 1C:Element distribution>"
28
+ python tools/extract_stdlib.py --dist "<path to the 1C:Element distribution>"
29
+ python tools/extract_metamodel.py --dist "<path to the 1C:Element distribution>"
30
+ ```
31
+
32
+ The scripts auto-detect the platform version and place the data under
33
+ `xbsllint/data/element/<version>/` (this folder is gitignored). Without the data, the linter and
34
+ the tests will tell you to generate it.
35
+
36
+ ## Step 2: install and run
37
+
38
+ ```sh
39
+ pip install -e .
40
+ xbsllint path/to/sources # or: python -m xbsllint path/to/sources
41
+ ```
42
+
43
+ Flags: `--list-rules`, `--select`/`--ignore` (by rule id, rule group — the part of the id before
44
+ `/` — or tier letter), `--element-version`.
45
+
46
+ ## Rule tiers
47
+
48
+ - **A. Structure and YAML** — `.xbsl`/`.yaml` pairing, schema validity, `Ид` as a UUID,
49
+ `Ид` uniqueness, `Имя` matching the file name.
50
+ - **B. Text and conventions** — typography (en dash, straight quotes),
51
+ encoding/BOM/newlines/trailing whitespace, indentation and line length.
52
+ - **C. Code structure** — balance of blocks and `;`, brackets, unused local and loop variables,
53
+ plus the platform's code style conventions (the `style/` group, see below).
54
+ - **D. Semantics** — against platform data: type existence (in `новый`, `как` casts, annotations,
55
+ method signatures), form handlers (a yaml handler exists as a method in the paired module), and
56
+ top-level object properties against the configuration metamodel.
57
+
58
+ ## Code style conventions (the `style/` rules)
59
+
60
+ Twenty-one rules that follow the platform documentation ("Code style conventions" and "Language
61
+ idioms"): layout and expression wrapping, naming, type descriptions and signatures, collection
62
+ literals, string interpolation, and checks of boolean values and `Неопределено`.
63
+
64
+ Rules that clean code already satisfies are enabled by default (`warning`) — they guard against
65
+ regressions. Rules that typically fire on accumulated legacy debt are `info` and disabled; enable
66
+ them to measure the debt and pay it down:
67
+
68
+ ```sh
69
+ xbsllint path/to/sources --select style # all conventions, including the disabled ones
70
+ xbsllint path/to/sources --ignore style # none of them
71
+ ```
72
+
73
+ `Запрос{ ... }` blocks (the query DSL) and string literals (HTML/CSS/SVG in web views) are
74
+ excluded from these checks. Not covered, and left to the author and review: indentation being a
75
+ multiple of four, collection idioms, `Строки.Соединить()` for bulk concatenation, the `?.` / `??`
76
+ idioms, and `выбор` instead of an `иначе если` chain.
77
+
78
+ ## MCP server
79
+
80
+ A thin adapter over the same core: an agent (e.g. Claude Code) can call the checks as tools and
81
+ receive structured diagnostics.
82
+
83
+ ```sh
84
+ pip install -e ".[mcp]"
85
+ claude mcp add xbsllint -- xbsllint-mcp
86
+ ```
87
+
88
+ Tools: `lint_paths(paths)`, `lint_source(filename, content)`, `list_rules()`. The core and the CLI
89
+ do not require `mcp` — it lives only in the `[mcp]` extra.
90
+
91
+ ## Web interface
92
+
93
+ A local page: point it at a project folder and see the diagnostics. Standard library only (no
94
+ external dependencies), binds to `127.0.0.1` only.
95
+
96
+ ```sh
97
+ xbsllint-web # then open http://127.0.0.1:8771/
98
+ ```
99
+
100
+ Per-tier rule toggles, a data-version selector, severity/text filters, dark/light theme; clicking
101
+ a diagnostic opens the file in VS Code (`vscode://`).
102
+
103
+ ## Element versions
104
+
105
+ The data is versioned by platform version:
106
+
107
+ ```
108
+ xbsllint/data/element/
109
+ index.json # { available: [...], default: "<version>" }
110
+ <version>/{language.json, stdlib.json, metamodel.json}
111
+ ```
112
+
113
+ Pick a version with `--element-version` / the `XBSLLINT_ELEMENT_VERSION` env var / the index
114
+ `default`; `--version` shows what is available. Add a new version by re-running the extractors with
115
+ a new `--dist`.
116
+
117
+ ## Tests
118
+
119
+ ```sh
120
+ pip install -e ".[dev]"
121
+ pytest
122
+ ```
123
+
124
+ Data-dependent tests are skipped automatically when the data has not been generated.
125
+
126
+ ## License
127
+
128
+ MIT — see [LICENSE](LICENSE). Trademarks and data provenance — [NOTICE](NOTICE).
129
+ How to add a rule — [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "xbsllint"
7
+ version = "0.2.0"
8
+ description = "Линтер исходников 1С:Элемент (пары .yaml/.xbsl)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "xbsl-lint contributors" }]
13
+ keywords = ["1c", "1c-element", "xbsl", "linter", "static-analysis"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Natural Language :: Russian",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Quality Assurance",
20
+ "Operating System :: OS Independent",
21
+ ]
22
+ dependencies = [
23
+ "PyYAML>=6.0",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/keyfire/xbsl-lint"
28
+ Repository = "https://github.com/keyfire/xbsl-lint"
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=7"]
32
+ mcp = ["mcp>=1.2"]
33
+
34
+ [project.scripts]
35
+ xbsllint = "xbsllint.cli:main"
36
+ xbsllint-mcp = "xbsllint.mcp_server:main"
37
+ xbsllint-web = "xbsllint.web:main"
38
+
39
+ [tool.setuptools]
40
+ packages = ["xbsllint", "xbsllint.rules"]
41
+
42
+ [tool.setuptools.package-data]
43
+ xbsllint = ["data/element/index.json", "data/element/*/*.json"]
44
+
45
+ [tool.pytest.ini_options]
46
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,27 @@
1
+ """Инвариант: реальный корпус проходит линтер без замечаний по умолчанию.
2
+
3
+ Тест пропускается, если корпус недоступен (переносимость).
4
+ Путь задаётся переменной окружения XBSLLINT_CORPUS.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from xbsllint import engine
13
+ from xbsllint.cli import discover
14
+
15
+ _CORPUS = os.environ.get("XBSLLINT_CORPUS")
16
+
17
+
18
+ @pytest.mark.skipif(not (_CORPUS and Path(_CORPUS).exists()), reason="корпус недоступен (задайте XBSLLINT_CORPUS)")
19
+ def test_corpus_no_errors_and_only_known_warnings():
20
+ # Корпус – валидный задеплоенный код: ошибок линтера быть не должно. Допустимы только
21
+ # известные находки code/unused-loop-var (их же выдаёт серверная компиляция); любое
22
+ # другое срабатывание на корпусе – признак ложного и должно ловиться тестом.
23
+ diags = engine.run(discover([str(_CORPUS)]))
24
+ errors = [d for d in diags if d.severity.value == "error"]
25
+ assert not errors, f"неожиданные ошибки: {[d.format() for d in errors[:5]]}"
26
+ unexpected = [d for d in diags if d.rule_id != "code/unused-loop-var"]
27
+ assert not unexpected, f"неожиданные замечания: {[d.format() for d in unexpected[:5]]}"
@@ -0,0 +1,27 @@
1
+ """Проверки версионированного доступа к данным (самодостаточность, выбор версии)."""
2
+
3
+ import pytest
4
+
5
+ from xbsllint import dataset
6
+
7
+
8
+ def test_default_is_available():
9
+ assert dataset.available_versions()
10
+ assert dataset.default_version() in dataset.available_versions()
11
+
12
+
13
+ def test_load_language_and_stdlib():
14
+ lang = dataset.load_json("language.json")
15
+ assert lang["keywords"]["METHOD"]["forms"]
16
+ std = dataset.load_json("stdlib.json")
17
+ assert "Массив" in std["names"]
18
+
19
+
20
+ def test_data_stamped_with_element_version():
21
+ lang = dataset.load_json("language.json")
22
+ assert lang["meta"]["element_version"] == dataset.default_version()
23
+
24
+
25
+ def test_invalid_version_raises():
26
+ with pytest.raises(dataset.DatasetError):
27
+ dataset.resolve_version("0.0.0-нет-такой")
@@ -0,0 +1,20 @@
1
+ """Проверки сгенерированных языковых данных (language.json)."""
2
+
3
+ from xbsllint.lexer import _keyword_forms, _language, _operators
4
+
5
+
6
+ def test_language_has_bilingual_keywords():
7
+ lang = _language()
8
+ assert lang["keywords"]["METHOD"]["forms"], "у METHOD должны быть формы"
9
+ kf = _keyword_forms()
10
+ assert kf.get("метод") == "METHOD"
11
+ assert kf.get("method") == "METHOD"
12
+ assert kf.get("возврат") == "RETURN"
13
+
14
+
15
+ def test_operators_have_multichar_and_sorted_longest_first():
16
+ ops = _operators()
17
+ for op in ("??", "?.", "::", "->", "==", "!="):
18
+ assert op in ops, f"оператор {op} отсутствует"
19
+ # по убыванию длины (для максимального откуса в лексере)
20
+ assert len(ops[0]) >= len(ops[-1])
@@ -0,0 +1,42 @@
1
+ """Проверки лексера XBSL."""
2
+
3
+ from xbsllint.lexer import tokenize
4
+
5
+
6
+ def _kinds(text):
7
+ return [t.kind for t in tokenize(text)]
8
+
9
+
10
+ def test_keywords_bilingual_with_canonical():
11
+ toks = tokenize("метод Ф(): Строка\n возврат 1\n;\n")
12
+ canon = {t.canonical for t in toks if t.kind == "KEYWORD"}
13
+ assert "METHOD" in canon
14
+ assert "RETURN" in canon
15
+
16
+
17
+ def test_string_and_comment_recognized():
18
+ kinds = _kinds('// коммент\nзнч s = "привет"\n')
19
+ assert "COMMENT" in kinds
20
+ assert "STRING" in kinds
21
+
22
+
23
+ def test_number_with_dot_is_single_token():
24
+ nums = [t for t in tokenize("знч x = 1.5\n") if t.kind == "NUMBER"]
25
+ assert len(nums) == 1
26
+ assert nums[0].value == "1.5"
27
+
28
+
29
+ def test_positions_are_1_indexed():
30
+ first = tokenize("метод Ф()\n;\n")[0]
31
+ assert (first.line, first.col) == (1, 1)
32
+
33
+
34
+ def test_operators_do_not_produce_unknown():
35
+ toks = tokenize("знч x = a ?? b?.c\nзнч f = (п) -> п\n")
36
+ assert not any(t.kind == "UNKNOWN" for t in toks)
37
+
38
+
39
+ def test_capitalized_form_still_keyword_token():
40
+ # 'Выбор' лексически — форма ключевого слова CASE (различение по контексту — на уровне правил)
41
+ toks = [t for t in tokenize("знч Выбор = 1\n") if t.kind == "KEYWORD"]
42
+ assert any(t.canonical == "CASE" for t in toks)
@@ -0,0 +1,41 @@
1
+ """Проверка MCP-адаптера через подставной FastMCP (не требует установленного mcp)."""
2
+
3
+ import importlib
4
+ import sys
5
+ import types
6
+
7
+
8
+ class _FakeMCP:
9
+ def __init__(self, name):
10
+ self.name = name
11
+ self.tools = {}
12
+
13
+ def tool(self):
14
+ def deco(fn):
15
+ self.tools[fn.__name__] = fn
16
+ return fn
17
+
18
+ return deco
19
+
20
+ def run(self): # pragma: no cover
21
+ pass
22
+
23
+
24
+ def test_mcp_adapter_registers_tools_and_lints(monkeypatch):
25
+ fast = types.ModuleType("mcp.server.fastmcp")
26
+ fast.FastMCP = _FakeMCP
27
+ monkeypatch.setitem(sys.modules, "mcp", types.ModuleType("mcp"))
28
+ monkeypatch.setitem(sys.modules, "mcp.server", types.ModuleType("mcp.server"))
29
+ monkeypatch.setitem(sys.modules, "mcp.server.fastmcp", fast)
30
+ sys.modules.pop("xbsllint.mcp_server", None)
31
+
32
+ m = importlib.import_module("xbsllint.mcp_server")
33
+ assert {"lint_paths", "lint_source", "list_rules"}.issubset(m.mcp.tools)
34
+
35
+ rules = m.list_rules()
36
+ assert any(r["id"] == "code/blocks" for r in rules)
37
+
38
+ res = m.lint_source("М.xbsl", "метод Ф() \n;\n", select=["whitespace/trailing"])
39
+ assert res["summary"]["diagnostics"] >= 1
40
+
41
+ sys.modules.pop("xbsllint.mcp_server", None)