-
Notifications
You must be signed in to change notification settings - Fork 3
fix: various fixes and improvements #62
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: next
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| export { default } from "./interpolate"; | ||
| export type { InterpolateProps, Mapping, MappingRenderFunction, MappingValue } from "./interpolate"; | ||
|
|
||
| export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from "./syntax"; | ||
| export { createSyntaxRule, SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from "./syntax"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] |
||
| export type { Syntax, SyntaxRule } from "./syntax"; | ||
|
|
||
| export { TOKEN_PLACEHOLDER, TOKEN_OPEN_TAG, TOKEN_CLOSE_TAG, TOKEN_SELF_TAG } from "./constants"; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,9 @@ import type { SyntaxNode } from "./node"; | |
| import parser from "./parser"; | ||
| import type { Syntax } from "./syntax"; | ||
|
|
||
| export type MappingRenderFunction = (children?: React.ReactNode) => React.ReactNode; | ||
| export type MappingRenderFunction = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] This union widens |
||
| | (() => React.ReactNode) | ||
| | ((children: React.ReactNode) => React.ReactNode); | ||
| export type MappingValue = React.ReactNode | MappingRenderFunction; | ||
| export type Mapping = Record<string, MappingValue>; | ||
|
|
||
|
|
@@ -44,7 +46,7 @@ function createElement(node: SyntaxNode, mapping: Mapping, keyPrefix: string): R | |
| const value = mapping[nodeName]; | ||
|
|
||
| if (typeof value === "function") { | ||
| return value(); | ||
| return (value as () => React.ReactNode)(); | ||
| } | ||
|
|
||
| return value ?? React.createElement(nodeName, null); | ||
|
|
@@ -59,7 +61,7 @@ function createElement(node: SyntaxNode, mapping: Mapping, keyPrefix: string): R | |
| } | ||
|
|
||
| if (typeof value === "function") { | ||
| return value(children); | ||
| return (value as (children: React.ReactNode) => React.ReactNode)(children); | ||
| } | ||
|
|
||
| if (React.isValidElement<{ children?: React.ReactNode }>(value)) { | ||
|
|
@@ -87,7 +89,7 @@ function createElement(node: SyntaxNode, mapping: Mapping, keyPrefix: string): R | |
| } | ||
|
|
||
| if (typeof value === "function") { | ||
| return value(); | ||
| return (value as () => React.ReactNode)(); | ||
| } | ||
|
|
||
| return value; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| import { TOKEN_PLACEHOLDER } from "./constants"; | ||
| import parser from "./parser"; | ||
| import { createSyntaxRule } from "./syntax"; | ||
|
|
||
| describe("parser", () => { | ||
| const tests = [ | ||
|
|
@@ -45,10 +47,19 @@ describe("parser: custom syntax validation", () => { | |
| expect(() => { | ||
| parser("hello {name}", [ | ||
| { | ||
| type: "TOKEN_PLACEHOLDER", | ||
| type: TOKEN_PLACEHOLDER, | ||
| regex: /{\s*(\w+)\s*}/, | ||
| getName(match) { | ||
| return match[1] ?? ""; | ||
| }, | ||
| }, | ||
| ]); | ||
| }).toThrow("Syntax rule regex must use the global flag"); | ||
| }); | ||
|
|
||
| test("syntax rules must capture a token name", () => { | ||
| expect(() => { | ||
| parser("hello {name}", [createSyntaxRule(TOKEN_PLACEHOLDER, /{\s*\w+\s*}/g, 1)]); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] This only covers the error path for missing captures. It still doesn't test the new behavior |
||
| }).toThrow("Syntax rule must capture a token name"); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,38 +9,53 @@ import { | |
| export interface SyntaxRule<T extends SyntaxTokenType = SyntaxTokenType> { | ||
| type: T; | ||
| regex: RegExp; | ||
| getName(match: RegExpExecArray): string; | ||
| } | ||
|
|
||
| export type Syntax = SyntaxRule[]; | ||
|
|
||
| function getCapturedName(match: RegExpExecArray, captureGroup: number): string { | ||
| const name = match[captureGroup]; | ||
|
|
||
| if (name === undefined) { | ||
| throw new Error("Syntax rule must capture a token name"); | ||
| } | ||
|
|
||
| return name; | ||
| } | ||
|
|
||
| export function createSyntaxRule<T extends SyntaxTokenType>( | ||
| type: T, | ||
| regex: RegExp, | ||
| captureGroup: number, | ||
| ): SyntaxRule<T> { | ||
| if (!regex.global) { | ||
| throw new Error("Syntax rule regex must use the global flag"); | ||
| } | ||
|
|
||
| if (!Number.isInteger(captureGroup) || captureGroup < 1) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] The immediate validation errors inside |
||
| throw new Error("Syntax rule capture group must be a positive integer"); | ||
| } | ||
|
|
||
| return { | ||
| type, | ||
| regex, | ||
| getName(match) { | ||
| return getCapturedName(match, captureGroup); | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| export const SYNTAX_BUILT_IN: Syntax = [ | ||
| { | ||
| type: TOKEN_PLACEHOLDER, | ||
| regex: /{\s*(\w+)\s*}/g, | ||
| }, | ||
| { | ||
| type: TOKEN_OPEN_TAG, | ||
| regex: /<(\w+)>/g, | ||
| }, | ||
| { | ||
| type: TOKEN_CLOSE_TAG, | ||
| regex: /<\/(\w+)>/g, | ||
| }, | ||
| { type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }, | ||
| createSyntaxRule(TOKEN_PLACEHOLDER, /{\s*(\w+)\s*}/g, 1), | ||
| createSyntaxRule(TOKEN_OPEN_TAG, /<(\w+)>/g, 1), | ||
| createSyntaxRule(TOKEN_CLOSE_TAG, /<\/(\w+)>/g, 1), | ||
| createSyntaxRule(TOKEN_SELF_TAG, /<(\w+)\s*\/>/g, 1), | ||
| ]; | ||
|
|
||
| export const SYNTAX_I18NEXT: Syntax = [ | ||
| { | ||
| type: TOKEN_PLACEHOLDER, | ||
| regex: /{{\s*(\w+)\s*}}/g, | ||
| }, | ||
| { | ||
| type: TOKEN_OPEN_TAG, | ||
| regex: /<(\w+)>/g, | ||
| }, | ||
| { | ||
| type: TOKEN_CLOSE_TAG, | ||
| regex: /<\/(\w+)>/g, | ||
| }, | ||
| { type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }, | ||
| createSyntaxRule(TOKEN_PLACEHOLDER, /{{\s*(\w+)\s*}}/g, 1), | ||
| createSyntaxRule(TOKEN_OPEN_TAG, /<(\w+)>/g, 1), | ||
| createSyntaxRule(TOKEN_CLOSE_TAG, /<\/(\w+)>/g, 1), | ||
| createSyntaxRule(TOKEN_SELF_TAG, /<(\w+)\s*\/>/g, 1), | ||
| ]; | ||
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.
[P2] This matrix now runs
npm run checktwice, but onlytypecheckis React-version-sensitive;check:formatandcheck:lintare identical on both legs. Splitting those invariant checks into a single non-matrix job, or running them only on one leg, would avoid a full duplicate pass without reducing the React 18/19 coverage.