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.
- xbsllint-0.2.0/LICENSE +21 -0
- xbsllint-0.2.0/NOTICE +36 -0
- xbsllint-0.2.0/PKG-INFO +155 -0
- xbsllint-0.2.0/README.md +129 -0
- xbsllint-0.2.0/pyproject.toml +46 -0
- xbsllint-0.2.0/setup.cfg +4 -0
- xbsllint-0.2.0/tests/test_corpus.py +27 -0
- xbsllint-0.2.0/tests/test_dataset.py +27 -0
- xbsllint-0.2.0/tests/test_language.py +20 -0
- xbsllint-0.2.0/tests/test_lexer.py +42 -0
- xbsllint-0.2.0/tests/test_mcp.py +41 -0
- xbsllint-0.2.0/tests/test_rules.py +307 -0
- xbsllint-0.2.0/tests/test_smoke.py +20 -0
- xbsllint-0.2.0/tests/test_style_rules.py +407 -0
- xbsllint-0.2.0/xbsllint/__init__.py +3 -0
- xbsllint-0.2.0/xbsllint/__main__.py +4 -0
- xbsllint-0.2.0/xbsllint/cli.py +126 -0
- xbsllint-0.2.0/xbsllint/dataset.py +70 -0
- xbsllint-0.2.0/xbsllint/diagnostics.py +35 -0
- xbsllint-0.2.0/xbsllint/engine.py +180 -0
- xbsllint-0.2.0/xbsllint/lexer.py +222 -0
- xbsllint-0.2.0/xbsllint/mcp_server.py +104 -0
- xbsllint-0.2.0/xbsllint/rules/__init__.py +27 -0
- xbsllint-0.2.0/xbsllint/rules/_syntax.py +385 -0
- xbsllint-0.2.0/xbsllint/rules/code_structure.py +114 -0
- xbsllint-0.2.0/xbsllint/rules/handlers.py +73 -0
- xbsllint-0.2.0/xbsllint/rules/locals_usage.py +152 -0
- xbsllint-0.2.0/xbsllint/rules/semantics.py +258 -0
- xbsllint-0.2.0/xbsllint/rules/structure.py +35 -0
- xbsllint-0.2.0/xbsllint/rules/style_conditions.py +117 -0
- xbsllint-0.2.0/xbsllint/rules/style_layout.py +135 -0
- xbsllint-0.2.0/xbsllint/rules/style_naming.py +198 -0
- xbsllint-0.2.0/xbsllint/rules/style_strings.py +152 -0
- xbsllint-0.2.0/xbsllint/rules/style_types.py +191 -0
- xbsllint-0.2.0/xbsllint/rules/typography.py +90 -0
- xbsllint-0.2.0/xbsllint/rules/whitespace.py +41 -0
- xbsllint-0.2.0/xbsllint/rules/yaml_properties.py +87 -0
- xbsllint-0.2.0/xbsllint/rules/yaml_schema.py +143 -0
- xbsllint-0.2.0/xbsllint/web.py +383 -0
- xbsllint-0.2.0/xbsllint.egg-info/PKG-INFO +155 -0
- xbsllint-0.2.0/xbsllint.egg-info/SOURCES.txt +43 -0
- xbsllint-0.2.0/xbsllint.egg-info/dependency_links.txt +1 -0
- xbsllint-0.2.0/xbsllint.egg-info/entry_points.txt +4 -0
- xbsllint-0.2.0/xbsllint.egg-info/requires.txt +7 -0
- 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).
|
xbsllint-0.2.0/PKG-INFO
ADDED
|
@@ -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
|
+

|
|
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).
|
xbsllint-0.2.0/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# xbsl-lint
|
|
2
|
+
|
|
3
|
+
**English** · [Русский](README.ru.md)
|
|
4
|
+
|
|
5
|
+

|
|
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"]
|
xbsllint-0.2.0/setup.cfg
ADDED
|
@@ -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)
|