Skip to content

Conversation

@cadolphs
Copy link

This commit adds support to use straightforward negated expressions in Alpine directives in the CSP build.

While we (probably) do not want to rewrite a full CSP-save expression evaluator to handle expressions of arbitrary complexity, directives with negation are such a common use case that it makes sense to support them.

Consider, for example,

  • x-show="!loading"
  • :disabled="!valid"

In the CSP, we would have to write custom getters for each boolean attribute whose negation we might want to use, adding needless verbosity.

On the other hand, being a unary operator, hand-rolling negation is easy enough.

To test that it all works, we add relevant cypress tests to the csp spec to verify that we can negate both a plain attribute as well as the result of a function call.

This commit adds support to use straightforward negated expressions in
Alpine directives in the CSP build.

While we (probably) do not want to rewrite a full CSP-save expression
evaluator to handle expressions of arbitrary complexity, directives with
negation are such a common use case that it makes sense to support them.

Consider, for example,

* `x-show="!loading"`
* `:disabled="!valid"`

In the CSP, we would have to write custom getters for each boolean
attribute whose negation we might want to use, adding needless
verbosity.

On the other hand, being a unary operator, hand-rolling negation is easy
enough.

To test that it all works, we add relevant cypress tests to the csp
spec to verify that we can negate both a plain attribute as well as the
result of a function call.
@ekwoka
Copy link
Contributor

ekwoka commented Nov 24, 2024

The part for "negation of a function call" I think is problematic. As this would now introduce unique behavior to the CSP build that doesn't work in Alpine standard, or in javascript.

🤔

It introduces a bit of unnecessary magic I think...

@cadolphs
Copy link
Author

Thanks for the feedback. Just so I understand correctly, current Alpine.js wouldn't be able to handle an expression like !foo() where foo is a function returning a bool?

@ekwoka
Copy link
Contributor

ekwoka commented Nov 25, 2024

Thanks for the feedback. Just so I understand correctly, current Alpine.js wouldn't be able to handle an expression like !foo() where foo is a function returning a bool?

It would.

but !foo with foo being a function would be false. It would not evaluate foo.

Here, your case seems to be running the function, and then negativing it's result. Based on that second test.

That is different results than the normal evaluator.

@SimoTod
Copy link
Collaborator

SimoTod commented Nov 25, 2024

I think you can apply the negation to evaluatedExpression before passing it into runIfTypeOfFunction to preserve the same behaviour as the non-CSP built

@cadolphs
Copy link
Author

I think you can apply the negation to evaluatedExpression before passing it into runIfTypeOfFunction to preserve the same behaviour as the non-CSP built

That's what I did initially, and then I started overthinking it, assuming that I needed to support the function-calling thing.

To keep feature parity with the Alpine standard build, we remove
function evaluation of negated function calls (`x-show=!foo`) where foo
is a function.

Also adjust the test so it checks for proper trimming and modify the
trimming of the expression to be evaluated to allow for whitespace
between the negation operatior and the expression: `x-show="! foo`.
@cadolphs
Copy link
Author

Thanks for your comments, everyone. I made the changes as recommended and pushed a new commit.

@ekwoka
Copy link
Contributor

ekwoka commented Nov 26, 2024

Yeah, I could see the motivation for it, and it could make sense in a strict "built from nothing" situation.

But in this case, divergent behaviors would be an issue.

@cadolphs cadolphs requested a review from levu42 December 1, 2024 03:27
@cadolphs
Copy link
Author

@ekwoka I've taken out the extra function evaluation magic; wondering if anyone wants to take a second look.

@cadolphs
Copy link
Author

cadolphs commented Mar 7, 2025

Did a merge with main and now the tests pass (was a spurious unrelated failure).

Any further thoughts on integrating this?

@cadolphs
Copy link
Author

@calebporzio Hi Caleb, thought I'd ping you here. I love Alpine and have pushed hard for it to be used in a commercial project, but due to CSP requirements we have to use the CSP build, and I just wanted to add this small ergonomic enhancement so that in addition to "plain" attributes, we can also use negations.

It's now been several months with no additional input on where this should go, so I was hoping to get your eyeballs on this.

Cheers!

@cadolphs
Copy link
Author

@ekwoka I see you being quite active in the discussions around here, so was hoping to get your thoughts on the whole CSP build side of things; am I missing something about how I should go about moving this PR forward?

Copy link
Contributor

@ekwoka ekwoka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Simple and clean

@cadolphs
Copy link
Author

Thanks; now what can we do to get it into a release? :D

@tanczosm
Copy link

tanczosm commented May 1, 2025

It's definitely frustrating to have to write custom negation methods for everything. (notIsOpen)

It also seems more like a hacky workaround to have to do this for CSP builds. I'm looking forward to seeing an update with this alteration.

@calebporzio
Copy link
Collaborator

PR Review: #4441 — Modify the CSP Evaluator to handle negated expressions

Type: Feature (CSP enhancement)
Verdict: Close

What's happening (plain English)

  1. In the old CSP build, Alpine evaluated expressions by splitting on . and walking the data scope — basically expression.split('.').reduce(...). This meant only simple property access like foo or foo.bar worked.
  2. Expressions like !foo or !loading would fail because !foo isn't a valid property name.
  3. This PR added a string-based hack: strip a leading !, evaluate the rest, then negate the result.
  4. However, since this PR was opened, the CSP build on main was completely rewritten with a proper expression parser (packages/csp/src/parser.js). This parser has a full tokenizer, AST builder, and evaluator that handles UnaryExpression nodes — including !, -, and + operators natively.

In short: the problem this PR solves no longer exists on main.

Proof

I added the contributor's exact negation test to main (without any of this PR's evaluator changes) and ran it:

test.csp('supports simple negation', ...)
// x-text="!foo" with foo: false → displays "true"
// click toggle → displays "false"

Result: 18/18 tests pass, including the negation test. The new CSP parser handles !foo out of the box.

Other approaches considered

  1. This PR's approach (string prefix stripping): Was reasonable given the old evaluator, but is now obsolete.
  2. The approach main took (full expression parser): Handles !, ternaries, comparisons, function calls, and much more. This is the right long-term solution and already landed.
  3. No action needed: The feature request is already fulfilled.

Changes Made

No changes made to the PR branch. No changes needed.

Test Results

On main: 18/18 CSP tests pass, including a negation test I added to verify. CI on the PR itself also shows passing (though against the old codebase).

Code Review

The PR's code was clean and well-intentioned, and the contributor was responsive to feedback (removed the function-call negation magic after reviewer comments). But the entire evaluator architecture has been replaced since this PR was opened.

Security

No security concerns — the new parser on main includes robust security protections (iframe/script blocking, global access prevention, property blacklisting) that the old evaluator lacked.

Verdict

Close. This is not a reflection of the contributor's work quality — the PR was reasonable when submitted. But the CSP build has since been rewritten with a proper expression parser that handles negation (and much more) natively. Merging this would conflict with the new architecture entirely.

@cadolphs — thank you for the contribution and patience. The good news is that the feature you wanted (!foo in CSP expressions) now works out of the box on main. No workaround needed.


Reviewed by Claude

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants