@astryxdesign/core 0.6.2 → 0.6.3-canary.0788c49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/dist/BottomSheet/BottomSheet.d.ts +1 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +3 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -19
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +13 -28
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +10 -5
- package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +48 -23
- package/dist/Chat/ChatComposerInput.d.ts +11 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +63 -16
- package/dist/Chat/ChatDictationButton.d.ts +3 -2
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +6 -4
- package/dist/Chat/ChatLayout.d.ts +19 -0
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayout.js +19 -0
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +44 -34
- package/dist/Chat/ChatMessageBubble.d.ts +2 -0
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +5 -3
- package/dist/Chat/ChatMessageList.d.ts +2 -1
- package/dist/Chat/ChatMessageList.d.ts.map +1 -1
- package/dist/Chat/ChatMessageList.js +4 -3
- package/dist/Chat/ChatMessageMetadata.d.ts +5 -4
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatMessageMetadata.js +12 -9
- package/dist/Chat/ChatSendButton.d.ts +2 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.js +6 -4
- package/dist/Chat/index.d.ts +1 -1
- package/dist/Chat/index.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +12 -2
- package/dist/Citation/Citation.d.ts +1 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +3 -2
- package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
- package/dist/CodeBlock/highlightRanges.js +14 -3
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +6 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +1 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +4 -4
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +16 -16
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +2 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +4 -4
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +8 -8
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
- package/dist/Dialog/DialogHeader.d.ts +8 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +13 -4
- package/dist/Field/InputClearButton.d.ts +4 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +4 -2
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +7 -7
- package/dist/Icon/Icon.d.ts +6 -31
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +8 -71
- package/dist/Icon/IconDefaultSizeContext.d.ts +4 -0
- package/dist/Icon/IconDefaultSizeContext.d.ts.map +1 -0
- package/dist/Icon/IconDefaultSizeContext.js +18 -0
- package/dist/Icon/IconSize.stylex.d.ts +65 -0
- package/dist/Icon/IconSize.stylex.d.ts.map +1 -0
- package/dist/Icon/IconSize.stylex.js +75 -0
- package/dist/Item/Item.d.ts +1 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +4 -3
- package/dist/Kbd/Kbd.d.ts +5 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +36 -42
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -2
- package/dist/Link/useLinkComponent.d.ts +5 -15
- package/dist/Link/useLinkComponent.d.ts.map +1 -1
- package/dist/Link/useLinkComponent.js +34 -51
- package/dist/List/List.d.ts +19 -1
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +9 -2
- package/dist/List/ListContext.d.ts +1 -0
- package/dist/List/ListContext.d.ts.map +1 -1
- package/dist/List/ListContext.js +2 -1
- package/dist/List/ListItem.d.ts +1 -1
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +9 -2
- package/dist/Markdown/Markdown.d.ts +8 -2
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.bench.js +150 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
- package/dist/Markdown/Markdown.js +262 -109
- package/dist/Markdown/ast.d.ts +162 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +74 -0
- package/dist/Markdown/index.d.ts +5 -3
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/index.js +3 -2
- package/dist/Markdown/parser/index.d.ts +10 -0
- package/dist/Markdown/parser/index.d.ts.map +1 -0
- package/dist/Markdown/parser/index.js +10 -0
- package/dist/Markdown/parser.d.ts +126 -58
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +726 -177
- package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
- package/dist/Markdown/plugins/frontmatter.js +132 -0
- package/dist/Markdown/plugins/index.d.ts +17 -0
- package/dist/Markdown/plugins/index.d.ts.map +1 -0
- package/dist/Markdown/plugins/index.js +14 -0
- package/dist/Markdown/plugins/protocol.d.ts +191 -0
- package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
- package/dist/Markdown/plugins/protocol.js +957 -0
- package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
- package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
- package/dist/Markdown/plugins/semanticFence.js +213 -0
- package/dist/Markdown/plugins/softBreaks.d.ts +3 -0
- package/dist/Markdown/plugins/softBreaks.d.ts.map +1 -0
- package/dist/Markdown/plugins/softBreaks.js +172 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
- package/dist/Markdown/plugins/sourceDecoration.js +393 -0
- package/dist/Markdown/plugins/textTransform.d.ts +30 -0
- package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
- package/dist/Markdown/plugins/textTransform.js +374 -0
- package/dist/Markdown/remark.d.ts +196 -0
- package/dist/Markdown/remark.d.ts.map +1 -0
- package/dist/Markdown/remark.js +1062 -0
- package/dist/Markdown/url.d.ts +6 -0
- package/dist/Markdown/url.d.ts.map +1 -0
- package/dist/Markdown/url.js +31 -0
- package/dist/Markdown/utils.d.ts +4 -2
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/Markdown/utils.js +2 -1
- package/dist/MetadataList/MetadataList.js +3 -3
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +4 -4
- package/dist/Outline/index.d.ts +1 -0
- package/dist/Outline/index.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
- package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +8 -3
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +5 -0
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +36 -31
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -1
- package/dist/ScrollableArea/ScrollableArea.d.ts +1 -0
- package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -1
- package/dist/ScrollableArea/ScrollableArea.js +1 -0
- package/dist/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControl.js +1 -0
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
- package/dist/Selector/Selector.d.ts +9 -7
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +16 -7
- package/dist/SideNav/SideNav.d.ts +2 -1
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +9 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +28 -12
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +23 -7
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +11 -0
- package/dist/TabList/Tab.d.ts +1 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +20 -7
- package/dist/Table/Table.d.ts +1 -0
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +29 -13
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +4 -4
- package/dist/Timer/Timer.d.ts +49 -0
- package/dist/Timer/Timer.d.ts.map +1 -0
- package/dist/Timer/Timer.js +158 -0
- package/dist/Timer/index.d.ts +9 -0
- package/dist/Timer/index.d.ts.map +1 -0
- package/dist/Timer/index.js +10 -0
- package/dist/ToggleButton/ToggleButton.d.ts +9 -6
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +15 -27
- package/dist/TopNav/TopNavMenu.d.ts +1 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +3 -2
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +3 -0
- package/dist/TreeList/TreeListItem.d.ts +6 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +12 -2
- package/dist/TreeList/TreeListTypes.d.ts +8 -1
- package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -7
- package/dist/astryx.css +27 -11
- package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
- package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
- package/dist/hooks/scrollKeyboardDelegation.js +146 -0
- package/dist/hooks/useClickableContainer.d.ts +10 -1
- package/dist/hooks/useClickableContainer.d.ts.map +1 -1
- package/dist/hooks/useClickableContainer.js +15 -4
- package/dist/hooks/useScrollableArea.d.ts +6 -2
- package/dist/hooks/useScrollableArea.d.ts.map +1 -1
- package/dist/hooks/useScrollableArea.js +17 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/theme/declarationBoundary.d.ts +17 -0
- package/dist/theme/declarationBoundary.d.ts.map +1 -0
- package/dist/theme/declarationBoundary.js +343 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +4 -0
- package/dist/theme/generateThemeRules.d.ts +13 -7
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +140 -34
- package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +9 -0
- package/dist/utils/safeUrl.d.ts +11 -0
- package/dist/utils/safeUrl.d.ts.map +1 -0
- package/dist/utils/safeUrl.js +78 -0
- package/locales/af-ZA.json +530 -1
- package/locales/ar-SA.json +490 -1
- package/locales/ca-ES.json +514 -1
- package/locales/cs-CZ.json +498 -1
- package/locales/da-DK.json +522 -1
- package/locales/de-DE.json +522 -1
- package/locales/el-GR.json +498 -1
- package/locales/es-ES.json +510 -1
- package/locales/fi-FI.json +498 -1
- package/locales/fr-FR.json +534 -1
- package/locales/he-IL.json +498 -1
- package/locales/hu-HU.json +498 -1
- package/locales/it-IT.json +510 -1
- package/locales/ja-JP.json +490 -1
- package/locales/ko-KR.json +498 -1
- package/locales/nl-NL.json +534 -1
- package/locales/no-NO.json +510 -1
- package/locales/pl-PL.json +506 -1
- package/locales/pt-BR.json +506 -1
- package/locales/pt-PT.json +506 -1
- package/locales/ro-RO.json +506 -1
- package/locales/ru-RU.json +498 -1
- package/locales/sr-SP.json +498 -1
- package/locales/sv-SE.json +502 -1
- package/locales/tr-TR.json +498 -1
- package/locales/uk-UA.json +498 -1
- package/locales/vi-VN.json +502 -1
- package/locales/zh-CN.json +490 -1
- package/locales/zh-TW.json +490 -1
- package/package.json +24 -4
- package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
- package/src/BottomSheet/BottomSheet.spec.md +46 -20
- package/src/BottomSheet/BottomSheet.test.tsx +6 -3
- package/src/BottomSheet/BottomSheet.tsx +3 -1
- package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
- package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
- package/src/Button/Button.doc.mjs +5 -5
- package/src/Button/Button.tsx +15 -13
- package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
- package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
- package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
- package/src/Button/__tests__/Button.a11y.states.ts +9 -0
- package/src/Chat/ChatComposer.doc.mjs +14 -0
- package/src/Chat/ChatComposer.spec.md +230 -0
- package/src/Chat/ChatComposer.test.tsx +57 -0
- package/src/Chat/ChatComposer.tsx +21 -9
- package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
- package/src/Chat/ChatComposerDrawer.spec.md +267 -0
- package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
- package/src/Chat/ChatComposerDrawer.tsx +38 -9
- package/src/Chat/ChatComposerInput.doc.mjs +114 -26
- package/src/Chat/ChatComposerInput.spec.md +185 -0
- package/src/Chat/ChatComposerInput.test.tsx +113 -3
- package/src/Chat/ChatComposerInput.tsx +78 -13
- package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
- package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
- package/src/Chat/ChatDictationButton.doc.mjs +3 -3
- package/src/Chat/ChatDictationButton.spec.md +160 -0
- package/src/Chat/ChatDictationButton.test.tsx +92 -27
- package/src/Chat/ChatDictationButton.tsx +8 -6
- package/src/Chat/ChatLayout.doc.mjs +12 -4
- package/src/Chat/ChatLayout.spec.md +271 -0
- package/src/Chat/ChatLayout.test.tsx +28 -0
- package/src/Chat/ChatLayout.tsx +19 -0
- package/src/Chat/ChatLayoutScrollButton.spec.md +338 -0
- package/src/Chat/ChatLayoutScrollButton.test.tsx +31 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +31 -14
- package/src/Chat/ChatMessageBubble.doc.mjs +28 -15
- package/src/Chat/ChatMessageBubble.spec.md +263 -0
- package/src/Chat/ChatMessageBubble.test.tsx +93 -0
- package/src/Chat/ChatMessageBubble.tsx +8 -3
- package/src/Chat/ChatMessageList.spec.md +222 -0
- package/src/Chat/ChatMessageList.test.tsx +64 -2
- package/src/Chat/ChatMessageList.tsx +4 -3
- package/src/Chat/ChatMessageMetadata.doc.mjs +57 -3
- package/src/Chat/ChatMessageMetadata.spec.md +220 -0
- package/src/Chat/ChatMessageMetadata.test.tsx +34 -0
- package/src/Chat/ChatMessageMetadata.tsx +13 -13
- package/src/Chat/ChatSendButton.doc.mjs +32 -0
- package/src/Chat/ChatSendButton.spec.md +227 -0
- package/src/Chat/ChatSendButton.test.tsx +128 -8
- package/src/Chat/ChatSendButton.tsx +6 -4
- package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +514 -0
- package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +740 -0
- package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +457 -0
- package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +91 -0
- package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +91 -0
- package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +456 -0
- package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +413 -0
- package/src/Chat/__tests__/ChatSendButton.a11y.chromium.spec.ts +1522 -0
- package/src/Chat/index.ts +1 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -1
- package/src/Citation/Citation.doc.mjs +2 -2
- package/src/Citation/Citation.test.tsx +17 -0
- package/src/Citation/Citation.tsx +4 -2
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
- package/src/ClickableCard/ClickableCard.test.tsx +102 -5
- package/src/CodeBlock/highlightRanges.test.ts +143 -1
- package/src/CodeBlock/highlightRanges.ts +15 -5
- package/src/Collapsible/Collapsible.doc.mjs +11 -0
- package/src/Collapsible/Collapsible.test.tsx +21 -0
- package/src/Collapsible/Collapsible.tsx +5 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
- package/src/ComplexSelector/ComplexSelector.tsx +6 -1
- package/src/DateInput/DateInput.tsx +6 -1
- package/src/DateInput/NativeDateField.tsx +24 -8
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateRangeInput/DateRangeInput.tsx +6 -1
- package/src/DateTimeInput/DateTimeInput.tsx +6 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
- package/src/Dialog/Dialog.doc.mjs +2 -0
- package/src/Dialog/DialogHeader.doc.mjs +22 -0
- package/src/Dialog/DialogHeader.test.tsx +167 -3
- package/src/Dialog/DialogHeader.tsx +37 -6
- package/src/Dialog/modules/DialogHeader.spec.md +101 -42
- package/src/DropdownMenu/DropdownMenu.test.tsx +5 -2
- package/src/Field/InputClearButton.test.tsx +17 -3
- package/src/Field/InputClearButton.tsx +5 -2
- package/src/Field/PanelSearchInput.tsx +5 -2
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
- package/src/FileInput/FileInput.tsx +11 -2
- package/src/Icon/Icon.doc.mjs +7 -5
- package/src/Icon/Icon.spec.md +81 -40
- package/src/Icon/Icon.test.tsx +6 -3
- package/src/Icon/Icon.tsx +20 -68
- package/src/Icon/IconDefaultSizeContext.ts +23 -0
- package/src/Icon/IconSize.stylex.ts +70 -0
- package/src/IconButton/IconButton.doc.mjs +3 -3
- package/src/IconButton/IconButton.test.tsx +40 -0
- package/src/Item/Item.doc.mjs +6 -2
- package/src/Item/Item.tsx +9 -3
- package/src/Kbd/Kbd.doc.mjs +3 -3
- package/src/Kbd/Kbd.test.tsx +57 -1
- package/src/Kbd/Kbd.tsx +57 -37
- package/src/Layout/LayoutFooter.doc.mjs +17 -0
- package/src/Link/Link.doc.mjs +17 -4
- package/src/Link/Link.test.tsx +24 -0
- package/src/Link/Link.tsx +5 -0
- package/src/Link/LinkProvider.doc.mjs +2 -2
- package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +261 -0
- package/src/Link/useLinkComponent.test.tsx +333 -0
- package/src/Link/useLinkComponent.ts +29 -57
- package/src/List/List.doc.mjs +6 -0
- package/src/List/List.spec.md +200 -0
- package/src/List/List.test.tsx +117 -1
- package/src/List/List.tsx +22 -2
- package/src/List/ListContext.tsx +3 -1
- package/src/List/ListItem.doc.mjs +2 -2
- package/src/List/ListItem.source-build.test.mjs +41 -0
- package/src/List/ListItem.tsx +26 -2
- package/src/Markdown/Markdown.doc.mjs +300 -2
- package/src/Markdown/Markdown.fr23.bench.ts +174 -0
- package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
- package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
- package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
- package/src/Markdown/Markdown.public.test.ts +130 -3
- package/src/Markdown/Markdown.renderBoundary.test.tsx +72 -0
- package/src/Markdown/Markdown.spec.md +210 -45
- package/src/Markdown/Markdown.test.tsx +87 -3
- package/src/Markdown/Markdown.tsx +414 -135
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +43 -0
- package/src/Markdown/index.ts +37 -1
- package/src/Markdown/modules/remark.spec.md +269 -0
- package/src/Markdown/modules/softBreaks.spec.md +137 -0
- package/src/Markdown/parser/index.ts +64 -0
- package/src/Markdown/parser.public.test.ts +103 -0
- package/src/Markdown/parser.test.ts +38 -6
- package/src/Markdown/parser.ts +1221 -242
- package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
- package/src/Markdown/plugins/frontmatter.ts +198 -0
- package/src/Markdown/plugins/index.ts +54 -0
- package/src/Markdown/plugins/protocol.test.tsx +1022 -0
- package/src/Markdown/plugins/protocol.ts +1618 -0
- package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
- package/src/Markdown/plugins/semanticFence.ts +424 -0
- package/src/Markdown/plugins/softBreaks.test.tsx +172 -0
- package/src/Markdown/plugins/softBreaks.ts +169 -0
- package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
- package/src/Markdown/plugins/sourceDecoration.ts +539 -0
- package/src/Markdown/plugins/textTransform.test.ts +467 -0
- package/src/Markdown/plugins/textTransform.ts +554 -0
- package/src/Markdown/remark.test.tsx +1877 -0
- package/src/Markdown/remark.ts +1574 -0
- package/src/Markdown/url.ts +36 -0
- package/src/Markdown/utils.ts +13 -1
- package/src/MetadataList/MetadataList.test.tsx +4 -2
- package/src/MetadataList/MetadataList.tsx +3 -3
- package/src/MultiSelector/MultiSelector.test.tsx +1 -35
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
- package/src/NumberInput/NumberInput.tsx +6 -1
- package/src/Outline/Outline.doc.mjs +7 -5
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/index.ts +1 -0
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
- package/src/Outline/useOutlineFromMarkdown.ts +15 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/PowerSearch/PowerSearchValueEditor.test.tsx +51 -0
- package/src/PowerSearch/PowerSearchValueEditor.tsx +4 -0
- package/src/ProgressBar/ProgressBar.test.tsx +29 -11
- package/src/ProgressBar/ProgressBar.tsx +59 -72
- package/src/RadioList/RadioList.doc.mjs +11 -0
- package/src/RadioList/RadioList.test.tsx +32 -0
- package/src/RadioList/RadioListItem.tsx +25 -1
- package/src/ScrollableArea/ScrollableArea.tsx +1 -0
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
- package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
- package/src/SegmentedControl/SegmentedControl.test.tsx +61 -0
- package/src/SegmentedControl/SegmentedControl.tsx +2 -1
- package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
- package/src/Selector/Selector.doc.mjs +3 -3
- package/src/Selector/Selector.spec.md +51 -49
- package/src/Selector/Selector.test.tsx +61 -55
- package/src/Selector/Selector.tsx +34 -15
- package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
- package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
- package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
- package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
- package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
- package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +45 -0
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
- package/src/SideNav/SideNav.doc.mjs +1 -1
- package/src/SideNav/SideNav.test.tsx +10 -0
- package/src/SideNav/SideNav.tsx +14 -2
- package/src/Slider/Slider.doc.mjs +17 -3
- package/src/Slider/Slider.test.tsx +221 -0
- package/src/Slider/Slider.tsx +40 -9
- package/src/Spinner/Spinner.test.tsx +4 -0
- package/src/Spinner/Spinner.tsx +31 -18
- package/src/Switch/Switch.doc.mjs +11 -0
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +28 -0
- package/src/TabList/Tab.tsx +35 -7
- package/src/TabList/TabList.doc.mjs +11 -0
- package/src/TabList/TabList.spec.md +204 -0
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
- package/src/Table/Table.doc.mjs +1 -1
- package/src/Table/Table.spec.md +54 -40
- package/src/Table/Table.test.tsx +83 -10
- package/src/Table/Table.tsx +30 -22
- package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
- package/src/TextArea/TextArea.tsx +6 -1
- package/src/TextInput/TextInput.test.tsx +20 -1
- package/src/TextInput/TextInput.tsx +6 -1
- package/src/TimeInput/TimeInput.tsx +6 -1
- package/src/Timer/Timer.doc.mjs +183 -0
- package/src/Timer/Timer.spec.md +215 -0
- package/src/Timer/Timer.test.tsx +302 -0
- package/src/Timer/Timer.tsx +258 -0
- package/src/Timer/index.ts +11 -0
- package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
- package/src/ToggleButton/ToggleButton.test.tsx +299 -4
- package/src/ToggleButton/ToggleButton.tsx +26 -33
- package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Token/Token.doc.mjs +2 -2
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/TopNav/TopNavMenu.doc.mjs +2 -2
- package/src/TopNav/TopNavMenu.test.tsx +30 -0
- package/src/TopNav/TopNavMenu.tsx +7 -2
- package/src/TreeList/TreeList.doc.mjs +3 -3
- package/src/TreeList/TreeList.spec.md +123 -75
- package/src/TreeList/TreeList.test.tsx +29 -0
- package/src/TreeList/TreeList.tsx +3 -0
- package/src/TreeList/TreeListItem.tsx +15 -1
- package/src/TreeList/TreeListTypes.ts +11 -1
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +21 -7
- package/src/Typeahead/Typeahead.test.tsx +53 -0
- package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
- package/src/__tests__/pressState.ts +93 -0
- package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
- package/src/hooks/scrollKeyboardDelegation.ts +233 -0
- package/src/hooks/useClickableContainer.test.tsx +236 -0
- package/src/hooks/useClickableContainer.ts +20 -4
- package/src/hooks/useScrollableArea.doc.mjs +15 -3
- package/src/hooks/useScrollableArea.test.tsx +59 -1
- package/src/hooks/useScrollableArea.ts +34 -10
- package/src/index.ts +3 -0
- package/src/inputFontFloor.test.ts +106 -0
- package/src/tailwind-theme.css +0 -6
- package/src/theme/Theme.test.tsx +32 -0
- package/src/theme/declarationBoundary.test.ts +126 -0
- package/src/theme/declarationBoundary.ts +347 -0
- package/src/theme/derivedVarRegistry.test.ts +7 -0
- package/src/theme/derivedVarRegistry.ts +1 -0
- package/src/theme/generateThemeRules.test.ts +526 -1
- package/src/theme/generateThemeRules.ts +228 -50
- package/src/theme/tokenValueCompat.test.ts +4 -1
- package/src/utils/interactionOverlay.stylex.ts +20 -0
- package/src/utils/safeUrl.test.ts +88 -0
- package/src/utils/safeUrl.ts +85 -0
|
@@ -0,0 +1,1574 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file remark.ts
|
|
5
|
+
* @input Synchronous transform-only Remark plugins over the supported MDAST subset
|
|
6
|
+
* @output A Markdown transform that applies a validated round trip or fails closed
|
|
7
|
+
* @position Optional tree-shakeable compatibility adapter; never imported by Markdown
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
MarkdownAstDataValue,
|
|
12
|
+
MarkdownAstExtensionNode,
|
|
13
|
+
MarkdownAstRoot,
|
|
14
|
+
} from './ast';
|
|
15
|
+
import {
|
|
16
|
+
isMarkdownPluginData,
|
|
17
|
+
type MarkdownTransform,
|
|
18
|
+
type MarkdownTransformContext,
|
|
19
|
+
} from './plugins/protocol';
|
|
20
|
+
import {isSafeMarkdownParserUrl} from './url';
|
|
21
|
+
|
|
22
|
+
/* ------------------------------------------------------------------ *
|
|
23
|
+
* Public mutable tree the adapted plugin observes
|
|
24
|
+
* ------------------------------------------------------------------ */
|
|
25
|
+
|
|
26
|
+
export interface MarkdownRemarkPoint {
|
|
27
|
+
line?: number;
|
|
28
|
+
column?: number;
|
|
29
|
+
offset?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MarkdownRemarkPosition {
|
|
33
|
+
start: MarkdownRemarkPoint;
|
|
34
|
+
end: MarkdownRemarkPoint;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface MarkdownRemarkNodeBase {
|
|
38
|
+
position?: MarkdownRemarkPosition;
|
|
39
|
+
data?: {[key: string]: unknown};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MarkdownRemarkText extends MarkdownRemarkNodeBase {
|
|
43
|
+
type: 'text';
|
|
44
|
+
value: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface MarkdownRemarkInlineCode extends MarkdownRemarkNodeBase {
|
|
48
|
+
type: 'inlineCode';
|
|
49
|
+
value: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MarkdownRemarkInlineMath extends MarkdownRemarkNodeBase {
|
|
53
|
+
type: 'inlineMath';
|
|
54
|
+
value: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface MarkdownRemarkBreak extends MarkdownRemarkNodeBase {
|
|
58
|
+
type: 'break';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MarkdownRemarkPhrasingParent<
|
|
62
|
+
Type extends 'strong' | 'emphasis' | 'delete',
|
|
63
|
+
> extends MarkdownRemarkNodeBase {
|
|
64
|
+
type: Type;
|
|
65
|
+
children: MarkdownRemarkPhrasingContent[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface MarkdownRemarkLink extends MarkdownRemarkNodeBase {
|
|
69
|
+
type: 'link';
|
|
70
|
+
url: string;
|
|
71
|
+
/** Always `null` on input; a non-empty title cannot round-trip. */
|
|
72
|
+
title?: string | null;
|
|
73
|
+
children: MarkdownRemarkPhrasingContent[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface MarkdownRemarkImage extends MarkdownRemarkNodeBase {
|
|
77
|
+
type: 'image';
|
|
78
|
+
url: string;
|
|
79
|
+
/** Always `null` on input; a non-empty title cannot round-trip. */
|
|
80
|
+
title?: string | null;
|
|
81
|
+
alt: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Astryx-owned citation. Read-only inside the adapter. */
|
|
85
|
+
export interface MarkdownRemarkCitation extends MarkdownRemarkNodeBase {
|
|
86
|
+
type: 'citation';
|
|
87
|
+
sourceId: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Astryx-owned plugin extension node. Read-only inside the adapter. */
|
|
91
|
+
export interface MarkdownRemarkExtension extends MarkdownRemarkNodeBase {
|
|
92
|
+
type: 'extension';
|
|
93
|
+
plugin: string;
|
|
94
|
+
name: string;
|
|
95
|
+
display: 'inline' | 'block';
|
|
96
|
+
source?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type MarkdownRemarkPhrasingContent =
|
|
100
|
+
| MarkdownRemarkText
|
|
101
|
+
| MarkdownRemarkInlineCode
|
|
102
|
+
| MarkdownRemarkInlineMath
|
|
103
|
+
| MarkdownRemarkBreak
|
|
104
|
+
| MarkdownRemarkPhrasingParent<'strong'>
|
|
105
|
+
| MarkdownRemarkPhrasingParent<'emphasis'>
|
|
106
|
+
| MarkdownRemarkPhrasingParent<'delete'>
|
|
107
|
+
| MarkdownRemarkLink
|
|
108
|
+
| MarkdownRemarkImage
|
|
109
|
+
| MarkdownRemarkCitation
|
|
110
|
+
| MarkdownRemarkExtension;
|
|
111
|
+
|
|
112
|
+
export interface MarkdownRemarkHeading extends MarkdownRemarkNodeBase {
|
|
113
|
+
type: 'heading';
|
|
114
|
+
depth: 1 | 2 | 3 | 4 | 5 | 6;
|
|
115
|
+
children: MarkdownRemarkPhrasingContent[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface MarkdownRemarkParagraph extends MarkdownRemarkNodeBase {
|
|
119
|
+
type: 'paragraph';
|
|
120
|
+
children: MarkdownRemarkPhrasingContent[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface MarkdownRemarkCode extends MarkdownRemarkNodeBase {
|
|
124
|
+
type: 'code';
|
|
125
|
+
lang: string | null;
|
|
126
|
+
/** The authored info string after the language, `null` when absent. */
|
|
127
|
+
meta?: string | null;
|
|
128
|
+
value: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface MarkdownRemarkMath extends MarkdownRemarkNodeBase {
|
|
132
|
+
type: 'math';
|
|
133
|
+
value: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface MarkdownRemarkBlockquote extends MarkdownRemarkNodeBase {
|
|
137
|
+
type: 'blockquote';
|
|
138
|
+
children: MarkdownRemarkBlockContent[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface MarkdownRemarkListItem extends MarkdownRemarkNodeBase {
|
|
142
|
+
type: 'listItem';
|
|
143
|
+
checked?: boolean | null;
|
|
144
|
+
/** Only a falsy value round-trips; Astryx has no loose-item contract. */
|
|
145
|
+
spread?: boolean | null;
|
|
146
|
+
children: MarkdownRemarkBlockContent[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface MarkdownRemarkList extends MarkdownRemarkNodeBase {
|
|
150
|
+
type: 'list';
|
|
151
|
+
ordered: boolean;
|
|
152
|
+
start?: number | null;
|
|
153
|
+
spread?: boolean | null;
|
|
154
|
+
/** Astryx-preserved ordered-list marker delimiter. */
|
|
155
|
+
delimiter?: '.' | ')';
|
|
156
|
+
children: MarkdownRemarkListItem[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export type MarkdownRemarkTableAlignment = 'left' | 'center' | 'right' | null;
|
|
160
|
+
|
|
161
|
+
export interface MarkdownRemarkTableCell extends MarkdownRemarkNodeBase {
|
|
162
|
+
type: 'tableCell';
|
|
163
|
+
children: MarkdownRemarkPhrasingContent[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface MarkdownRemarkTableRow extends MarkdownRemarkNodeBase {
|
|
167
|
+
type: 'tableRow';
|
|
168
|
+
children: MarkdownRemarkTableCell[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface MarkdownRemarkTable extends MarkdownRemarkNodeBase {
|
|
172
|
+
type: 'table';
|
|
173
|
+
align: MarkdownRemarkTableAlignment[];
|
|
174
|
+
children: MarkdownRemarkTableRow[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface MarkdownRemarkThematicBreak extends MarkdownRemarkNodeBase {
|
|
178
|
+
type: 'thematicBreak';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type MarkdownRemarkBlockContent =
|
|
182
|
+
| MarkdownRemarkHeading
|
|
183
|
+
| MarkdownRemarkParagraph
|
|
184
|
+
| MarkdownRemarkCode
|
|
185
|
+
| MarkdownRemarkMath
|
|
186
|
+
| MarkdownRemarkBlockquote
|
|
187
|
+
| MarkdownRemarkList
|
|
188
|
+
| MarkdownRemarkTable
|
|
189
|
+
| MarkdownRemarkThematicBreak
|
|
190
|
+
| MarkdownRemarkImage
|
|
191
|
+
| MarkdownRemarkExtension;
|
|
192
|
+
|
|
193
|
+
export interface MarkdownRemarkRoot extends MarkdownRemarkNodeBase {
|
|
194
|
+
type: 'root';
|
|
195
|
+
children: MarkdownRemarkBlockContent[];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* ------------------------------------------------------------------ *
|
|
199
|
+
* Public constrained file and plugin shapes
|
|
200
|
+
* ------------------------------------------------------------------ */
|
|
201
|
+
|
|
202
|
+
export interface MarkdownRemarkMessage {
|
|
203
|
+
reason: string;
|
|
204
|
+
fatal: boolean;
|
|
205
|
+
place: unknown;
|
|
206
|
+
ruleId: string | null;
|
|
207
|
+
toString(): string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The isolated per-invocation file. It exposes readonly source text, finite
|
|
212
|
+
* JSON-like data, and diagnostics — never processor registration, cross-run
|
|
213
|
+
* state, a compiler, I/O, or asynchronous completion.
|
|
214
|
+
*/
|
|
215
|
+
export interface MarkdownRemarkFile {
|
|
216
|
+
readonly value: string;
|
|
217
|
+
readonly data: {[key: string]: unknown};
|
|
218
|
+
readonly messages: MarkdownRemarkMessage[];
|
|
219
|
+
message(
|
|
220
|
+
reason: string | Error,
|
|
221
|
+
place?: unknown,
|
|
222
|
+
ruleId?: string,
|
|
223
|
+
): MarkdownRemarkMessage;
|
|
224
|
+
fail(reason: string | Error, place?: unknown, ruleId?: string): never;
|
|
225
|
+
toString(): string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* A Remark transformer either mutates the tree and returns nothing or returns
|
|
230
|
+
* a replacement root, and an attacher may legitimately return nothing at all.
|
|
231
|
+
* Both shapes have to stay assignable, so `void` belongs in these unions.
|
|
232
|
+
*/
|
|
233
|
+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- see above
|
|
234
|
+
type Returned<Value> = Value | undefined | void;
|
|
235
|
+
|
|
236
|
+
export type MarkdownRemarkTransformer = (
|
|
237
|
+
tree: MarkdownRemarkRoot,
|
|
238
|
+
file: MarkdownRemarkFile,
|
|
239
|
+
) => Returned<MarkdownRemarkRoot>;
|
|
240
|
+
|
|
241
|
+
export type MarkdownRemarkPlugin<
|
|
242
|
+
Settings extends ReadonlyArray<unknown> = readonly [],
|
|
243
|
+
> = (...settings: Settings) => Returned<MarkdownRemarkTransformer>;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* The structural shape a conventional Unified/Remark plugin already has. Its
|
|
247
|
+
* declared transformer may take Unified's callback parameter and may declare
|
|
248
|
+
* an asynchronous return union, so an existing typed plugin type-checks at the
|
|
249
|
+
* callsite without being rewritten. The adapter rejects an actual promise, and
|
|
250
|
+
* a callback-style transformer, at run time instead.
|
|
251
|
+
*
|
|
252
|
+
* Authoring a new plugin against Astryx? Annotate it with
|
|
253
|
+
* `MarkdownRemarkPlugin` to keep a typed tree and file.
|
|
254
|
+
*/
|
|
255
|
+
export type MarkdownRemarkCompatibleTransformer = (...args: never[]) => unknown;
|
|
256
|
+
|
|
257
|
+
export type MarkdownRemarkCompatiblePlugin<
|
|
258
|
+
Settings extends ReadonlyArray<unknown> = readonly [],
|
|
259
|
+
> = (...settings: Settings) => Returned<MarkdownRemarkCompatibleTransformer>;
|
|
260
|
+
|
|
261
|
+
/* ------------------------------------------------------------------ *
|
|
262
|
+
* Internal helpers
|
|
263
|
+
* ------------------------------------------------------------------ */
|
|
264
|
+
|
|
265
|
+
type UnknownRecord = Record<string, unknown>;
|
|
266
|
+
|
|
267
|
+
/** Links a mutable copy back to the canonical node it was copied from. */
|
|
268
|
+
const REMARK_ORIGIN = Symbol('astryx.markdown.remarkOrigin');
|
|
269
|
+
|
|
270
|
+
/** hast escape channels; they are raw-markup sinks, not Markdown data. */
|
|
271
|
+
const RAW_MARKUP_DATA_KEYS: ReadonlyArray<string> = [
|
|
272
|
+
'hName',
|
|
273
|
+
'hProperties',
|
|
274
|
+
'hChildren',
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const PHRASING_TYPES = new Set([
|
|
278
|
+
'text',
|
|
279
|
+
'inlineCode',
|
|
280
|
+
'inlineMath',
|
|
281
|
+
'break',
|
|
282
|
+
'strong',
|
|
283
|
+
'emphasis',
|
|
284
|
+
'delete',
|
|
285
|
+
'link',
|
|
286
|
+
'image',
|
|
287
|
+
'citation',
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
const BLOCK_TYPES = new Set([
|
|
291
|
+
'heading',
|
|
292
|
+
'paragraph',
|
|
293
|
+
'code',
|
|
294
|
+
'math',
|
|
295
|
+
'blockquote',
|
|
296
|
+
'list',
|
|
297
|
+
'table',
|
|
298
|
+
'thematicBreak',
|
|
299
|
+
'image',
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
const PHRASING_PARENTS = new Set([
|
|
303
|
+
'heading',
|
|
304
|
+
'paragraph',
|
|
305
|
+
'strong',
|
|
306
|
+
'emphasis',
|
|
307
|
+
'delete',
|
|
308
|
+
'link',
|
|
309
|
+
'tableCell',
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
const BLOCK_PARENTS = new Set(['root', 'blockquote', 'listItem']);
|
|
313
|
+
|
|
314
|
+
const FIELDS_BY_TYPE: Readonly<Record<string, ReadonlyArray<string>>> = {
|
|
315
|
+
root: ['children'],
|
|
316
|
+
paragraph: ['children'],
|
|
317
|
+
heading: ['depth', 'children'],
|
|
318
|
+
blockquote: ['children'],
|
|
319
|
+
strong: ['children'],
|
|
320
|
+
emphasis: ['children'],
|
|
321
|
+
delete: ['children'],
|
|
322
|
+
tableRow: ['children'],
|
|
323
|
+
tableCell: ['children'],
|
|
324
|
+
text: ['value'],
|
|
325
|
+
inlineCode: ['value'],
|
|
326
|
+
inlineMath: ['value'],
|
|
327
|
+
math: ['value'],
|
|
328
|
+
code: ['lang', 'meta', 'value'],
|
|
329
|
+
link: ['url', 'title', 'children'],
|
|
330
|
+
image: ['url', 'title', 'alt'],
|
|
331
|
+
list: ['ordered', 'start', 'spread', 'delimiter', 'children'],
|
|
332
|
+
listItem: ['checked', 'spread', 'children'],
|
|
333
|
+
table: ['align', 'children'],
|
|
334
|
+
thematicBreak: [],
|
|
335
|
+
break: [],
|
|
336
|
+
citation: ['sourceId'],
|
|
337
|
+
extension: ['plugin', 'name', 'display', 'source'],
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* MDAST node kinds Astryx recognizes by name but deliberately does not
|
|
342
|
+
* support. Naming them makes a rejection actionable while keeping every
|
|
343
|
+
* diagnostic built from adapter-owned vocabulary only.
|
|
344
|
+
*/
|
|
345
|
+
const NAMED_UNSUPPORTED_TYPES: ReadonlySet<string> = new Set([
|
|
346
|
+
'html',
|
|
347
|
+
'yaml',
|
|
348
|
+
'toml',
|
|
349
|
+
'definition',
|
|
350
|
+
'footnote',
|
|
351
|
+
'footnoteDefinition',
|
|
352
|
+
'footnoteReference',
|
|
353
|
+
'imageReference',
|
|
354
|
+
'linkReference',
|
|
355
|
+
'containerDirective',
|
|
356
|
+
'leafDirective',
|
|
357
|
+
'textDirective',
|
|
358
|
+
'mdxjsEsm',
|
|
359
|
+
'mdxFlowExpression',
|
|
360
|
+
'mdxTextExpression',
|
|
361
|
+
'mdxJsxFlowElement',
|
|
362
|
+
'mdxJsxTextElement',
|
|
363
|
+
]);
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* VFile keys the profile knows about and refuses. Naming them makes the
|
|
367
|
+
* refusal actionable while keeping diagnostics free of plugin-chosen text.
|
|
368
|
+
*/
|
|
369
|
+
const NAMED_UNSUPPORTED_FILE_KEYS: ReadonlySet<string> = new Set([
|
|
370
|
+
'path',
|
|
371
|
+
'cwd',
|
|
372
|
+
'history',
|
|
373
|
+
'basename',
|
|
374
|
+
'dirname',
|
|
375
|
+
'extname',
|
|
376
|
+
'stem',
|
|
377
|
+
'stored',
|
|
378
|
+
'result',
|
|
379
|
+
'map',
|
|
380
|
+
'contents',
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
/** The one reason reported when a plugin declares the document unsupported. */
|
|
384
|
+
const FAILED_BY_PLUGIN = 'the plugin reported the document as unsupported';
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* A closed rejection.
|
|
388
|
+
*
|
|
389
|
+
* Its reason is assembled only from this module's own constants, so it can
|
|
390
|
+
* never carry document source, plugin-authored text, or a plugin-chosen
|
|
391
|
+
* identifier — see `knownType` and `knownField`.
|
|
392
|
+
*/
|
|
393
|
+
class RemarkAdapterRejection extends Error {}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* The reason for each rejection this module raised, keyed by the thrown
|
|
397
|
+
* object.
|
|
398
|
+
*
|
|
399
|
+
* A plugin that catches one of our rejections can reach the class through
|
|
400
|
+
* `error.constructor` and throw a forgery, and `message` is writable on any
|
|
401
|
+
* Error — so neither the class nor the message is evidence. Only a throw this
|
|
402
|
+
* registry recorded is trusted, and its reason is read from here.
|
|
403
|
+
*/
|
|
404
|
+
const adapterReasons = new WeakMap<RemarkAdapterRejection, string>();
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Sticky failure state for the run currently on the stack.
|
|
408
|
+
*
|
|
409
|
+
* Every rejection this module raises is recorded here, not only on the throw:
|
|
410
|
+
* a plugin can wrap any adapter call in `try`/`catch` — a prohibited file
|
|
411
|
+
* read, a write, a `delete`, a processor access — and carry on as if it had
|
|
412
|
+
* not happened. The run must still fail, so the verdict lives somewhere the
|
|
413
|
+
* plugin cannot reach or rewrite.
|
|
414
|
+
*
|
|
415
|
+
* Runs never overlap: the adapter is synchronous and rejects anything
|
|
416
|
+
* asynchronous, so a single module-level slot is the whole story.
|
|
417
|
+
*/
|
|
418
|
+
let activeRun: RunState | null = null;
|
|
419
|
+
|
|
420
|
+
interface RunState {
|
|
421
|
+
failed: boolean;
|
|
422
|
+
reason: string | null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function createRunState(): RunState {
|
|
426
|
+
return {failed: false, reason: null};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function reject(reason: string): never {
|
|
430
|
+
if (activeRun != null && !activeRun.failed) {
|
|
431
|
+
activeRun.failed = true;
|
|
432
|
+
activeRun.reason = reason;
|
|
433
|
+
}
|
|
434
|
+
const rejection = new RemarkAdapterRejection(reason);
|
|
435
|
+
adapterReasons.set(rejection, reason);
|
|
436
|
+
throw rejection;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Runs `work` with `state` as the sticky failure target, restoring whatever
|
|
441
|
+
* was active before.
|
|
442
|
+
*/
|
|
443
|
+
function withRunState<Result>(state: RunState, work: () => Result): Result {
|
|
444
|
+
const previous = activeRun;
|
|
445
|
+
activeRun = state;
|
|
446
|
+
try {
|
|
447
|
+
return work();
|
|
448
|
+
} finally {
|
|
449
|
+
activeRun = previous;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Reports whether a value is thenable and, if it is, attaches a handler.
|
|
455
|
+
*
|
|
456
|
+
* An already-rejected promise with no handler becomes an unhandled rejection:
|
|
457
|
+
* it ends a Node SSR process by default, and its reason came from the plugin,
|
|
458
|
+
* so it would print document text the diagnostics work hard to keep out. The
|
|
459
|
+
* value is refused either way; attaching a handler only makes the refusal
|
|
460
|
+
* quiet.
|
|
461
|
+
*
|
|
462
|
+
* `then` is read exactly once. It may be a hostile getter — reading it can
|
|
463
|
+
* throw, or trip a guard of its own — so this never reads it a second time to
|
|
464
|
+
* re-ask the same question.
|
|
465
|
+
*/
|
|
466
|
+
function defuseThenable(value: unknown): boolean {
|
|
467
|
+
if (
|
|
468
|
+
(typeof value !== 'object' && typeof value !== 'function') ||
|
|
469
|
+
value === null
|
|
470
|
+
) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
let then: unknown;
|
|
474
|
+
try {
|
|
475
|
+
then = (value as {then?: unknown}).then;
|
|
476
|
+
} catch {
|
|
477
|
+
// A `then` accessor that throws is a thenable-shaped hostile value.
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
if (typeof then !== 'function') {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
then.call(
|
|
485
|
+
value,
|
|
486
|
+
() => {},
|
|
487
|
+
() => {},
|
|
488
|
+
);
|
|
489
|
+
} catch {
|
|
490
|
+
// A `then` that throws when called is refused all the same.
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Echoes a node type only when it is adapter-owned vocabulary. */
|
|
496
|
+
function knownType(type: unknown): string {
|
|
497
|
+
return typeof type === 'string' &&
|
|
498
|
+
(Object.prototype.hasOwnProperty.call(FIELDS_BY_TYPE, type) ||
|
|
499
|
+
NAMED_UNSUPPORTED_TYPES.has(type))
|
|
500
|
+
? type
|
|
501
|
+
: 'unknown';
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Echoes a file key only when it is a name this module already documents —
|
|
506
|
+
* the supported surface, or an unsupported VFile key the profile explicitly
|
|
507
|
+
* names. A plugin-chosen key is never echoed.
|
|
508
|
+
*/
|
|
509
|
+
function knownFileKey(property: string | symbol): string {
|
|
510
|
+
const key = typeof property === 'symbol' ? 'unknown' : property;
|
|
511
|
+
return FILE_SURFACE.has(key) || NAMED_UNSUPPORTED_FILE_KEYS.has(key)
|
|
512
|
+
? key
|
|
513
|
+
: 'unknown';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Echoes a field name only when it is adapter-owned vocabulary. */
|
|
517
|
+
function knownField(type: string, field: string): string {
|
|
518
|
+
return field === 'position' ||
|
|
519
|
+
field === 'data' ||
|
|
520
|
+
field === 'type' ||
|
|
521
|
+
RAW_MARKUP_DATA_KEYS.includes(field) ||
|
|
522
|
+
(FIELDS_BY_TYPE[type]?.includes(field) ?? false)
|
|
523
|
+
? field
|
|
524
|
+
: 'unknown';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function isPlainObject(value: unknown): value is UnknownRecord {
|
|
528
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
const prototype = Object.getPrototypeOf(value);
|
|
532
|
+
return prototype === Object.prototype || prototype === null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const POINT_FIELDS: ReadonlyArray<string> = ['line', 'column', 'offset'];
|
|
536
|
+
const POSITION_FIELDS: ReadonlyArray<string> = ['start', 'end'];
|
|
537
|
+
|
|
538
|
+
function samePoint(left: unknown, right: unknown): boolean {
|
|
539
|
+
if (!isPlainObject(left) || !isPlainObject(right)) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
for (const point of [left, right]) {
|
|
543
|
+
for (const field of Object.keys(point)) {
|
|
544
|
+
if (!POINT_FIELDS.includes(field)) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return POINT_FIELDS.every(field => left[field] === right[field]);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Exact provenance equality: every line, column, and offset on both points.
|
|
554
|
+
* Comparing offsets alone would let a shifted line or column through, and the
|
|
555
|
+
* adapter must never quietly restore Core's position over a changed one.
|
|
556
|
+
*/
|
|
557
|
+
function samePosition(left: unknown, right: unknown): boolean {
|
|
558
|
+
if (left === undefined || right === undefined) {
|
|
559
|
+
return left === right;
|
|
560
|
+
}
|
|
561
|
+
if (!isPlainObject(left) || !isPlainObject(right)) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
for (const position of [left, right]) {
|
|
565
|
+
for (const field of Object.keys(position)) {
|
|
566
|
+
if (!POSITION_FIELDS.includes(field)) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return samePoint(left.start, right.start) && samePoint(left.end, right.end);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function copyPoint(point: unknown): MarkdownRemarkPoint | undefined {
|
|
575
|
+
if (!isPlainObject(point)) {
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
const copy: MarkdownRemarkPoint = {};
|
|
579
|
+
if (typeof point.line === 'number') {
|
|
580
|
+
copy.line = point.line;
|
|
581
|
+
}
|
|
582
|
+
if (typeof point.column === 'number') {
|
|
583
|
+
copy.column = point.column;
|
|
584
|
+
}
|
|
585
|
+
if (typeof point.offset === 'number') {
|
|
586
|
+
copy.offset = point.offset;
|
|
587
|
+
}
|
|
588
|
+
return copy;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function copyPosition(position: unknown): MarkdownRemarkPosition | undefined {
|
|
592
|
+
if (!isPlainObject(position)) {
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|
|
595
|
+
const start = copyPoint(position.start);
|
|
596
|
+
const end = copyPoint(position.end);
|
|
597
|
+
return start == null || end == null ? undefined : {start, end};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function copyData<Value>(value: Value): Value {
|
|
601
|
+
if (Array.isArray(value)) {
|
|
602
|
+
const copy: unknown[] = [];
|
|
603
|
+
for (const entry of value) {
|
|
604
|
+
copy.push(copyData(entry));
|
|
605
|
+
}
|
|
606
|
+
return copy as Value;
|
|
607
|
+
}
|
|
608
|
+
if (isPlainObject(value)) {
|
|
609
|
+
const copy: UnknownRecord = {};
|
|
610
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
611
|
+
copy[key] = copyData(nested);
|
|
612
|
+
}
|
|
613
|
+
return copy as Value;
|
|
614
|
+
}
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function sameJsonValue(left: unknown, right: unknown): boolean {
|
|
619
|
+
if (left === right) {
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
623
|
+
return (
|
|
624
|
+
left.length === right.length &&
|
|
625
|
+
left.every((item, index) => sameJsonValue(item, right[index]))
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
629
|
+
const leftKeys = Object.keys(left);
|
|
630
|
+
const rightKeys = Object.keys(right);
|
|
631
|
+
return (
|
|
632
|
+
leftKeys.length === rightKeys.length &&
|
|
633
|
+
leftKeys.every(
|
|
634
|
+
key => key in right && sameJsonValue(left[key], right[key]),
|
|
635
|
+
)
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/* ------------------------------------------------------------------ *
|
|
642
|
+
* Canonical tree → fresh mutable Remark tree
|
|
643
|
+
* ------------------------------------------------------------------ */
|
|
644
|
+
|
|
645
|
+
interface OriginIndex {
|
|
646
|
+
readonly all: Set<UnknownRecord>;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function createOriginIndex(): OriginIndex {
|
|
650
|
+
return {all: new Set()};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function indexOrigin(node: UnknownRecord, index: OriginIndex): void {
|
|
654
|
+
index.all.add(node);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function toRemarkChildren(
|
|
658
|
+
children: unknown,
|
|
659
|
+
index: OriginIndex,
|
|
660
|
+
): UnknownRecord[] {
|
|
661
|
+
return Array.isArray(children)
|
|
662
|
+
? children.map(child => toRemarkNode(child as UnknownRecord, index))
|
|
663
|
+
: [];
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function toRemarkNode(node: UnknownRecord, index: OriginIndex): UnknownRecord {
|
|
667
|
+
indexOrigin(node, index);
|
|
668
|
+
const copy: UnknownRecord = {type: node.type};
|
|
669
|
+
switch (node.type) {
|
|
670
|
+
case 'root':
|
|
671
|
+
case 'paragraph':
|
|
672
|
+
case 'blockquote':
|
|
673
|
+
case 'strong':
|
|
674
|
+
case 'emphasis':
|
|
675
|
+
case 'delete':
|
|
676
|
+
case 'tableRow':
|
|
677
|
+
case 'tableCell':
|
|
678
|
+
case 'listItem':
|
|
679
|
+
if (node.type === 'listItem' && typeof node.checked === 'boolean') {
|
|
680
|
+
copy.checked = node.checked;
|
|
681
|
+
}
|
|
682
|
+
copy.children = toRemarkChildren(node.children, index);
|
|
683
|
+
break;
|
|
684
|
+
case 'heading':
|
|
685
|
+
copy.depth = node.depth;
|
|
686
|
+
copy.children = toRemarkChildren(node.children, index);
|
|
687
|
+
break;
|
|
688
|
+
case 'text':
|
|
689
|
+
case 'inlineCode':
|
|
690
|
+
case 'inlineMath':
|
|
691
|
+
case 'math':
|
|
692
|
+
copy.value = node.value;
|
|
693
|
+
break;
|
|
694
|
+
case 'code':
|
|
695
|
+
copy.lang = node.lang ?? null;
|
|
696
|
+
// Astryx stores an absent info string as no key; MDAST spells it
|
|
697
|
+
// `null`. Both conversions translate, so authored fence metadata —
|
|
698
|
+
// which the semantic-fence helper reads — survives the round trip.
|
|
699
|
+
copy.meta = node.meta ?? null;
|
|
700
|
+
copy.value = node.value;
|
|
701
|
+
break;
|
|
702
|
+
case 'link':
|
|
703
|
+
copy.url = node.url;
|
|
704
|
+
copy.title = null;
|
|
705
|
+
copy.children = toRemarkChildren(node.children, index);
|
|
706
|
+
break;
|
|
707
|
+
case 'image':
|
|
708
|
+
copy.url = node.url;
|
|
709
|
+
copy.title = null;
|
|
710
|
+
copy.alt = node.alt;
|
|
711
|
+
break;
|
|
712
|
+
case 'list':
|
|
713
|
+
copy.ordered = node.ordered;
|
|
714
|
+
if (node.start !== undefined) {
|
|
715
|
+
copy.start = node.start;
|
|
716
|
+
}
|
|
717
|
+
if (node.spread !== undefined) {
|
|
718
|
+
copy.spread = node.spread;
|
|
719
|
+
}
|
|
720
|
+
if (node.delimiter !== undefined) {
|
|
721
|
+
copy.delimiter = node.delimiter;
|
|
722
|
+
}
|
|
723
|
+
copy.children = toRemarkChildren(node.children, index);
|
|
724
|
+
break;
|
|
725
|
+
case 'table':
|
|
726
|
+
copy.align = Array.isArray(node.align) ? [...node.align] : [];
|
|
727
|
+
copy.children = toRemarkChildren(node.children, index);
|
|
728
|
+
break;
|
|
729
|
+
case 'citation':
|
|
730
|
+
copy.sourceId = node.sourceId;
|
|
731
|
+
break;
|
|
732
|
+
case 'extension':
|
|
733
|
+
copy.plugin = node.plugin;
|
|
734
|
+
copy.name = node.name;
|
|
735
|
+
copy.display = node.display;
|
|
736
|
+
if (node.source !== undefined) {
|
|
737
|
+
copy.source = node.source;
|
|
738
|
+
}
|
|
739
|
+
break;
|
|
740
|
+
default:
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
if (node.data !== undefined) {
|
|
744
|
+
copy.data = copyData(node.data);
|
|
745
|
+
}
|
|
746
|
+
const position = copyPosition(node.position);
|
|
747
|
+
if (position != null) {
|
|
748
|
+
copy.position = position;
|
|
749
|
+
}
|
|
750
|
+
// Enumerable so an immutable-style plugin that spreads a node keeps its
|
|
751
|
+
// provenance; symbol-keyed so it never reaches JSON, data, or rendering.
|
|
752
|
+
Object.defineProperty(copy, REMARK_ORIGIN, {
|
|
753
|
+
configurable: false,
|
|
754
|
+
enumerable: true,
|
|
755
|
+
value: node,
|
|
756
|
+
writable: false,
|
|
757
|
+
});
|
|
758
|
+
return copy;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* ------------------------------------------------------------------ *
|
|
762
|
+
* Mutated Remark tree → validated canonical tree
|
|
763
|
+
* ------------------------------------------------------------------ */
|
|
764
|
+
|
|
765
|
+
interface BackContext {
|
|
766
|
+
readonly index: OriginIndex;
|
|
767
|
+
readonly used: Set<UnknownRecord>;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Resolves the canonical node a transformed node came from.
|
|
772
|
+
*
|
|
773
|
+
* Provenance travels on an enumerable symbol the adapter puts on every copy,
|
|
774
|
+
* so a plugin that spreads a node (`{...node}`) keeps it. Nothing else counts:
|
|
775
|
+
* a node that merely looks like a source node — same position, same
|
|
776
|
+
* discriminants, rebuilt from scratch — is a forgery, not provenance, and is
|
|
777
|
+
* rejected rather than matched.
|
|
778
|
+
*/
|
|
779
|
+
function resolveOrigin(
|
|
780
|
+
node: UnknownRecord,
|
|
781
|
+
type: string,
|
|
782
|
+
context: BackContext,
|
|
783
|
+
): UnknownRecord | null {
|
|
784
|
+
const linked = (node as {[REMARK_ORIGIN]?: unknown})[REMARK_ORIGIN];
|
|
785
|
+
if (linked === undefined) {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
if (!isPlainObject(linked) || !context.index.all.has(linked)) {
|
|
789
|
+
reject(`a "${knownType(type)}" node carries forged provenance`);
|
|
790
|
+
}
|
|
791
|
+
if (linked.type !== type) {
|
|
792
|
+
reject(`a source "${knownType(linked.type)}" node cannot be retyped`);
|
|
793
|
+
}
|
|
794
|
+
if (context.used.has(linked)) {
|
|
795
|
+
reject(`a source "${knownType(type)}" node cannot be duplicated`);
|
|
796
|
+
}
|
|
797
|
+
context.used.add(linked);
|
|
798
|
+
return linked;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function assertKnownFields(type: string, node: UnknownRecord): void {
|
|
802
|
+
const allowed = FIELDS_BY_TYPE[type];
|
|
803
|
+
for (const key of Object.keys(node)) {
|
|
804
|
+
if (
|
|
805
|
+
key !== 'type' &&
|
|
806
|
+
key !== 'position' &&
|
|
807
|
+
key !== 'data' &&
|
|
808
|
+
!allowed.includes(key)
|
|
809
|
+
) {
|
|
810
|
+
reject(
|
|
811
|
+
`a "${knownType(type)}" node cannot carry the unsupported field "${knownField(type, key)}"`,
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function convertData(
|
|
818
|
+
type: string,
|
|
819
|
+
value: unknown,
|
|
820
|
+
): MarkdownAstDataValue | undefined {
|
|
821
|
+
if (value === undefined) {
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
if (isPlainObject(value)) {
|
|
825
|
+
for (const key of RAW_MARKUP_DATA_KEYS) {
|
|
826
|
+
if (key in value) {
|
|
827
|
+
reject(
|
|
828
|
+
`a "${knownType(type)}" node cannot carry the raw-markup data channel "${key}"`,
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (!isMarkdownPluginData(value)) {
|
|
834
|
+
reject(
|
|
835
|
+
`a "${knownType(type)}" node's data must be finite JSON-like values`,
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
return copyData(value);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function requireString(type: string, field: string, value: unknown): string {
|
|
842
|
+
if (typeof value !== 'string') {
|
|
843
|
+
reject(
|
|
844
|
+
`a "${knownType(type)}" node requires a string "${knownField(type, field)}"`,
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
return value;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function requireAbsentText(type: string, field: string, value: unknown): void {
|
|
851
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
852
|
+
reject(
|
|
853
|
+
`a "${knownType(type)}" node's "${knownField(type, field)}" has no Astryx representation`,
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function assertPlacement(
|
|
859
|
+
type: string,
|
|
860
|
+
node: UnknownRecord,
|
|
861
|
+
parentType: string | null,
|
|
862
|
+
insideLink: boolean,
|
|
863
|
+
): void {
|
|
864
|
+
if (parentType == null) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (type === 'root') {
|
|
868
|
+
reject('a document can only contain one root');
|
|
869
|
+
}
|
|
870
|
+
const display = type === 'extension' ? node.display : undefined;
|
|
871
|
+
const phrasing = PHRASING_TYPES.has(type) || display === 'inline';
|
|
872
|
+
const block = BLOCK_TYPES.has(type) || display === 'block';
|
|
873
|
+
if (
|
|
874
|
+
(PHRASING_PARENTS.has(parentType) && !phrasing) ||
|
|
875
|
+
(BLOCK_PARENTS.has(parentType) && !block) ||
|
|
876
|
+
(parentType === 'list' && type !== 'listItem') ||
|
|
877
|
+
(parentType === 'table' && type !== 'tableRow') ||
|
|
878
|
+
(parentType === 'tableRow' && type !== 'tableCell')
|
|
879
|
+
) {
|
|
880
|
+
reject(
|
|
881
|
+
`a "${knownType(type)}" node cannot be a child of a "${knownType(parentType)}" node`,
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
if (insideLink && type === 'link') {
|
|
885
|
+
reject('a link cannot be nested inside another link');
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function sameCanonicalFields(
|
|
890
|
+
origin: UnknownRecord,
|
|
891
|
+
fields: UnknownRecord,
|
|
892
|
+
): boolean {
|
|
893
|
+
// Canonical nodes may carry explicitly undefined optional keys; an absent
|
|
894
|
+
// key and an undefined key mean the same thing.
|
|
895
|
+
const originKeys = Object.keys(origin).filter(
|
|
896
|
+
key => key !== 'type' && origin[key] !== undefined,
|
|
897
|
+
);
|
|
898
|
+
const fieldKeys = Object.keys(fields).filter(
|
|
899
|
+
key => fields[key] !== undefined,
|
|
900
|
+
);
|
|
901
|
+
if (originKeys.length !== fieldKeys.length) {
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
for (const key of fieldKeys) {
|
|
905
|
+
const next = fields[key];
|
|
906
|
+
const previous = origin[key];
|
|
907
|
+
if (next === previous) {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (Array.isArray(next) && Array.isArray(previous)) {
|
|
911
|
+
if (
|
|
912
|
+
next.length !== previous.length ||
|
|
913
|
+
next.some((item, position) => item !== previous[position])
|
|
914
|
+
) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (!sameJsonValue(next, previous)) {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
return true;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function buildCanonicalNode(
|
|
927
|
+
type: string,
|
|
928
|
+
fields: UnknownRecord,
|
|
929
|
+
origin: UnknownRecord | null,
|
|
930
|
+
): UnknownRecord {
|
|
931
|
+
if (origin != null && sameCanonicalFields(origin, fields)) {
|
|
932
|
+
return origin;
|
|
933
|
+
}
|
|
934
|
+
const built: UnknownRecord = {type, ...fields};
|
|
935
|
+
if (origin != null) {
|
|
936
|
+
// Carries Core's own enumerable symbol markers (source-heading identity)
|
|
937
|
+
// onto the replacement so Core still recognizes the node it authored.
|
|
938
|
+
for (const marker of Object.getOwnPropertySymbols(origin)) {
|
|
939
|
+
if (Object.prototype.propertyIsEnumerable.call(origin, marker)) {
|
|
940
|
+
Object.defineProperty(built, marker, {
|
|
941
|
+
configurable: false,
|
|
942
|
+
enumerable: true,
|
|
943
|
+
value: (origin as UnknownRecord & {[key: symbol]: unknown})[marker],
|
|
944
|
+
writable: false,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return built;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function fromRemarkChildren(
|
|
953
|
+
children: unknown,
|
|
954
|
+
type: string,
|
|
955
|
+
insideLink: boolean,
|
|
956
|
+
context: BackContext,
|
|
957
|
+
): UnknownRecord[] {
|
|
958
|
+
if (!Array.isArray(children)) {
|
|
959
|
+
reject(`a "${knownType(type)}" node requires a children array`);
|
|
960
|
+
}
|
|
961
|
+
return children.map(child =>
|
|
962
|
+
fromRemarkNode(child, type, insideLink || type === 'link', context),
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function fromRemarkNode(
|
|
967
|
+
value: unknown,
|
|
968
|
+
parentType: string | null,
|
|
969
|
+
insideLink: boolean,
|
|
970
|
+
context: BackContext,
|
|
971
|
+
): UnknownRecord {
|
|
972
|
+
if (!isPlainObject(value)) {
|
|
973
|
+
reject('every transformed node must be a plain object');
|
|
974
|
+
}
|
|
975
|
+
const type = value.type;
|
|
976
|
+
if (typeof type !== 'string') {
|
|
977
|
+
reject('every transformed node requires a string type');
|
|
978
|
+
}
|
|
979
|
+
if (type === 'html') {
|
|
980
|
+
reject('raw HTML has no Astryx representation');
|
|
981
|
+
}
|
|
982
|
+
if (!(type in FIELDS_BY_TYPE)) {
|
|
983
|
+
reject(
|
|
984
|
+
`the node type "${knownType(type)}" is outside the supported MDAST subset`,
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
assertKnownFields(type, value);
|
|
988
|
+
assertPlacement(type, value, parentType, insideLink);
|
|
989
|
+
|
|
990
|
+
const origin = resolveOrigin(value, type, context);
|
|
991
|
+
if (origin == null) {
|
|
992
|
+
if (value.position !== undefined) {
|
|
993
|
+
reject(`a new "${knownType(type)}" node cannot carry a source position`);
|
|
994
|
+
}
|
|
995
|
+
} else if (!samePosition(value.position, origin.position)) {
|
|
996
|
+
// Covers a removed, rebuilt, offset-shifted, and line/column-shifted
|
|
997
|
+
// position alike. Core's position is never restored over a changed one.
|
|
998
|
+
reject(
|
|
999
|
+
`a source "${knownType(type)}" node's position must stay exactly as Core authored it`,
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const fields: UnknownRecord = {};
|
|
1004
|
+
switch (type) {
|
|
1005
|
+
case 'root':
|
|
1006
|
+
case 'paragraph':
|
|
1007
|
+
case 'blockquote':
|
|
1008
|
+
case 'strong':
|
|
1009
|
+
case 'emphasis':
|
|
1010
|
+
case 'delete':
|
|
1011
|
+
case 'tableRow':
|
|
1012
|
+
case 'tableCell':
|
|
1013
|
+
fields.children = fromRemarkChildren(
|
|
1014
|
+
value.children,
|
|
1015
|
+
type,
|
|
1016
|
+
insideLink,
|
|
1017
|
+
context,
|
|
1018
|
+
);
|
|
1019
|
+
break;
|
|
1020
|
+
case 'heading': {
|
|
1021
|
+
const depth = value.depth;
|
|
1022
|
+
if (
|
|
1023
|
+
typeof depth !== 'number' ||
|
|
1024
|
+
!Number.isInteger(depth) ||
|
|
1025
|
+
depth < 1 ||
|
|
1026
|
+
depth > 6
|
|
1027
|
+
) {
|
|
1028
|
+
reject('a heading requires a depth between 1 and 6');
|
|
1029
|
+
}
|
|
1030
|
+
if (origin != null && origin.depth !== depth) {
|
|
1031
|
+
reject("a source heading's depth cannot change");
|
|
1032
|
+
}
|
|
1033
|
+
fields.depth = depth;
|
|
1034
|
+
fields.children = fromRemarkChildren(
|
|
1035
|
+
value.children,
|
|
1036
|
+
type,
|
|
1037
|
+
insideLink,
|
|
1038
|
+
context,
|
|
1039
|
+
);
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
case 'listItem': {
|
|
1043
|
+
const checked = value.checked;
|
|
1044
|
+
if (checked !== undefined && checked !== null) {
|
|
1045
|
+
if (typeof checked !== 'boolean') {
|
|
1046
|
+
reject('a list item requires a boolean "checked" value');
|
|
1047
|
+
}
|
|
1048
|
+
fields.checked = checked;
|
|
1049
|
+
}
|
|
1050
|
+
if (value.spread === true) {
|
|
1051
|
+
reject('a loose list item has no Astryx representation');
|
|
1052
|
+
}
|
|
1053
|
+
fields.children = fromRemarkChildren(
|
|
1054
|
+
value.children,
|
|
1055
|
+
type,
|
|
1056
|
+
insideLink,
|
|
1057
|
+
context,
|
|
1058
|
+
);
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
case 'text':
|
|
1062
|
+
case 'inlineCode':
|
|
1063
|
+
case 'inlineMath':
|
|
1064
|
+
case 'math':
|
|
1065
|
+
fields.value = requireString(type, 'value', value.value);
|
|
1066
|
+
break;
|
|
1067
|
+
case 'code': {
|
|
1068
|
+
const lang = value.lang;
|
|
1069
|
+
if (lang !== undefined && lang !== null && typeof lang !== 'string') {
|
|
1070
|
+
reject('a code node requires a string or null "lang"');
|
|
1071
|
+
}
|
|
1072
|
+
const meta = value.meta;
|
|
1073
|
+
if (meta !== undefined && meta !== null && typeof meta !== 'string') {
|
|
1074
|
+
reject('a code node requires a string or null "meta"');
|
|
1075
|
+
}
|
|
1076
|
+
fields.lang = lang ?? null;
|
|
1077
|
+
if (meta != null && meta !== '') {
|
|
1078
|
+
fields.meta = meta;
|
|
1079
|
+
}
|
|
1080
|
+
fields.value = requireString(type, 'value', value.value);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
case 'link': {
|
|
1084
|
+
const url = requireString(type, 'url', value.url);
|
|
1085
|
+
if (!isSafeMarkdownParserUrl(url)) {
|
|
1086
|
+
reject('a link destination was rejected by the navigation owner');
|
|
1087
|
+
}
|
|
1088
|
+
requireAbsentText(type, 'title', value.title);
|
|
1089
|
+
fields.url = url;
|
|
1090
|
+
fields.children = fromRemarkChildren(
|
|
1091
|
+
value.children,
|
|
1092
|
+
type,
|
|
1093
|
+
insideLink,
|
|
1094
|
+
context,
|
|
1095
|
+
);
|
|
1096
|
+
break;
|
|
1097
|
+
}
|
|
1098
|
+
case 'image': {
|
|
1099
|
+
const url = requireString(type, 'url', value.url);
|
|
1100
|
+
if (!isSafeMarkdownParserUrl(url)) {
|
|
1101
|
+
reject('an image source was rejected by the resource owner');
|
|
1102
|
+
}
|
|
1103
|
+
requireAbsentText(type, 'title', value.title);
|
|
1104
|
+
if (typeof value.alt !== 'string') {
|
|
1105
|
+
reject('an image requires its text alternative');
|
|
1106
|
+
}
|
|
1107
|
+
fields.url = url;
|
|
1108
|
+
fields.alt = value.alt;
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
1111
|
+
case 'list': {
|
|
1112
|
+
if (typeof value.ordered !== 'boolean') {
|
|
1113
|
+
reject('a list requires a boolean "ordered" value');
|
|
1114
|
+
}
|
|
1115
|
+
fields.ordered = value.ordered;
|
|
1116
|
+
const start = value.start;
|
|
1117
|
+
if (start !== undefined && start !== null) {
|
|
1118
|
+
if (typeof start !== 'number' || !Number.isFinite(start)) {
|
|
1119
|
+
reject('a list requires a finite "start" value');
|
|
1120
|
+
}
|
|
1121
|
+
fields.start = start;
|
|
1122
|
+
}
|
|
1123
|
+
if (typeof value.spread === 'boolean') {
|
|
1124
|
+
fields.spread = value.spread;
|
|
1125
|
+
}
|
|
1126
|
+
const delimiter = value.delimiter;
|
|
1127
|
+
if (delimiter !== undefined) {
|
|
1128
|
+
if (delimiter !== '.' && delimiter !== ')') {
|
|
1129
|
+
reject('a list requires a "." or ")" delimiter');
|
|
1130
|
+
}
|
|
1131
|
+
fields.delimiter = delimiter;
|
|
1132
|
+
}
|
|
1133
|
+
fields.children = fromRemarkChildren(
|
|
1134
|
+
value.children,
|
|
1135
|
+
type,
|
|
1136
|
+
insideLink,
|
|
1137
|
+
context,
|
|
1138
|
+
);
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
case 'table': {
|
|
1142
|
+
const align = value.align;
|
|
1143
|
+
if (
|
|
1144
|
+
!Array.isArray(align) ||
|
|
1145
|
+
!align.every(
|
|
1146
|
+
entry =>
|
|
1147
|
+
entry === null ||
|
|
1148
|
+
entry === 'left' ||
|
|
1149
|
+
entry === 'center' ||
|
|
1150
|
+
entry === 'right',
|
|
1151
|
+
)
|
|
1152
|
+
) {
|
|
1153
|
+
reject('a table requires normalized alignment values');
|
|
1154
|
+
}
|
|
1155
|
+
fields.align = [...align];
|
|
1156
|
+
fields.children = fromRemarkChildren(
|
|
1157
|
+
value.children,
|
|
1158
|
+
type,
|
|
1159
|
+
insideLink,
|
|
1160
|
+
context,
|
|
1161
|
+
);
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
case 'thematicBreak':
|
|
1165
|
+
case 'break':
|
|
1166
|
+
break;
|
|
1167
|
+
case 'citation':
|
|
1168
|
+
case 'extension': {
|
|
1169
|
+
// Astryx owns these outright: the adapter can show one to a plugin but
|
|
1170
|
+
// never lets one be authored or edited, so every field must come back
|
|
1171
|
+
// exactly as it went out.
|
|
1172
|
+
const unchanged =
|
|
1173
|
+
origin != null &&
|
|
1174
|
+
sameJsonValue(value.data, origin.data) &&
|
|
1175
|
+
(type === 'citation'
|
|
1176
|
+
? value.sourceId === origin.sourceId
|
|
1177
|
+
: value.plugin === origin.plugin &&
|
|
1178
|
+
value.name === origin.name &&
|
|
1179
|
+
value.display === origin.display &&
|
|
1180
|
+
value.source === origin.source);
|
|
1181
|
+
if (!unchanged) {
|
|
1182
|
+
reject(
|
|
1183
|
+
`an Astryx "${knownType(type)}" node cannot be authored or changed here`,
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
// Equality across every owned field proves the node round-tripped, so
|
|
1187
|
+
// the canonical node itself stays in place.
|
|
1188
|
+
return origin;
|
|
1189
|
+
}
|
|
1190
|
+
default:
|
|
1191
|
+
reject(
|
|
1192
|
+
`the node type "${knownType(type)}" is outside the supported MDAST subset`,
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const data = convertData(type, value.data);
|
|
1197
|
+
if (data !== undefined) {
|
|
1198
|
+
fields.data = data;
|
|
1199
|
+
}
|
|
1200
|
+
if (origin != null && origin.position !== undefined) {
|
|
1201
|
+
fields.position = origin.position;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const node = buildCanonicalNode(type, fields, origin);
|
|
1205
|
+
if (type === 'table' && node !== origin) {
|
|
1206
|
+
const rows = fields.children as UnknownRecord[];
|
|
1207
|
+
const width = (rows[0]?.children as unknown[] | undefined)?.length ?? 0;
|
|
1208
|
+
if (
|
|
1209
|
+
rows.some(
|
|
1210
|
+
row => (row.children as unknown[] | undefined)?.length !== width,
|
|
1211
|
+
)
|
|
1212
|
+
) {
|
|
1213
|
+
reject('a transformed table cannot have ragged rows');
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return node;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/* ------------------------------------------------------------------ *
|
|
1220
|
+
* Constrained file and processor guard
|
|
1221
|
+
* ------------------------------------------------------------------ */
|
|
1222
|
+
|
|
1223
|
+
function createMessage(
|
|
1224
|
+
reason: string | Error,
|
|
1225
|
+
place: unknown,
|
|
1226
|
+
ruleId: string | undefined,
|
|
1227
|
+
): MarkdownRemarkMessage {
|
|
1228
|
+
const text = reason instanceof Error ? reason.message : reason;
|
|
1229
|
+
return {
|
|
1230
|
+
reason: text,
|
|
1231
|
+
fatal: false,
|
|
1232
|
+
place: place ?? null,
|
|
1233
|
+
ruleId: ruleId ?? null,
|
|
1234
|
+
toString() {
|
|
1235
|
+
return text;
|
|
1236
|
+
},
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* The documented surface of the constrained file. Everything else — `path`,
|
|
1242
|
+
* `cwd`, `history`, `basename`, `dirname`, `extname`, `stem`, `stored`, and
|
|
1243
|
+
* any other key — is unsupported: the adapter has no filesystem, no processor,
|
|
1244
|
+
* and no run history to answer with.
|
|
1245
|
+
*/
|
|
1246
|
+
const FILE_SURFACE: ReadonlySet<string> = new Set([
|
|
1247
|
+
'value',
|
|
1248
|
+
'data',
|
|
1249
|
+
'messages',
|
|
1250
|
+
'message',
|
|
1251
|
+
'fail',
|
|
1252
|
+
'toString',
|
|
1253
|
+
]);
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Adapter-owned data for one invocation.
|
|
1257
|
+
*
|
|
1258
|
+
* It lives here rather than on the file the plugin holds, because everything
|
|
1259
|
+
* the plugin can reach it can also rewrite — a plugin must not be able to
|
|
1260
|
+
* fake a `fatal` message or hide one it caused. The failure verdict itself
|
|
1261
|
+
* belongs to the run (`RunState`), which every rejection records.
|
|
1262
|
+
*/
|
|
1263
|
+
interface FileState {
|
|
1264
|
+
readonly data: UnknownRecord;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function createConstrainedFile(source: string): {
|
|
1268
|
+
readonly file: MarkdownRemarkFile;
|
|
1269
|
+
readonly state: FileState;
|
|
1270
|
+
} {
|
|
1271
|
+
const messages: MarkdownRemarkMessage[] = [];
|
|
1272
|
+
const state: FileState = {data: {}};
|
|
1273
|
+
const target: MarkdownRemarkFile = {
|
|
1274
|
+
get value() {
|
|
1275
|
+
return source;
|
|
1276
|
+
},
|
|
1277
|
+
get data() {
|
|
1278
|
+
return state.data;
|
|
1279
|
+
},
|
|
1280
|
+
get messages() {
|
|
1281
|
+
return messages;
|
|
1282
|
+
},
|
|
1283
|
+
message(reason, place, ruleId) {
|
|
1284
|
+
const message = createMessage(reason, place, ruleId);
|
|
1285
|
+
messages.push(message);
|
|
1286
|
+
return message;
|
|
1287
|
+
},
|
|
1288
|
+
fail(reason, place, ruleId) {
|
|
1289
|
+
const message = createMessage(reason, place, ruleId);
|
|
1290
|
+
message.fatal = true;
|
|
1291
|
+
messages.push(message);
|
|
1292
|
+
// `reject` records the verdict in run state, so a plugin that catches
|
|
1293
|
+
// this and carries on has still declared the document unsupported and
|
|
1294
|
+
// the run cannot succeed behind its back.
|
|
1295
|
+
reject(FAILED_BY_PLUGIN);
|
|
1296
|
+
},
|
|
1297
|
+
toString() {
|
|
1298
|
+
return source;
|
|
1299
|
+
},
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
// Fails closed on every unsupported read and on every write. Returning
|
|
1303
|
+
// `undefined` for `file.cwd` would let a plugin silently take a path that
|
|
1304
|
+
// depends on a capability this profile does not have.
|
|
1305
|
+
/* eslint-disable @typescript-eslint/promise-function-async -- these are
|
|
1306
|
+
Proxy traps, not async work; the adapter is synchronous throughout. */
|
|
1307
|
+
const file = new Proxy(target, {
|
|
1308
|
+
get(_target, property, receiver) {
|
|
1309
|
+
if (typeof property === 'symbol') {
|
|
1310
|
+
// JS protocol (inspection, iteration, `instanceof`), not VFile
|
|
1311
|
+
// surface. Answering `undefined` here cannot mislead a plugin.
|
|
1312
|
+
return Reflect.get(target, property, receiver) as unknown;
|
|
1313
|
+
}
|
|
1314
|
+
if (!FILE_SURFACE.has(property)) {
|
|
1315
|
+
reject(`the file has no "${knownFileKey(property)}" in this profile`);
|
|
1316
|
+
}
|
|
1317
|
+
return Reflect.get(target, property, receiver) as unknown;
|
|
1318
|
+
},
|
|
1319
|
+
set(_target, property) {
|
|
1320
|
+
reject(
|
|
1321
|
+
`the file's "${knownFileKey(property)}" cannot be assigned in this profile`,
|
|
1322
|
+
);
|
|
1323
|
+
},
|
|
1324
|
+
defineProperty(_target, property) {
|
|
1325
|
+
reject(
|
|
1326
|
+
`the file's "${knownFileKey(property)}" cannot be assigned in this profile`,
|
|
1327
|
+
);
|
|
1328
|
+
},
|
|
1329
|
+
deleteProperty(_target, property) {
|
|
1330
|
+
reject(
|
|
1331
|
+
`the file's "${knownFileKey(property)}" cannot be assigned in this profile`,
|
|
1332
|
+
);
|
|
1333
|
+
},
|
|
1334
|
+
// Truthfully answers "no such capability" so a plugin that feature-detects
|
|
1335
|
+
// can take its own graceful path instead of being rejected.
|
|
1336
|
+
has(_target, property) {
|
|
1337
|
+
return typeof property === 'symbol' || FILE_SURFACE.has(property);
|
|
1338
|
+
},
|
|
1339
|
+
ownKeys() {
|
|
1340
|
+
return [...FILE_SURFACE];
|
|
1341
|
+
},
|
|
1342
|
+
getOwnPropertyDescriptor(_target, property) {
|
|
1343
|
+
return typeof property !== 'symbol' && !FILE_SURFACE.has(property)
|
|
1344
|
+
? undefined
|
|
1345
|
+
: Reflect.getOwnPropertyDescriptor(target, property);
|
|
1346
|
+
},
|
|
1347
|
+
});
|
|
1348
|
+
/* eslint-enable @typescript-eslint/promise-function-async */
|
|
1349
|
+
|
|
1350
|
+
return {file, state};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
/**
|
|
1354
|
+
* Stands in for Unified's processor. Any access fails closed, so parser,
|
|
1355
|
+
* compiler, and cross-run `data()` registration cannot be mistaken for
|
|
1356
|
+
* supported behavior.
|
|
1357
|
+
*/
|
|
1358
|
+
const PROCESSOR_ACCESS = 'processor registration is outside the adapter';
|
|
1359
|
+
|
|
1360
|
+
const processorGuard: unknown = new Proxy(Object.freeze({}), {
|
|
1361
|
+
get() {
|
|
1362
|
+
reject(PROCESSOR_ACCESS);
|
|
1363
|
+
},
|
|
1364
|
+
set() {
|
|
1365
|
+
reject(PROCESSOR_ACCESS);
|
|
1366
|
+
},
|
|
1367
|
+
defineProperty() {
|
|
1368
|
+
reject(PROCESSOR_ACCESS);
|
|
1369
|
+
},
|
|
1370
|
+
deleteProperty() {
|
|
1371
|
+
reject(PROCESSOR_ACCESS);
|
|
1372
|
+
},
|
|
1373
|
+
apply() {
|
|
1374
|
+
reject(PROCESSOR_ACCESS);
|
|
1375
|
+
},
|
|
1376
|
+
has() {
|
|
1377
|
+
// Truthful: this stand-in offers no processor capability at all.
|
|
1378
|
+
return false;
|
|
1379
|
+
},
|
|
1380
|
+
ownKeys() {
|
|
1381
|
+
return [];
|
|
1382
|
+
},
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
/* ------------------------------------------------------------------ *
|
|
1386
|
+
* Public adapter
|
|
1387
|
+
* ------------------------------------------------------------------ */
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* The one reason reported for a failed run.
|
|
1391
|
+
*
|
|
1392
|
+
* A rejection's reason is built from this module's own vocabulary, so it is
|
|
1393
|
+
* safe to report. Anything a plugin authored — a thrown error's message, a
|
|
1394
|
+
* `file.fail()` reason, a `file.message()` diagnostic — is not: the plugin
|
|
1395
|
+
* read the document, so its text may quote it. Those stay on the file, which
|
|
1396
|
+
* only the plugin sees.
|
|
1397
|
+
*/
|
|
1398
|
+
function describeFailure(error: unknown): string {
|
|
1399
|
+
const reason =
|
|
1400
|
+
error instanceof RemarkAdapterRejection
|
|
1401
|
+
? adapterReasons.get(error)
|
|
1402
|
+
: undefined;
|
|
1403
|
+
return reason ?? 'the plugin threw an error';
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Adapt one synchronous transform-only Remark plugin to a Markdown transform.
|
|
1408
|
+
*
|
|
1409
|
+
* The adapted plugin receives a fresh mutable copy of the canonical document
|
|
1410
|
+
* and an isolated constrained file. Every returned or mutated tree round-trips
|
|
1411
|
+
* through the supported MDAST subset before it becomes observable; anything
|
|
1412
|
+
* unsupported — a parser or compiler plugin, asynchronous work, processor
|
|
1413
|
+
* state, raw HTML, an unsupported node, a forged position, or unrepresentable
|
|
1414
|
+
* metadata — keeps the last valid document and reports one diagnostic.
|
|
1415
|
+
*
|
|
1416
|
+
* Compatibility is per plugin and proven by fixtures, never inferred from a
|
|
1417
|
+
* package name.
|
|
1418
|
+
*
|
|
1419
|
+
* @example
|
|
1420
|
+
* ```
|
|
1421
|
+
* import {createMarkdownPlugin} from '@astryxdesign/core/Markdown';
|
|
1422
|
+
* import {createMarkdownRemarkTransform} from '@astryxdesign/core/Markdown/remark';
|
|
1423
|
+
*
|
|
1424
|
+
* const shouting = createMarkdownPlugin({
|
|
1425
|
+
* name: 'shouting-headings',
|
|
1426
|
+
* apiVersion: 1,
|
|
1427
|
+
* transform: createMarkdownRemarkTransform(remarkShoutingHeadings),
|
|
1428
|
+
* });
|
|
1429
|
+
* ```
|
|
1430
|
+
*/
|
|
1431
|
+
export function createMarkdownRemarkTransform<
|
|
1432
|
+
Settings extends ReadonlyArray<unknown> = readonly [],
|
|
1433
|
+
>(
|
|
1434
|
+
plugin: MarkdownRemarkPlugin<Settings>,
|
|
1435
|
+
...settings: Settings
|
|
1436
|
+
): MarkdownTransform<never>;
|
|
1437
|
+
export function createMarkdownRemarkTransform<
|
|
1438
|
+
Settings extends ReadonlyArray<unknown> = readonly [],
|
|
1439
|
+
>(
|
|
1440
|
+
plugin: MarkdownRemarkCompatiblePlugin<Settings>,
|
|
1441
|
+
...settings: Settings
|
|
1442
|
+
): MarkdownTransform<never>;
|
|
1443
|
+
export function createMarkdownRemarkTransform(
|
|
1444
|
+
plugin: MarkdownRemarkCompatiblePlugin<ReadonlyArray<unknown>>,
|
|
1445
|
+
...settings: ReadonlyArray<unknown>
|
|
1446
|
+
): MarkdownTransform<never> {
|
|
1447
|
+
if (typeof plugin !== 'function') {
|
|
1448
|
+
throw new TypeError('Markdown Remark adapter: plugin must be a function');
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
let attached = false;
|
|
1452
|
+
let transformer: MarkdownRemarkTransformer | null = null;
|
|
1453
|
+
let attachFailure: string | null = null;
|
|
1454
|
+
|
|
1455
|
+
const attach = (): void => {
|
|
1456
|
+
attached = true;
|
|
1457
|
+
// The attacher can touch the processor guard and catch the rejection, so
|
|
1458
|
+
// it gets its own sticky state; a swallowed capability access is still a
|
|
1459
|
+
// failed attach.
|
|
1460
|
+
const attachState = createRunState();
|
|
1461
|
+
withRunState(attachState, () => {
|
|
1462
|
+
try {
|
|
1463
|
+
const created = (
|
|
1464
|
+
plugin as (this: unknown, ...args: ReadonlyArray<unknown>) => unknown
|
|
1465
|
+
).apply(processorGuard, settings as unknown as unknown[]);
|
|
1466
|
+
// Defuse before any verdict: an attacher that both tripped a guard
|
|
1467
|
+
// and returned a rejected promise must not leave that promise
|
|
1468
|
+
// unhandled just because the guard is reported instead.
|
|
1469
|
+
const attacherIsAsync = defuseThenable(created);
|
|
1470
|
+
if (attachState.failed) {
|
|
1471
|
+
reject(attachState.reason ?? FAILED_BY_PLUGIN);
|
|
1472
|
+
}
|
|
1473
|
+
if (attacherIsAsync) {
|
|
1474
|
+
reject('an asynchronous plugin is outside the adapter');
|
|
1475
|
+
}
|
|
1476
|
+
if (typeof created !== 'function') {
|
|
1477
|
+
attachFailure =
|
|
1478
|
+
'only a transform-only plugin returning one synchronous transformer is supported';
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
if (created.length >= 3) {
|
|
1482
|
+
attachFailure =
|
|
1483
|
+
'a callback-style (asynchronous) transformer is outside the adapter';
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
transformer = created as MarkdownRemarkTransformer;
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
attachFailure = describeFailure(error);
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
if (attachState.failed && attachFailure == null) {
|
|
1492
|
+
attachFailure = attachState.reason;
|
|
1493
|
+
transformer = null;
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
|
|
1497
|
+
return (
|
|
1498
|
+
document: MarkdownAstRoot<MarkdownAstExtensionNode>,
|
|
1499
|
+
context: MarkdownTransformContext,
|
|
1500
|
+
): MarkdownAstRoot<MarkdownAstExtensionNode> => {
|
|
1501
|
+
if (!attached) {
|
|
1502
|
+
attach();
|
|
1503
|
+
}
|
|
1504
|
+
const runner = transformer;
|
|
1505
|
+
if (runner == null) {
|
|
1506
|
+
context.report(`Remark adapter: ${attachFailure ?? 'the plugin failed'}`);
|
|
1507
|
+
return document;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const index = createOriginIndex();
|
|
1511
|
+
const {file, state} = createConstrainedFile(context.source);
|
|
1512
|
+
const run = createRunState();
|
|
1513
|
+
|
|
1514
|
+
return withRunState(run, () => {
|
|
1515
|
+
try {
|
|
1516
|
+
const tree = toRemarkNode(
|
|
1517
|
+
document as unknown as UnknownRecord,
|
|
1518
|
+
index,
|
|
1519
|
+
) as unknown as MarkdownRemarkRoot;
|
|
1520
|
+
const returned = runner(tree, file);
|
|
1521
|
+
// Defuse first, whatever else is wrong. A transformer that both
|
|
1522
|
+
// swallowed a guard refusal and returned a rejected promise still
|
|
1523
|
+
// gets that promise handled — the sticky verdict decides only what
|
|
1524
|
+
// is *reported*, never whether the promise is left dangling.
|
|
1525
|
+
const returnedIsAsync = defuseThenable(returned);
|
|
1526
|
+
if (run.failed) {
|
|
1527
|
+
// The plugin tripped a guard — a prohibited file key, a processor
|
|
1528
|
+
// access, its own fail() — and swallowed the rejection. The verdict
|
|
1529
|
+
// stands, and its original reason is the one reported.
|
|
1530
|
+
reject(run.reason ?? FAILED_BY_PLUGIN);
|
|
1531
|
+
}
|
|
1532
|
+
if (returnedIsAsync) {
|
|
1533
|
+
reject('an asynchronous transformer is outside the adapter');
|
|
1534
|
+
}
|
|
1535
|
+
const output = returned ?? tree;
|
|
1536
|
+
if (!isMarkdownPluginData(state.data)) {
|
|
1537
|
+
reject('file data must be finite JSON-like values');
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
const back: BackContext = {index, used: new Set()};
|
|
1541
|
+
const root = fromRemarkNode(output, null, false, back);
|
|
1542
|
+
if (root.type !== 'root') {
|
|
1543
|
+
reject('a transformer must return the document root');
|
|
1544
|
+
}
|
|
1545
|
+
const children = root.children as UnknownRecord[];
|
|
1546
|
+
if (
|
|
1547
|
+
context.display === 'inline' &&
|
|
1548
|
+
(children.length !== 1 || children[0]?.type !== 'paragraph')
|
|
1549
|
+
) {
|
|
1550
|
+
reject(
|
|
1551
|
+
'an inline document must stay one paragraph of phrasing content',
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
if (run.failed) {
|
|
1555
|
+
// Last word before the output becomes observable. A guard can fire
|
|
1556
|
+
// after the earlier check — a hostile `then` accessor, a getter on
|
|
1557
|
+
// a returned node — and a refusal the plugin caught must never end
|
|
1558
|
+
// with its output accepted.
|
|
1559
|
+
reject(run.reason ?? FAILED_BY_PLUGIN);
|
|
1560
|
+
}
|
|
1561
|
+
return root as unknown as MarkdownAstRoot<MarkdownAstExtensionNode>;
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
// Exactly one report per failed run, and never a second for the
|
|
1564
|
+
// plugin's own messages — see `describeFailure`. The first recorded
|
|
1565
|
+
// rejection outranks a later error, so swallowing one and throwing
|
|
1566
|
+
// something friendlier cannot change what is reported.
|
|
1567
|
+
context.report(
|
|
1568
|
+
`Remark adapter: ${run.reason ?? describeFailure(error)}`,
|
|
1569
|
+
);
|
|
1570
|
+
return document;
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
};
|
|
1574
|
+
}
|