chipgraph 0.1.0__py3-none-any.whl
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.
- chipgraph/__init__.py +7 -0
- chipgraph/adapters/__init__.py +1 -0
- chipgraph/adapters/format/__init__.py +6 -0
- chipgraph/adapters/format/__main__.py +16 -0
- chipgraph/adapters/format/_yaml_lines.py +54 -0
- chipgraph/adapters/format/chip_yaml.py +323 -0
- chipgraph/adapters/format/qsoc_contract.py +246 -0
- chipgraph/adapters/llm/__init__.py +58 -0
- chipgraph/adapters/llm/_budget.py +173 -0
- chipgraph/adapters/llm/_errors.py +50 -0
- chipgraph/adapters/llm/_nda.py +30 -0
- chipgraph/adapters/llm/_retry.py +85 -0
- chipgraph/adapters/llm/anthropic.py +247 -0
- chipgraph/adapters/llm/decide_backend.py +179 -0
- chipgraph/adapters/llm/fake.py +87 -0
- chipgraph/adapters/llm/guard.py +68 -0
- chipgraph/adapters/parser/__init__.py +22 -0
- chipgraph/adapters/parser/cocotb.py +304 -0
- chipgraph/adapters/parser/generic_regex.py +75 -0
- chipgraph/adapters/parser/verible.py +55 -0
- chipgraph/adapters/parser/verilator.py +46 -0
- chipgraph/adapters/review/__init__.py +7 -0
- chipgraph/adapters/review/file.py +123 -0
- chipgraph/adapters/review/github.py +886 -0
- chipgraph/adapters/runner/__init__.py +7 -0
- chipgraph/adapters/runner/local.py +122 -0
- chipgraph/adapters/runtime/__init__.py +5 -0
- chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
- chipgraph/adapters/runtime/claude_code/agents.py +158 -0
- chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
- chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
- chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
- chipgraph/adapters/runtime/claude_code/loop.py +342 -0
- chipgraph/adapters/runtime/claude_code/previous.py +419 -0
- chipgraph/adapters/runtime/claude_code/review.py +492 -0
- chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
- chipgraph/adapters/runtime/claude_code/service.py +2285 -0
- chipgraph/adapters/tool/__init__.py +7 -0
- chipgraph/adapters/tool/cmd.py +229 -0
- chipgraph/adapters/tool/edalize.py +773 -0
- chipgraph/adapters/tool/filelist.py +259 -0
- chipgraph/adapters/tool/pyslang.py +485 -0
- chipgraph/adapters/vcs/__init__.py +1 -0
- chipgraph/adapters/vcs/git.py +521 -0
- chipgraph/app/__init__.py +10 -0
- chipgraph/app/baseline.py +339 -0
- chipgraph/app/build.py +332 -0
- chipgraph/app/checks.py +202 -0
- chipgraph/app/context.py +191 -0
- chipgraph/app/errors.py +11 -0
- chipgraph/app/executors.py +79 -0
- chipgraph/app/findings.py +203 -0
- chipgraph/app/ingest.py +523 -0
- chipgraph/checks/__init__.py +52 -0
- chipgraph/checks/_cdc_analyze.py +272 -0
- chipgraph/checks/_cdc_elaborate.py +455 -0
- chipgraph/checks/_common.py +268 -0
- chipgraph/checks/_cross.py +96 -0
- chipgraph/checks/_model.py +64 -0
- chipgraph/checks/_naming_pyslang.py +235 -0
- chipgraph/checks/_naming_rules.py +127 -0
- chipgraph/checks/_xref_syntax.py +260 -0
- chipgraph/checks/cdc_struct.py +235 -0
- chipgraph/checks/connect.py +353 -0
- chipgraph/checks/cross_chip.py +454 -0
- chipgraph/checks/duplicate.py +153 -0
- chipgraph/checks/filelist.py +323 -0
- chipgraph/checks/format.py +268 -0
- chipgraph/checks/generated.py +151 -0
- chipgraph/checks/hardcode.py +233 -0
- chipgraph/checks/layout.py +155 -0
- chipgraph/checks/naming.py +311 -0
- chipgraph/checks/plan_check.py +901 -0
- chipgraph/checks/ports_diff.py +203 -0
- chipgraph/checks/spec_schema.py +277 -0
- chipgraph/checks/style.py +910 -0
- chipgraph/checks/tb_static.py +599 -0
- chipgraph/checks/trace.py +224 -0
- chipgraph/checks/vendor_guard.py +369 -0
- chipgraph/checks/wrap_check.py +486 -0
- chipgraph/cli/__init__.py +1688 -0
- chipgraph/cli/next_steps.py +137 -0
- chipgraph/core/__init__.py +1 -0
- chipgraph/core/config/__init__.py +93 -0
- chipgraph/core/config/defaults.py +14 -0
- chipgraph/core/config/errors.py +32 -0
- chipgraph/core/config/loader.py +766 -0
- chipgraph/core/config/models.py +845 -0
- chipgraph/core/config/templates.py +340 -0
- chipgraph/core/contracts/__init__.py +75 -0
- chipgraph/core/contracts/_paths.py +26 -0
- chipgraph/core/contracts/agent.py +49 -0
- chipgraph/core/contracts/artifact.py +68 -0
- chipgraph/core/contracts/check.py +76 -0
- chipgraph/core/contracts/event.py +70 -0
- chipgraph/core/contracts/export.py +128 -0
- chipgraph/core/contracts/finding.py +171 -0
- chipgraph/core/contracts/gate.py +50 -0
- chipgraph/core/contracts/rule.py +177 -0
- chipgraph/core/contracts/types.py +45 -0
- chipgraph/core/engine/__init__.py +70 -0
- chipgraph/core/engine/agent_rule.py +1362 -0
- chipgraph/core/engine/baseline.py +283 -0
- chipgraph/core/engine/decide.py +462 -0
- chipgraph/core/engine/fanout.py +1358 -0
- chipgraph/core/engine/gate.py +170 -0
- chipgraph/core/engine/graph.py +647 -0
- chipgraph/core/engine/records.py +60 -0
- chipgraph/core/engine/rules.py +92 -0
- chipgraph/core/engine/scheduler.py +637 -0
- chipgraph/core/engine/select.py +1239 -0
- chipgraph/core/engine/targets.py +342 -0
- chipgraph/core/model/__init__.py +98 -0
- chipgraph/core/model/entities.py +244 -0
- chipgraph/core/model/export.py +16 -0
- chipgraph/core/model/ingest.py +693 -0
- chipgraph/core/model/json_value.py +4 -0
- chipgraph/core/model/keys.py +95 -0
- chipgraph/core/model/model.py +153 -0
- chipgraph/core/model/provenance.py +27 -0
- chipgraph/core/model/query.py +583 -0
- chipgraph/core/model/registry.py +80 -0
- chipgraph/core/model/relations.py +61 -0
- chipgraph/core/model/store.py +279 -0
- chipgraph/core/plugin_api/__init__.py +81 -0
- chipgraph/core/plugin_api/local.py +167 -0
- chipgraph/core/plugin_api/pack.py +159 -0
- chipgraph/core/plugin_api/protocols.py +177 -0
- chipgraph/core/plugin_api/registry.py +111 -0
- chipgraph/core/plugin_api/types.py +94 -0
- chipgraph/core/runtime/__init__.py +41 -0
- chipgraph/core/runtime/executor.py +79 -0
- chipgraph/core/runtime/queue.py +565 -0
- chipgraph/core/runtime/roles/__init__.py +91 -0
- chipgraph/core/runtime/roles/data/author.md +41 -0
- chipgraph/core/runtime/roles/data/critic.md +56 -0
- chipgraph/core/runtime/roles/data/planner.md +38 -0
- chipgraph/core/runtime/roles/data/researcher.md +37 -0
- chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
- chipgraph/core/runtime/roles/data/tb-author.md +48 -0
- chipgraph/core/runtime/roles/data/triage.md +34 -0
- chipgraph/core/runtime/roles/policy.py +299 -0
- chipgraph/core/runtime/roles/registry.py +52 -0
- chipgraph/core/runtime/roles/review.py +275 -0
- chipgraph/core/runtime/roles/schema.py +82 -0
- chipgraph/core/runtime/roles/skills.py +134 -0
- chipgraph/core/runtime/roles/spec.py +232 -0
- chipgraph/core/state/__init__.py +1 -0
- chipgraph/core/state/artifacts.py +156 -0
- chipgraph/core/state/backend.py +117 -0
- chipgraph/core/state/findings.py +151 -0
- chipgraph/core/state/handoff.py +411 -0
- chipgraph/core/state/idempotency.py +59 -0
- chipgraph/core/state/journal.py +238 -0
- chipgraph/core/state/layout.py +82 -0
- chipgraph/core/state/lock.py +195 -0
- chipgraph/core/state/trace.py +225 -0
- chipgraph/learn/__init__.py +96 -0
- chipgraph/learn/draft.py +187 -0
- chipgraph/learn/infer.py +580 -0
- chipgraph/learn/models.py +145 -0
- chipgraph/learn/try_run.py +228 -0
- chipgraph/mcp/__init__.py +12 -0
- chipgraph/mcp/model_tools.py +167 -0
- chipgraph/mcp/server.py +787 -0
- chipgraph/orgs/qnsc/naming-v1.yml +113 -0
- chipgraph/orgs/qnsc/profile.yml +5 -0
- chipgraph/packs/__init__.py +8 -0
- chipgraph/packs/assist/__init__.py +16 -0
- chipgraph/packs/assist/ask/__init__.py +58 -0
- chipgraph/packs/assist/ask/_db.py +57 -0
- chipgraph/packs/assist/ask/_project.py +230 -0
- chipgraph/packs/assist/ask/answer.py +266 -0
- chipgraph/packs/assist/ask/check.py +182 -0
- chipgraph/packs/assist/ask/contract.py +115 -0
- chipgraph/packs/assist/ask/documents.py +157 -0
- chipgraph/packs/assist/ask/fts.py +80 -0
- chipgraph/packs/assist/ask/retrieve.py +331 -0
- chipgraph/packs/assist/audit/__init__.py +383 -0
- chipgraph/packs/assist/pack.yml +13 -0
- chipgraph/packs/assist/triage/__init__.py +64 -0
- chipgraph/packs/assist/triage/contract.py +94 -0
- chipgraph/packs/assist/triage/parse.py +371 -0
- chipgraph/packs/assist/triage/question.py +384 -0
- chipgraph/packs/assist/triage/report.py +160 -0
- chipgraph/packs/assist/triage/rules.py +447 -0
- chipgraph/packs/assist/triage/run.py +224 -0
- chipgraph/packs/digital_rtl/__init__.py +19 -0
- chipgraph/packs/digital_rtl/layout.py +144 -0
- chipgraph/packs/digital_rtl/pack.yml +25 -0
- chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
- chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
- chipgraph/packs/digital_rtl/plan/context.py +82 -0
- chipgraph/packs/digital_rtl/plan/expand.py +94 -0
- chipgraph/packs/digital_rtl/plan/model.py +150 -0
- chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
- chipgraph/packs/digital_rtl/plan/show.py +176 -0
- chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
- chipgraph/packs/digital_rtl/pr/body.py +165 -0
- chipgraph/packs/digital_rtl/pr/gate.py +110 -0
- chipgraph/packs/digital_rtl/pr/open.py +397 -0
- chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
- chipgraph/packs/digital_rtl/rtl/context.py +272 -0
- chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
- chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
- chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
- chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
- chipgraph/packs/digital_rtl/rules/review.yml +31 -0
- chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
- chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
- chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
- chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
- chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
- chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
- chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
- chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
- chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
- chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
- chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
- chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
- chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
- chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
- chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
- chipgraph/packs/dv/__init__.py +15 -0
- chipgraph/packs/dv/declaration.py +392 -0
- chipgraph/packs/dv/interface.py +596 -0
- chipgraph/packs/dv/pack.yml +14 -0
- chipgraph/packs/dv/rules/tb_module.yml +42 -0
- chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
- chipgraph/packs/spec_core/__init__.py +9 -0
- chipgraph/packs/spec_core/extract/__init__.py +12 -0
- chipgraph/packs/spec_core/extract/_markdown.py +371 -0
- chipgraph/packs/spec_core/extract/_text.py +116 -0
- chipgraph/packs/spec_core/extract/mas.py +975 -0
- chipgraph/packs/spec_core/gen/__init__.py +11 -0
- chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
- chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
- chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
- chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
- chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
- chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
- chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
- chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
- chipgraph/packs/spec_core/pack.yml +6 -0
- chipgraph/py.typed +0 -0
- chipgraph-0.1.0.dist-info/METADATA +53 -0
- chipgraph-0.1.0.dist-info/RECORD +252 -0
- chipgraph-0.1.0.dist-info/WHEEL +4 -0
- chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
- chipgraph-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""`NamingCheck`: identifiers follow the project's naming rule, classified by pyslang.
|
|
2
|
+
|
|
3
|
+
The rule is data (`org:qnsc/naming-v1.yml`, a `chipgraph.checks._naming_rules.NamingRules`),
|
|
4
|
+
not code: per object kind an allowed pattern, plus lexical and vocabulary rules. The
|
|
5
|
+
check parses each in-scope SystemVerilog file into a pyslang **syntax tree** (no
|
|
6
|
+
elaboration, so a single file with missing packages still parses), classifies every
|
|
7
|
+
identifier the file *declares* by its syntax node kind (module, port, parameter vs
|
|
8
|
+
localparam, enum member, instance, signal vs memory, genvar), and checks each name
|
|
9
|
+
against the rule for its kind and the lexical/vocabulary rules. A regex over text cannot
|
|
10
|
+
tell a port from a signal; pyslang can, so this is more precise than the reference
|
|
11
|
+
text checker it is modelled on.
|
|
12
|
+
|
|
13
|
+
Example profile usage (`.chipgraph.yml`):
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
adapters:
|
|
17
|
+
naming: { use: naming, rules: "org:qnsc/naming-v1.yml", scope: ["design/{block}/rtl/**/*.sv"] }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`{block}` in a scope glob is substituted from `ctx.params["block"]`. `ignore` globs
|
|
21
|
+
(default: any path with a `vendor` segment) drop files that keep their upstream's
|
|
22
|
+
conventions. A per-declaration exemption comment (`// naming-check: ignore`) on the
|
|
23
|
+
declaration line suppresses that line. A parse error becomes an `error` issue for the
|
|
24
|
+
file, never an exception, and the names pyslang recovered around it are still checked;
|
|
25
|
+
a missing rules file or bad rules ref is a whole-check `error`.
|
|
26
|
+
|
|
27
|
+
Code in an inactive `` `ifdef `` branch is not seen: the file is preprocessed without the
|
|
28
|
+
project's defines, so names there are checked only when that branch is active.
|
|
29
|
+
|
|
30
|
+
Regenerate `schemas/formats/naming-rules.schema.json` with `python -m chipgraph.checks.naming`.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import re
|
|
36
|
+
import time
|
|
37
|
+
from collections.abc import Iterable
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
import yaml
|
|
42
|
+
from pydantic import ValidationError
|
|
43
|
+
|
|
44
|
+
from chipgraph.checks._common import (
|
|
45
|
+
ArgError,
|
|
46
|
+
compute_idempotency_key,
|
|
47
|
+
error_result,
|
|
48
|
+
glob_to_regex,
|
|
49
|
+
iter_repo_files,
|
|
50
|
+
optional_list_of_str,
|
|
51
|
+
require_list_of_str,
|
|
52
|
+
require_str,
|
|
53
|
+
sha256_bytes,
|
|
54
|
+
sha256_file,
|
|
55
|
+
)
|
|
56
|
+
from chipgraph.checks._naming_pyslang import (
|
|
57
|
+
Decl,
|
|
58
|
+
ParseFailure,
|
|
59
|
+
declared_identifiers,
|
|
60
|
+
parse_errors,
|
|
61
|
+
parse_file,
|
|
62
|
+
)
|
|
63
|
+
from chipgraph.checks._naming_rules import LexicalRule, NamingRules, VocabularyRule
|
|
64
|
+
from chipgraph.core.config.loader import resolve_data_ref
|
|
65
|
+
from chipgraph.core.contracts import CheckResult, CheckSpec, Issue
|
|
66
|
+
from chipgraph.core.contracts.types import CheckStatus
|
|
67
|
+
from chipgraph.core.plugin_api.types import ToolContext
|
|
68
|
+
|
|
69
|
+
_DEFAULT_IGNORE = ("**/vendor/**", "vendor/**")
|
|
70
|
+
"""By default, never check anything under a `vendor` path segment (D: it keeps upstream
|
|
71
|
+
conventions and is not ours to rename)."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class NamingCheck:
|
|
75
|
+
"""Checks that declared identifiers follow the project's data-defined naming rule."""
|
|
76
|
+
|
|
77
|
+
id = "naming"
|
|
78
|
+
name = "Naming"
|
|
79
|
+
|
|
80
|
+
async def run(self, spec: CheckSpec, ctx: ToolContext) -> CheckResult:
|
|
81
|
+
start = time.monotonic()
|
|
82
|
+
args = spec.args
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
rules_ref = require_str(args, "rules")
|
|
86
|
+
scope = _require_scope(args)
|
|
87
|
+
ignore = optional_list_of_str(args, "ignore") or list(_DEFAULT_IGNORE)
|
|
88
|
+
block = ctx.params.get("block")
|
|
89
|
+
scope = [_substitute(glob, block) for glob in scope]
|
|
90
|
+
rules_path = _resolve_rules_path(rules_ref, ctx.repo_root)
|
|
91
|
+
rules, rules_bytes = _load_rules(rules_path)
|
|
92
|
+
except (ArgError, _RulesError) as exc:
|
|
93
|
+
return error_result(spec, str(exc), start)
|
|
94
|
+
|
|
95
|
+
scope_regexes = [glob_to_regex(p) for p in scope]
|
|
96
|
+
ignore_regexes = [glob_to_regex(p) for p in ignore]
|
|
97
|
+
in_scope = [
|
|
98
|
+
f
|
|
99
|
+
for f in iter_repo_files(ctx.repo_root)
|
|
100
|
+
if any(r.fullmatch(f) for r in scope_regexes)
|
|
101
|
+
and not any(r.fullmatch(f) for r in ignore_regexes)
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
compiled = _CompiledRules(rules)
|
|
105
|
+
issues: list[Issue] = []
|
|
106
|
+
for rel in in_scope:
|
|
107
|
+
issues.extend(_check_file(ctx.repo_root / rel, rel, compiled))
|
|
108
|
+
issues.sort(key=lambda i: (i.file or "", i.line or 0, i.rule))
|
|
109
|
+
|
|
110
|
+
file_hashes = [(f, sha256_file(ctx.repo_root / f)) for f in in_scope]
|
|
111
|
+
file_hashes.append(("<rules>", sha256_bytes(rules_bytes)))
|
|
112
|
+
key = compute_idempotency_key(spec.id, args, file_hashes)
|
|
113
|
+
status: CheckStatus = "fail" if issues else "pass"
|
|
114
|
+
return CheckResult(
|
|
115
|
+
check_id=spec.id,
|
|
116
|
+
status=status,
|
|
117
|
+
issues=tuple(issues),
|
|
118
|
+
duration_s=time.monotonic() - start,
|
|
119
|
+
idempotency_key=key,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# --------------------------------------------------------------------------------------
|
|
124
|
+
# argument and rule loading
|
|
125
|
+
# --------------------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class _RulesError(Exception):
|
|
129
|
+
"""Raised when the rules ref cannot be resolved or the rules file is invalid."""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _require_scope(args: Any) -> list[str]:
|
|
133
|
+
scope = require_list_of_str(args, "scope")
|
|
134
|
+
if not scope:
|
|
135
|
+
raise ArgError("args['scope'] must list at least one glob")
|
|
136
|
+
return scope
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _substitute(glob: str, block: str | None) -> str:
|
|
140
|
+
"""Substitute `{block}` in a scope glob from `ctx.params`, or raise if it is unset."""
|
|
141
|
+
if "{block}" not in glob:
|
|
142
|
+
return glob
|
|
143
|
+
if block is None:
|
|
144
|
+
raise ArgError(f"scope glob {glob!r} uses {{block}} but no block is set in ctx.params")
|
|
145
|
+
return glob.replace("{block}", block)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _resolve_rules_path(ref: str, repo_root: Path) -> Path:
|
|
149
|
+
try:
|
|
150
|
+
return resolve_data_ref(ref, repo_root)
|
|
151
|
+
except Exception as exc:
|
|
152
|
+
raise _RulesError(f"cannot resolve rules ref {ref!r}: {exc}") from exc
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _load_rules(path: Path) -> tuple[NamingRules, bytes]:
|
|
156
|
+
try:
|
|
157
|
+
raw_bytes = path.read_bytes()
|
|
158
|
+
except OSError as exc:
|
|
159
|
+
raise _RulesError(f"cannot read rules file {path}: {exc}") from exc
|
|
160
|
+
try:
|
|
161
|
+
data = yaml.safe_load(raw_bytes)
|
|
162
|
+
except yaml.YAMLError as exc:
|
|
163
|
+
raise _RulesError(f"invalid YAML in rules file {path}: {exc}") from exc
|
|
164
|
+
try:
|
|
165
|
+
return NamingRules.model_validate(data), raw_bytes
|
|
166
|
+
except ValidationError as exc:
|
|
167
|
+
first = exc.errors()[0]
|
|
168
|
+
loc = ".".join(str(p) for p in first["loc"])
|
|
169
|
+
raise _RulesError(f"invalid rules file {path}: {loc}: {first['msg']}") from exc
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# --------------------------------------------------------------------------------------
|
|
173
|
+
# compiled rules and file checking
|
|
174
|
+
# --------------------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class _CompiledRules:
|
|
178
|
+
"""`NamingRules` with its regexes compiled once, ready to check names against."""
|
|
179
|
+
|
|
180
|
+
def __init__(self, rules: NamingRules) -> None:
|
|
181
|
+
self.rules = rules
|
|
182
|
+
self.kind_patterns: dict[str, tuple[str, re.Pattern[str], str]] = {
|
|
183
|
+
str(kind): (rule.rule, re.compile(rule.pattern), rule.message)
|
|
184
|
+
for kind, rule in rules.identifiers.items()
|
|
185
|
+
}
|
|
186
|
+
self.lexical = [_CompiledLexical(lx) for lx in rules.lexical]
|
|
187
|
+
self.vocabulary = _CompiledVocabulary(rules.vocabulary) if rules.vocabulary else None
|
|
188
|
+
self.ignore = re.compile(r"//\s*" + re.escape(rules.ignore_comment))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class _CompiledLexical:
|
|
192
|
+
def __init__(self, rule: LexicalRule) -> None:
|
|
193
|
+
self.rule = rule.rule
|
|
194
|
+
self.message = rule.message
|
|
195
|
+
self.forbid = re.compile(rule.forbid_pattern)
|
|
196
|
+
self.allow = re.compile(rule.allow_if_matches) if rule.allow_if_matches else None
|
|
197
|
+
|
|
198
|
+
def violated_by(self, name: str) -> bool:
|
|
199
|
+
if self.allow is not None and self.allow.fullmatch(name):
|
|
200
|
+
return False
|
|
201
|
+
return self.forbid.search(name) is not None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class _CompiledVocabulary:
|
|
205
|
+
def __init__(self, rule: VocabularyRule) -> None:
|
|
206
|
+
self.rule = rule.rule
|
|
207
|
+
self.message = rule.message
|
|
208
|
+
self.replace = dict(rule.replace)
|
|
209
|
+
|
|
210
|
+
def offending_word(self, name: str) -> tuple[str, str] | None:
|
|
211
|
+
low = name.lower()
|
|
212
|
+
for bad, good in self.replace.items():
|
|
213
|
+
if re.search(rf"(?:^|_){re.escape(bad)}(?:_|$)", low):
|
|
214
|
+
return bad, good
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _check_file(path: Path, rel: str, rules: _CompiledRules) -> list[Issue]:
|
|
219
|
+
try:
|
|
220
|
+
text = path.read_text(errors="ignore")
|
|
221
|
+
tree = parse_file(path)
|
|
222
|
+
except OSError as exc:
|
|
223
|
+
return [Issue(file=rel, rule="parse", severity="error", msg=f"cannot read file: {exc}")]
|
|
224
|
+
|
|
225
|
+
# A parse error is reported, but the names pyslang still recovered are checked too:
|
|
226
|
+
# one unexpanded vendor macro must not hide every naming violation in the file.
|
|
227
|
+
issues: list[Issue] = [_parse_issue(rel, f) for f in parse_errors(tree)]
|
|
228
|
+
exempt_lines = _exempt_lines(text, rules.ignore)
|
|
229
|
+
for decl in declared_identifiers(tree):
|
|
230
|
+
if decl.line in exempt_lines:
|
|
231
|
+
continue
|
|
232
|
+
issues.extend(_check_decl(rel, decl, rules))
|
|
233
|
+
return issues
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _parse_issue(rel: str, failure: ParseFailure) -> Issue:
|
|
237
|
+
return Issue(
|
|
238
|
+
file=rel,
|
|
239
|
+
line=failure.line,
|
|
240
|
+
rule="parse",
|
|
241
|
+
severity="error",
|
|
242
|
+
msg=f"parse error: {failure.message}",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _exempt_lines(text: str, ignore: re.Pattern[str]) -> frozenset[int]:
|
|
247
|
+
return frozenset(i for i, line in enumerate(text.splitlines(), start=1) if ignore.search(line))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _check_decl(rel: str, decl: Decl, rules: _CompiledRules) -> Iterable[Issue]:
|
|
251
|
+
issues: list[Issue] = []
|
|
252
|
+
kind_rule = rules.kind_patterns.get(decl.kind)
|
|
253
|
+
if kind_rule is not None:
|
|
254
|
+
rule_id, pattern, message = kind_rule
|
|
255
|
+
if not pattern.fullmatch(decl.name):
|
|
256
|
+
issues.append(
|
|
257
|
+
Issue(
|
|
258
|
+
file=rel,
|
|
259
|
+
line=decl.line,
|
|
260
|
+
rule=rule_id,
|
|
261
|
+
severity="error",
|
|
262
|
+
msg=f"{decl.kind} '{decl.name}' {message}",
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
for lx in rules.lexical:
|
|
267
|
+
if lx.violated_by(decl.name):
|
|
268
|
+
issues.append(
|
|
269
|
+
Issue(
|
|
270
|
+
file=rel,
|
|
271
|
+
line=decl.line,
|
|
272
|
+
rule=lx.rule,
|
|
273
|
+
severity="error",
|
|
274
|
+
msg=f"'{decl.name}' {lx.message}",
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if rules.vocabulary is not None:
|
|
279
|
+
hit = rules.vocabulary.offending_word(decl.name)
|
|
280
|
+
if hit is not None:
|
|
281
|
+
bad, good = hit
|
|
282
|
+
issues.append(
|
|
283
|
+
Issue(
|
|
284
|
+
file=rel,
|
|
285
|
+
line=decl.line,
|
|
286
|
+
rule=rules.vocabulary.rule,
|
|
287
|
+
severity="error",
|
|
288
|
+
msg=f"'{decl.name}' uses '{bad}'; {rules.vocabulary.message} '{good}'",
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
return issues
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def main() -> None:
|
|
295
|
+
"""Write the naming-rules JSON Schema (`python -m chipgraph.checks.naming [PATH]`)."""
|
|
296
|
+
import sys
|
|
297
|
+
|
|
298
|
+
from chipgraph.checks._naming_rules import schema_json
|
|
299
|
+
|
|
300
|
+
out = (
|
|
301
|
+
Path(sys.argv[1]) if len(sys.argv) > 1 else Path("schemas/formats/naming-rules.schema.json")
|
|
302
|
+
)
|
|
303
|
+
out.write_text(schema_json(), encoding="utf-8")
|
|
304
|
+
print(f"wrote {out}")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
main()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
__all__ = ["NamingCheck"]
|