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:
@@ -152,6 +152,45 @@ def test_class_references_reads_vue_static_and_dynamic_forms_only():
|
||||
}
|
||||
|
||||
|
||||
def test_class_references_reads_framework_transition_names():
|
||||
"""A transition `name=` is a class reference: the framework applies
|
||||
`.toast-enter-active` and friends at runtime, so a stylesheet that
|
||||
defines them is consumed even though no template spells one out (#2970).
|
||||
Every spelling of the tag counts; a bound `:name` stays unknowable."""
|
||||
refs = class_references(
|
||||
"a/T.vue",
|
||||
'<transition-group name="toast"><div class="toast-item" /></transition-group>',
|
||||
)
|
||||
for suffix in ("-enter-from", "-enter-active", "-leave-to", "-move"):
|
||||
assert refs["toast" + suffix] == 1, suffix
|
||||
assert refs["toast-item"] == 1 # the static attribute still counts
|
||||
|
||||
assert "peek-enter-active" in class_references("a/T.vue", '<Transition name="peek">')
|
||||
assert "g-move" in class_references("a/T.vue", '<TransitionGroup name="g">')
|
||||
# React's CSSTransition names the same idea with a different suffix set
|
||||
react = class_references("a/T.jsx", '<CSSTransition classNames="fade">')
|
||||
assert {"fade-enter-active", "fade-exit-active", "fade-exit-done"} <= set(react)
|
||||
# A bound name is a variable, not a name we can read
|
||||
assert class_references("a/T.vue", '<Transition :name="dyn">') == {}
|
||||
|
||||
|
||||
def test_class_references_reads_a_concatenated_name_as_a_prefix():
|
||||
"""`status-${s}` cannot be resolved to one class, but its static head is
|
||||
real information: it is emitted as the prefix reference `status-*` so
|
||||
the rows it could have built are not reported unused (#2970). A head too
|
||||
short to mean anything, or a bare separator, says nothing."""
|
||||
vue = (
|
||||
'<div :class="`status-${task.status}`" />'
|
||||
"<span :class=\"['pri-' + p]\" />"
|
||||
'<b class="a-" /><u class="-" />'
|
||||
)
|
||||
assert class_references("a/P.vue", vue) == {"status-*": 1, "pri-*": 1}
|
||||
# a server template interpolating into the middle of a name, same reading
|
||||
assert class_references("t/p.html", '<i class="card-{{ v }}" />') == {"card-*": 1}
|
||||
# an ordinary name never picks up the marker
|
||||
assert class_references("a/P.vue", '<div class="page-header" />') == {"page-header": 1}
|
||||
|
||||
|
||||
def test_class_references_reads_react_svelte_and_server_templates():
|
||||
tsx = (
|
||||
"export function X({ on }: { on: boolean }) {\n"
|
||||
|
||||
Reference in New Issue
Block a user