feat(ledger): the consumer map reads transition names and concatenated prefixes (#2970)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Failing after 10s
CI & Build / integration (push) Successful in 26s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 23s
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Failing after 10s
CI & Build / integration (push) Successful in 26s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 23s
Paying down #2962 measured `flag="unused-css"` against a hand audit and found it had a permanent false-positive floor: roughly sixty classes it called unused are alive and always would be, because two ordinary authoring forms produce names no reader of `class=` attributes can see. A flag whose list you cannot act on line by line is worse than no flag — act on it and you delete live UI. - A transition `name=` IS a class reference. `<Transition name="toast">` makes Vue apply `.toast-enter-active` and its siblings at runtime, and React's `<CSSTransition classNames="fade">` does the same with a different suffix set. Every spelling of the tag is read (`Transition`, `TransitionGroup`, `transition-group`), and the emitted suffix set is the union of Vue 3, Vue 2 and React: naming a class no rule defines costs nothing, since it resolves to no row. A bound `:name` stays unknowable. - A concatenated name contributes its static head as a PREFIX reference. `` `status-${s}` ``, `'pri-' + p` and `class="card-{{ v }}"` all leave a head behind once the hole is blanked — and `_CLASS_TOKEN_RE` accepts a trailing hyphen, so until now the extractor emitted a junk token `"status-"` that matched nothing. It is now `status-*`, and resolve_consumers credits every row whose symbol starts with that head, each under the same own-file-else-fan-out rule as an exact token. `*` cannot occur in a class token, so the marker rides the existing dict[str, int] with no schema change. A head shorter than two characters says nothing and is dropped. Crediting every candidate row is the honest reading: the template genuinely does not say which one it built, and the alternative is reporting live rules as dead. What the map still cannot see is a name assembled in a script — `classList.add` — which stays deliberately out of scope; the skill, the `flag=` docstring and the refresh payload docs all say so now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -473,6 +473,40 @@ def test_resolve_consumers_prefers_the_own_file_and_fans_out_for_shared_names():
|
||||
assert all(sid != 6 for sid, _ in resolve_consumers(css_rows, refs))
|
||||
|
||||
|
||||
def test_resolve_consumers_credits_every_row_a_prefix_could_have_built():
|
||||
"""#2970: `status-${s}` names a class the map cannot pin down, so the
|
||||
prefix reference `status-*` credits every row whose symbol starts with
|
||||
the head — each under the same own-file-else-fan-out rule. Crediting all
|
||||
of them is the honest reading: the alternative is calling live rules
|
||||
unused, which is what the flag existed to avoid."""
|
||||
from scribe.services.shape_ledger import resolve_consumers
|
||||
css_rows = [
|
||||
(1, "assets/app.css", "status-done"),
|
||||
(2, "assets/app.css", "status-todo"),
|
||||
(3, "v/Board.vue", "status-done"), # a scoped copy of one of them
|
||||
(4, "assets/app.css", "btn-primary"),
|
||||
(5, "assets/anim.css", "toast-enter-active"),
|
||||
]
|
||||
# A file that defines none of them fans out across every match.
|
||||
assert resolve_consumers(css_rows, {"v/List.vue": {"status-*": 2}}) == {
|
||||
(1, "v/List.vue"): 2, (2, "v/List.vue"): 2, (3, "v/List.vue"): 2,
|
||||
}
|
||||
# A file that DOES define one keeps the own-file rule, per symbol: its own
|
||||
# status-done row, and the shared status-todo it does not define.
|
||||
assert resolve_consumers(css_rows, {"v/Board.vue": {"status-*": 1}}) == {
|
||||
(3, "v/Board.vue"): 1, (2, "v/Board.vue"): 1,
|
||||
}
|
||||
# A prefix that matches nothing is silent, and exact tokens are untouched.
|
||||
assert resolve_consumers(css_rows, {"v/X.vue": {"zz-*": 1}}) == {}
|
||||
assert resolve_consumers(css_rows, {"v/X.vue": {"btn-primary": 3}}) == {
|
||||
(4, "v/X.vue"): 3,
|
||||
}
|
||||
# A transition class resolves exactly, like any other name.
|
||||
assert resolve_consumers(css_rows, {"c/Toast.vue": {"toast-enter-active": 1}}) == {
|
||||
(5, "c/Toast.vue"): 1,
|
||||
}
|
||||
|
||||
|
||||
def test_consumer_edges_table_cascades_with_the_shape():
|
||||
from scribe.models import Base
|
||||
from scribe.models.code_shape import CONSUMER_BASES, CodeShapeConsumer
|
||||
|
||||
Reference in New Issue
Block a user