Skip to content

Commit

Permalink
feat(puppeteer-core): Infer element type from complex selector (#9253)
Browse files Browse the repository at this point in the history
**What kind of change does this PR introduce?**

Better type inference.

**Did you add tests for your changes?**

~Not yet.~ Yes.

**If relevant, did you update the documentation?**

Not yet.

**Summary**

<!-- Explain the **motivation** for making this change. What existing
problem does the pull request solve? -->
<!-- Try to link to an open issue for more information. -->
Currently methods that return an element handle, i.e. `.$`,
`.waitForSelector` attempt to infer the node element type from the
selector string. However, this only works when the selector is an exact
match of the element tag, i.e. a selector `"a"` would be inferred as
`HTMLAnchorElement` . And not when the selector is complex, i.e.
selectors `"a#some-id"`, `div > a`, `a:nth-child(2)` would all fallback
on `Element`.

This is due to simply looking up the the selector in
`HTMLElementTagNameMap` and `SVGElementTagNameMap` without any attempt
to parse the selector string.

This PR is an attempt to do so.

**Does this PR introduce a breaking change?**

<!-- If this PR introduces a breaking change, please describe the impact
and a migration path for existing applications. -->
This could break existing incorrect assertions using the `as` keyword.

**Other information**

~This PR introduces a dependency on the `type-fest` package.~

This PR is far from complete (no tests, no docs). Put out early for
feedback and discussion.

Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
  • Loading branch information
gomain and OrKoN committed Nov 23, 2022
1 parent 16784fc commit bef1061
Show file tree
Hide file tree
Showing 4 changed files with 1,125 additions and 22 deletions.
14 changes: 8 additions & 6 deletions docs/api/puppeteer.nodefor.md
Expand Up @@ -7,10 +7,12 @@ sidebar_label: NodeFor
#### Signature:

```typescript
export declare type NodeFor<Selector extends string> =
Selector extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[Selector]
: Selector extends keyof SVGElementTagNameMap
? SVGElementTagNameMap[Selector]
: Element;
export declare type NodeFor<ComplexSelector extends string> =
TypeSelectorOfComplexSelector<ComplexSelector> extends infer TypeSelector
? TypeSelector extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[TypeSelector]
: TypeSelector extends keyof SVGElementTagNameMap
? SVGElementTagNameMap[TypeSelector]
: Element
: never;
```
103 changes: 97 additions & 6 deletions packages/puppeteer-core/src/common/types.ts
Expand Up @@ -67,9 +67,100 @@ export type EvaluateFunc<T extends unknown[]> = (
/**
* @public
*/
export type NodeFor<Selector extends string> =
Selector extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[Selector]
: Selector extends keyof SVGElementTagNameMap
? SVGElementTagNameMap[Selector]
: Element;
export type NodeFor<ComplexSelector extends string> =
TypeSelectorOfComplexSelector<ComplexSelector> extends infer TypeSelector
? TypeSelector extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[TypeSelector]
: TypeSelector extends keyof SVGElementTagNameMap
? SVGElementTagNameMap[TypeSelector]
: Element
: never;

type TypeSelectorOfComplexSelector<ComplexSelector extends string> =
CompoundSelectorsOfComplexSelector<ComplexSelector> extends infer CompoundSelectors
? CompoundSelectors extends NonEmptyReadonlyArray<string>
? Last<CompoundSelectors> extends infer LastCompoundSelector
? LastCompoundSelector extends string
? TypeSelectorOfCompoundSelector<LastCompoundSelector>
: never
: never
: unknown
: never;

type TypeSelectorOfCompoundSelector<CompoundSelector extends string> =
SplitWithDelemiters<
CompoundSelector,
BeginSubclassSelectorTokens
> extends infer CompoundSelectorTokens
? CompoundSelectorTokens extends [infer TypeSelector, ...any[]]
? TypeSelector extends ''
? unknown
: TypeSelector
: never
: never;

type Last<Arr extends NonEmptyReadonlyArray<unknown>> = Arr extends [
infer Head,
...infer Tail
]
? Tail extends NonEmptyReadonlyArray<unknown>
? Last<Tail>
: Head
: never;

type NonEmptyReadonlyArray<T> = [T, ...(readonly T[])];

type CompoundSelectorsOfComplexSelector<ComplexSelector extends string> =
SplitWithDelemiters<
ComplexSelector,
CombinatorTokens
> extends infer IntermediateTokens
? IntermediateTokens extends readonly string[]
? Drop<IntermediateTokens, ''>
: never
: never;

type SplitWithDelemiters<
Input extends string,
Delemiters extends readonly string[]
> = Delemiters extends [infer FirstDelemiter, ...infer RestDelemiters]
? FirstDelemiter extends string
? RestDelemiters extends readonly string[]
? FlatmapSplitWithDelemiters<Split<Input, FirstDelemiter>, RestDelemiters>
: never
: never
: [Input];

type BeginSubclassSelectorTokens = ['.', '#', '[', ':'];

type CombinatorTokens = [' ', '>', '+', '~', '|', '|'];

type Drop<Arr extends readonly unknown[], Remove> = Arr extends [
infer Head,
...infer Tail
]
? Head extends Remove
? Drop<Tail, Remove>
: [Head, ...Drop<Tail, Remove>]
: [];

type FlatmapSplitWithDelemiters<
Inputs extends readonly string[],
Delemiters extends readonly string[]
> = Inputs extends [infer FirstInput, ...infer RestInputs]
? FirstInput extends string
? RestInputs extends readonly string[]
? [
...SplitWithDelemiters<FirstInput, Delemiters>,
...FlatmapSplitWithDelemiters<RestInputs, Delemiters>
]
: never
: never
: [];

type Split<
Input extends string,
Delemiter extends string
> = Input extends `${infer Prefix}${Delemiter}${infer Suffix}`
? [Prefix, ...Split<Suffix, Delemiter>]
: [Input];

0 comments on commit bef1061

Please sign in to comment.