English · Русский
Thanks for contributing. Below is the minimum you need to add a rule or update the data.
You need Python 3.10 or newer. The language data is not part of the repository, so generate it first from your own 1C:Element distribution. Without it neither the linter nor some of the tests will run:
python tools/extract.py --dist "<path to the distribution>" # the whole dataset
pip install -e ".[dev]" # linter + pytest + PyYAML
pytest # tests (data-dependent ones are skipped without data)
python -m xbsl <path> # run over sourcesSet XBSL_XLIB_CORPUS to a local .xlib file or a directory containing library
deliveries, then run pytest tests/test_libs.py. The integration test checks public
and internal types through both YAML and XBSL. Keep library archives outside Git.
Without this variable, the optional integration test is skipped.
The VS Code extension can show an engine change live without reinstalling the package. Point the
xbsl.lsp.command setting at tools/lsp-dev.cmd, and the wrapper starts the LSP server from this
clone. PYTHONPATH comes before site-packages, so the clone wins from any working directory.
Reload the window after changing the setting, and the status bar reports the clone's engine
version. To go back to the installed package, remove the setting and reload again. The
interpreter needs the [lsp] and [morph] extras.
CLI subcommands run differently from the clone. python -m xbsl parses the check mode only: it
takes a subcommand for a path and lints 0 files. Write
python -c "from xbsl.cli import main; main()" list-rules instead.
python -m xbsl from the clone puts the working directory first on sys.path, so the sources
silently take over from an installed wheel. A probe then passes or fails depending on where it
was started, and the defect looks intermittent. Print where the module came from instead of
assuming it:
python -c "import xbsl.lexer as L; print(L.__file__)"Every call that reads a process as text names encoding="utf-8", and a Python child started
here gets PYTHONIOENCODING=utf-8 in its environment. Both are required. On Windows the child
writes in the console code page, cp1251 here, while the parent decodes utf-8. The reader thread
of subprocess dies inside itself, stdout comes back None, and the return code stays zero.
Nothing in the output hints that the text was lost. That is how the Russian half of a
documentation page, a help text, or a traceback with a Cyrillic path disappears.
tests/test_conventions.py holds the repository to both rules, reading the sources with ast.
The reading itself lives in the shared docsguard package, because three repositories have the
same failure waiting. CI installs that guard from a tag, so it cannot change a verdict here
without a commit here.
-
Create a module under
xbsl/rules/(or extend an existing one). -
Declare a rule function and decorate it:
from xbsl.diagnostics import Diagnostic, Severity from xbsl.engine import SourceFile, rule @rule("group/name", "Short title", "B", severity=Severity.WARNING) def my_rule(source: SourceFile): if source.kind != "xbsl": return # ... return/yield Diagnostic(path, line, col, rule_id, severity, message)
tier:Astructure/YAML,Btext/conventions,Ccode,Dsemantics.scope="project"– for cross-file rules; the function then receiveslist[SourceFile].enabled_by_default=False– if the rule is noisy on legacy code (enable it via--select).- Line/column positions are 1-indexed. Use
xbsl.lexer.linemapfor positions.
-
Register the module in
xbsl/rules/__init__.py(importing it registers the rule). -
The project's main rule: run it on a real project's sources and reach zero false positives. If a rule fires massively on existing code, make it
infoand disabled by default. Do not make everyone clean up old code for it. -
Add a test under
tests/(seetests/test_rules.pyfor examples). -
Update the accompanying metadata in the same change: the row in the tables of
docs/RULES.mdanddocs/RULES.ru.md(id, severity, default, scope, one-line description, docs link), the rule count there and in both READMEs, the entry ineditors/vscode/src/ruleDocs.ts– when a platform documentation section stands behind the rule, the per-level counts in the group descriptions (editors/vscode/package.nls.jsonand.ru.json), and for a new group thexbsl.groups.<group>setting ineditors/vscode/package.jsonas well. All of it is checked against the registry bytests/test_metadata_sync.py, so a forgotten place shows up right away instead of at the next extension release. -
If the rule checks a name, seed it for bilingual parity – see below.
The lexer and the language and type data are extracted from the platform itself: from the Xtext and ANTLR grammar and from the documentation of the distribution. Stick to that principle and verify against the primary source.
Element identifiers are bilingual, and the tables the rules judge by are extracted from
documentation that exists in Russian only. A rule matching source text against such a table reads
a translated project against a vocabulary that does not contain it. It misses real defects, or it
reports what the compiler accepts. The fix is to derive the English spelling from the platform
dictionaries, xbsl/terms.py and xbsl/uischema.py. Writing a second spelling by hand does not
help: a guess quietly matches nothing at all.
Linting a translated project and comparing the counts finds only the rules whose count moved. A rule that stays silent on both sides is invisible to that measurement, whether it is broken or the project simply carries no such construct. So the check plants its own case:
python tools/parity_seed.py # every seed
python tools/parity_seed.py --quiet # only the seeds that disagree, and the summary
python tools/parity_seed.py --rule group/name # one rule
python tools/parity_seed.py --uncovered # rules no seed speaks for
A seed is a small Russian tree plus the verdict the rule owes it: a finding, or silence. The
English twin comes from the toolkit's own translator, not from a second fixture, so the spelling
under test is the one the toolkit really produces. The verdict names the side that is wrong and
what it did: en-misses, en-invents, and the same with an ru- prefix. A table lacking the
English spelling makes a rule miss, while one lacking the Russian reading makes it invent, and
the two need opposite fixes. A seed that stops planting its case reports stale instead of
passing quietly. tests/test_parity_seed.py runs the whole catalog, so seeds are checked on
every test run and not only when someone remembers the tool.
A rule that matches names against the platform's tables also gets its English twin written by
hand (english=), spelled from terms.json, uiterms.json and the ui schema. Never guess it.
The rule is then judged on the platform's own spelling, and the translator's output becomes a
third tree. There translator-misses and translator-invents name a gap of the translator, not
of the rule, and the files it wrote differently from the hand are listed next to the verdict.
Always seed a pair, one case for the finding and one for the silence: a seed catches only the
direction it plants.
A gap you cannot close today is still worth planting. Give the seed a known= reason and it
reports known (...) instead of failing. Deleting it would delete the evidence, and the next
reader would rediscover the same thing from scratch. The note cannot hide a failure for long:
the moment such a seed starts agreeing it reports fixed! and fails the run, which is the signal
to delete the note.
The data is versioned under xbsl/data/element/<version>/. To add a new version, take its
distribution and run the extractors. They detect the version themselves:
python tools/extract.py --dist "<path to the distribution>" # the whole datasetThe extractors read the distribution where it lies and copy none of its files into the clone.
What they write is the derived data, and it stays out of the repository too: xbsl/data/element/
is ignored by git.