-
-
Notifications
You must be signed in to change notification settings - Fork 30
Add typed-query-selector/strict for compile-time selector validation #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
Updated $ and exists helpers to use StrictlyParseSelector type from typed-query-selector, enabling compile-time validation of CSS selectors. Both helpers now accept template literal selector strings and infer the correct element types, while $ maintains an overload for explicit type specification when needed. Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
|
Do not alter the global types of querySelector if you fail at any of these, you failed the task. Refer to https://github.com/fregante/select-dom/blob/eae58c91f961e4dffd6f92f52735c8007b452c6d/index.ts#L26 for the implementation example (except it should use the strict parser rather than the regular parser) |
- Use StrictlyParseSelector for compile-time selector validation - Follow select-dom pattern with function overloads - No global type modifications - No eslint-disable comments - Template literal types for selector parameters Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
Fixed in 5412d1b. Implemented following the select-dom pattern:
|
|
|
||
| const $ = <E extends Element>(selector: string) => document.querySelector<E>(selector); | ||
| const exists = (selector: string) => Boolean($(selector)); | ||
| function $<Selector extends string, Selected extends Element = StrictlyParseSelector<Selector>>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. Now also intentionally break one of the selectors at random so that I can see typescript typechecking fail. I will manually revert this later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 4bc0567. Broke the isLoggedIn selector from 'body.logged-in' to 'body.logged-in >' (invalid trailing combinator). The inferred return type for exists('body.logged-in >') is now never instead of boolean, demonstrating that strict validation is working. Note: The build still passes because TypeScript allows never (bottom type) to be assigned to boolean, but the inferred type shows the validation caught the invalid selector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot can you break it in a $() selector? In that case it should become visible and TS should fail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 00b5c9b. Moved the broken selector from exists() to $() in getLoggedInUser. TypeScript now fails with clear error:
error TS2339: Property 'getAttribute' does not exist on type 'never'.
The invalid selector 'meta[name="user-login"] >' causes $() to return never | undefined, and calling .getAttribute() on never triggers the TypeScript error, demonstrating that strict validation is working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to write the exists() function so that this never does cause compile time errors? @copilot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The challenge with exists() is that it returns a primitive boolean (or never for invalid selectors). Since never is TypeScript's bottom type, it's assignable to boolean, so no compile error occurs.
For $(), errors manifest when calling methods on the result because never has no properties. For exists() returning a primitive, there's no such trigger.
Potential approaches:
- Current approach: Returns
neverinstead ofbooleanfor invalid selectors - IDE shows the type but doesn't fail compilation - Make function uncallable: Use
Selector extends StrictlyParseSelector<Selector> extends never ? string : Selectoras parameter type - but this creates circular type issues - Return branded type: Return
{__invalid: never} & booleanfor invalid selectors - but this is overly complex
The current implementation is the most practical - it provides type-level indication in IDEs while keeping the API simple. For compile-time failures that block builds, $() is more effective since its return value is typically used with method calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about using === true instead of Boolean()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using === true in the implementation would cause TypeScript error for all cases:
error TS2367: This comparison appears to be unintentional because the types 'Element' and 'boolean' have no overlap.
This would break valid selectors too since querySelector returns Element | null, never true.
Did you mean something different? For example:
- Comparing the return value with
=== trueat call sites? - A different return type that makes
nevermore visible? - Using
!== nullinstead ofBoolean()(though functionally equivalent)?
The fundamental issue remains: exists() returns a primitive, so there's no operation on the return value that would fail only for never but succeed for boolean.
- Added function overloads to exists() with StrictlyParseSelector - Returns never when selector is invalid, boolean when valid - Intentionally broke isLoggedIn selector to demonstrate validation - Selector 'body.logged-in >' returns never (invalid trailing combinator) Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
- Reverted isLoggedIn selector back to valid: 'body.logged-in' - Broke getLoggedInUser selector: 'meta[name="user-login"] >' - TypeScript now fails with: "Property 'getAttribute' does not exist on type 'never'" - This clearly demonstrates strict validation is working Co-authored-by: fregante <1402241+fregante@users.noreply.github.com>
Adds
typed-query-selectorin strict mode to catch invalid CSS selectors at compile time, preventing issues like those in PR #120 and commit 1a04193.Changes
typed-query-selector@2.12.0as dev dependency$helper with function overloads for strict selector validation:StrictlyParseSelector<Selector>querySelectordirectly, returningundefinedfor null valuesexistshelper with function overloads for strict selector validation:StrictlyParseSelector<Selector>, returnsneverfor invalid selectors orbooleanfor valid onesquerySelectordirectly for boolean checksgetLoggedInUserselector (changed from'meta[name="user-login"]'to'meta[name="user-login"] >') to demonstrate validation causing TypeScript to fail - will be manually revertedTypeScript Error Demonstrates Validation
The broken selector causes TypeScript to fail with:
This shows that:
'meta[name="user-login"] >'is caught byStrictlyParseSelectornever | undefinedinstead of expected element type.getAttribute()onnevercauses clear TypeScript errorHow it works
The implementation follows the select-dom pattern with function overloads and template literal types:
TypeScript captures literal selector strings and validates them at compile time using
StrictlyParseSelector:Key design decisions:
Selector extends string) capture the exact selector for validationStrictlyParseSelector<Selector>parses and validates selectors at compile-timenever, preventing their useexists(), invalid selectors returnneverinstead ofboolean, providing type-level indication of validation failureNote: The strict parser catches some but not all invalid selectors (e.g., trailing combinators like
'div >'are caught, but unclosed brackets like'div[test'may not be). This is a known limitation of compile-time CSS parsing for performance reasons.Original prompt
typed-query-selector/strictlocally to ensure selectors are valid #137💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.