diff --git a/biome.json b/.biome.json similarity index 96% rename from biome.json rename to .biome.json index 6a925f575bb6..ba8ec05d161e 100644 --- a/biome.json +++ b/.biome.json @@ -71,11 +71,11 @@ "noUndeclaredDependencies": "error" }, "suspicious": { - "noVar": "on" + "noVar": "on", + "noImportCycles": "error" }, "nursery": { - "noFloatingPromises": "error", - "noImportCycles": "error" + "noFloatingPromises": "error" } } }, diff --git a/.changeset/add-e18e-rule-source.md b/.changeset/add-e18e-rule-source.md new file mode 100644 index 000000000000..d5b770032fa7 --- /dev/null +++ b/.changeset/add-e18e-rule-source.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Added e18e ESLint plugin as a recognized rule source. Six Biome rules now reference their e18e equivalents: `useAtIndex`, `useExponentiationOperator`, `noPrototypeBuiltins`, `useDateNow`, `useSpread`, and `useObjectSpread`. diff --git a/.changeset/add-ignore-option-use-hook-at-top-level.md b/.changeset/add-ignore-option-use-hook-at-top-level.md new file mode 100644 index 000000000000..8db2c81494f8 --- /dev/null +++ b/.changeset/add-ignore-option-use-hook-at-top-level.md @@ -0,0 +1,25 @@ +--- +"@biomejs/biome": minor +--- + +Added `ignore` option to the [`useHookAtTopLevel`](https://biomejs.dev/linter/rules/use-hook-at-top-level/) rule. + +You can now specify function names that should not be treated as hooks, even if they follow the `use*` naming convention. + +Example configuration: + +```json +{ + "linter": { + "rules": { + "correctness": { + "useHookAtTopLevel": { + "options": { + "ignore": ["useDebounce", "useCustomUtility"] + } + } + } + } + } +} +``` diff --git a/.changeset/add-use-anchor-content-html.md b/.changeset/add-use-anchor-content-html.md new file mode 100644 index 000000000000..cacaec4d37de --- /dev/null +++ b/.changeset/add-use-anchor-content-html.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added the rule [`useAnchorContent`](https://biomejs.dev/linter/rules/use-anchor-content/) for HTML to enforce that anchor elements have accessible content for screen readers. The rule flags empty anchors, anchors with only whitespace, and anchors where all content is hidden with `aria-hidden`. Anchors with `aria-label` or `title` attributes providing a non-empty accessible name are considered valid. diff --git a/.changeset/add-use-media-caption-html.md b/.changeset/add-use-media-caption-html.md new file mode 100644 index 000000000000..c827d2e9ddd6 --- /dev/null +++ b/.changeset/add-use-media-caption-html.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added the rule [`useMediaCaption`](https://biomejs.dev/linter/rules/use-media-caption/) to the HTML language. Enforces that `audio` and `video` elements have a `track` element with `kind="captions"` for accessibility. Muted videos are allowed without captions. diff --git a/.changeset/all-pumas-stop.md b/.changeset/all-pumas-stop.md new file mode 100644 index 000000000000..d9ebe0cc7a7d --- /dev/null +++ b/.changeset/all-pumas-stop.md @@ -0,0 +1,31 @@ +--- +"@biomejs/biome": minor +--- + +Added support for multiple reporters, and the ability to save reporters on arbitrary files. + +#### Combine two reporters in CI + +If you run Biome on GitHub, take advantage of the reporter and still see the errors in console, you can now use both reporters: + +```shell +biome ci --reporter=default --reporter=github +``` + +#### Save reporter output to a file + +With the new `--reporter-file` CLI option, it's now possible to save the output of all reporters to a file. The file is a path, +so you can pass a relative or an absolute path: + +```shell +biome ci --reporter=rdjson --reporter-file=/etc/tmp/report.json +biome ci --reporter=summary --reporter-file=./reports/file.txt +``` + +You can combine these two features. For example, have the `default` reporter written on terminal, and the `rdjson` reporter written on file: + +```shell +biome ci --reporter=default --reporter=rdjson --reporter-file=/etc/tmp/report.json +``` + +**The `--reporter` and `--reporter-file` flags must appear next to each other, otherwise an error is thrown.** diff --git a/.changeset/angry-women-accept.md b/.changeset/angry-women-accept.md new file mode 100644 index 000000000000..ea2fa52908c4 --- /dev/null +++ b/.changeset/angry-women-accept.md @@ -0,0 +1,7 @@ +--- +"@biomejs/biome": minor +--- + +The Biome CSS parser is now able to parse Vue SFC syntax such as `:slotted` and `:deep`. These pseudo functions are only correctly parsed when the CSS is defined inside `.vue` components. Otherwise, Biome will a emit a parse error. + +This capability is only available when `experimentalFullHtmlSupportedEnabled` is set to `true`. diff --git a/.changeset/blue-buttons-own.md b/.changeset/blue-buttons-own.md new file mode 100644 index 000000000000..b04b6af0d4e1 --- /dev/null +++ b/.changeset/blue-buttons-own.md @@ -0,0 +1,9 @@ +--- +"@biomejs/biome": minor +--- + +Added support for Cursor files. When Biome sees a Cursor JSON file, it will parse it with comments enabled and trailing commas enabled: +- `$PROJECT/.cursor/` +- `%APPDATA%\Cursor\User\` on Windows +- `~/Library/Application Support/Cursor/User/` on macOS +- `~/.config/Cursor/User/` on Linux diff --git a/.changeset/bright-foxes-glow.md b/.changeset/bright-foxes-glow.md new file mode 100644 index 000000000000..ccab2e60d1e8 --- /dev/null +++ b/.changeset/bright-foxes-glow.md @@ -0,0 +1,30 @@ +--- +"@biomejs/biome": minor +--- + +Added JSON as a target language for GritQL pattern matching. You can now write Grit plugins for JSON files. + +This enables users to write GritQL patterns that match against JSON files, useful for: +- Searching and transforming JSON configuration files +- Enforcing patterns in `package.json` and other JSON configs +- Writing custom lint rules for JSON using GritQL + +**Example patterns:** + +Match all key-value pairs: +```grit +language json + +pair(key = $k, value = $v) +``` + +Match objects with specific structure: +```grit +language json + +JsonObjectValue() +``` + +Supports both native Biome AST names (`JsonMember`, `JsonObjectValue`) and TreeSitter-compatible names (`pair`, `object`, `array`) for compatibility with existing Grit patterns. + +For more details, see the [GritQL documentation](https://biomejs.dev/reference/gritql/). diff --git a/.changeset/brown-dryers-spend.md b/.changeset/brown-dryers-spend.md new file mode 100644 index 000000000000..57fb8bb2efa4 --- /dev/null +++ b/.changeset/brown-dryers-spend.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added `ignore` option to [`noUnknownProperty`](https://biomejs.dev/linter/rules/no-unknown-property). If an unknown property name matches any of the items provided in `ignore`, a diagnostic won't be emitted. diff --git a/.changeset/brown-women-jump.md b/.changeset/brown-women-jump.md new file mode 100644 index 000000000000..62b9418ec416 --- /dev/null +++ b/.changeset/brown-women-jump.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Add a new reporter `--reporter=sarif`, that emits diagnostics using the [SARIF](https://sarifweb.azurewebsites.net/) format. diff --git a/.changeset/calm-goats-talk.md b/.changeset/calm-goats-talk.md new file mode 100644 index 000000000000..8714b809faf5 --- /dev/null +++ b/.changeset/calm-goats-talk.md @@ -0,0 +1,18 @@ +--- +"@biomejs/biome": minor +--- + +Added the `useIframeTitle` lint rule for HTML. The rule enforces the usage of the `title` attribute for the `iframe` element. + +Invalid: + +```html + + +``` + +Valid: + +```html + +``` diff --git a/.changeset/chilly-jokes-decide.md b/.changeset/chilly-jokes-decide.md new file mode 100644 index 000000000000..9a17e469ed1b --- /dev/null +++ b/.changeset/chilly-jokes-decide.md @@ -0,0 +1,29 @@ +--- +"@biomejs/biome": minor +--- + +Added a new assist action `useSortedInterfaceMembers` that sorts TypeScript interface members, for readability. + +It includes an autofix. + +Invalid example. + +```ts,expect_diagnostic +interface MixedMembers { + z: string; + a: number; + (): void; + y: boolean; +} +``` + +Valid example (after using the assist). + +```ts +interface MixedMembers { + a: number; + y: boolean; + z: string; + (): void; +} +``` diff --git a/.changeset/chubby-buttons-lie.md b/.changeset/chubby-buttons-lie.md new file mode 100644 index 000000000000..2e35902a2b71 --- /dev/null +++ b/.changeset/chubby-buttons-lie.md @@ -0,0 +1,9 @@ +--- +"@biomejs/biome": minor +--- + +It's now possible to provide the stacktrace for a fatal error. The stacktrace is only available when the environment variable `RUST_BACKTRACE=1` is set, either via the CLI or exported `$PATH`. This is useful when providing detailed information for debugging purposes: + +```shell +RUST_BACKTRACE=1 biome lint +``` diff --git a/.changeset/clever-clocks-decide.md b/.changeset/clever-clocks-decide.md new file mode 100644 index 000000000000..e53cceb21900 --- /dev/null +++ b/.changeset/clever-clocks-decide.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +The Biome Language Server now reports progress while scanning files and dependencies in the project. diff --git a/.changeset/cruel-candles-grab.md b/.changeset/cruel-candles-grab.md new file mode 100644 index 000000000000..9ff0a60dfc64 --- /dev/null +++ b/.changeset/cruel-candles-grab.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Added proper parsing and formatting for Svelte directives when the `html.experimentalFullSupportEnabled` is set to `true`. diff --git a/.changeset/curly-facts-kick.md b/.changeset/curly-facts-kick.md new file mode 100644 index 000000000000..82fd4d8b5662 --- /dev/null +++ b/.changeset/curly-facts-kick.md @@ -0,0 +1,48 @@ +--- +"@biomejs/biome": patch +--- + +Revamped the logging options for all Biome commands. Now the commands `format`, `lint`, `check`, `ci`, `search`, `lsp-proxy` and `start` accept the following CLI options. + +Some options might have been present before, but they were inconsistent. Plus, all new options have an environment variable as aliases. + +#### `--log-file` + +Optional path/file to redirect log messages to. This option is applicable only to the CLI. If omitted, logs are printed to stdout. + +Environment variable alias: `BIOME_LOG_FILE` + +#### `--log-prefix-name` + +Allows changing the prefix applied to the file name of the logs. This option is applicable only to the daemon. + +Environment variable alias: `BIOME_LOG_PREFIX_NAME` + +#### `--log-path` + +Allows changing the folder where logs are stored. This option is applicable only to the daemon. + +Environment variable alias: `BIOME_LOG_PATH` + +#### `--log-level` + +The level of logging. In order, from the most verbose to the least verbose: `debug`, `info`, `warn`, `error` + +The value `none` won't show any logging. + +Environment variable alias: `BIOME_LOG_LEVEL` + +#### `--log-kind` + +What the log should look like. + +Environment variable alias: `BIOME_LOG_KIND` + +#### Reduce dumping of LSP logs + +When you use a Biome editor extension, Biome's Daemon dumps its logs using the `debug` level. If you want to reduce +the quantity of these logs, you can now customize it: + +```shell +BIOME_LOG_LEVEL=info biome lsp-proxy +``` diff --git a/.changeset/dirty-beans-flash.md b/.changeset/dirty-beans-flash.md new file mode 100644 index 000000000000..64b632b1a9db --- /dev/null +++ b/.changeset/dirty-beans-flash.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Fixed [#8024](https://github.com/biomejs/biome/issues/8024). The rule [`useIterableCallbackReturn`](https://biomejs.dev/linter/rules/use-iterable-callback-return/) now supports a `checkForEach` option. When set to `false`, the rule will skip checking for `forEach()` callbacks for returning values. diff --git a/.changeset/eight-bars-unite.md b/.changeset/eight-bars-unite.md new file mode 100644 index 000000000000..97b725798f06 --- /dev/null +++ b/.changeset/eight-bars-unite.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added the rule [`useValidLang`](https://biomejs.dev/linter/rules/use-valid-lang) to the HTML language. diff --git a/.changeset/eight-eels-pull.md b/.changeset/eight-eels-pull.md new file mode 100644 index 000000000000..5fc560898657 --- /dev/null +++ b/.changeset/eight-eels-pull.md @@ -0,0 +1,25 @@ +--- +"@biomejs/biome": minor +--- + +Added support for `jsxFactory` and `jsxFragmentFactory`.Biome now respects `jsxFactory` and `jsxFragmentFactory` settings from `tsconfig.json` when using the classic JSX runtime, preventing false positive [noUnusedImports](https://biomejs.dev/linter/rules/no-unused-imports/) errors for custom JSX libraries like Preact. + +```json5 +// tsconfig.json +{ + "compilerOptions": { + "jsx": "react", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment" + } +} +``` + +```jsx +// Component.jsx +import { h, Fragment } from 'preact'; + +function App() { + return
Hello
; +} +``` diff --git a/.changeset/every-beers-sleep.md b/.changeset/every-beers-sleep.md new file mode 100644 index 000000000000..9c82ca0dcb6f --- /dev/null +++ b/.changeset/every-beers-sleep.md @@ -0,0 +1,24 @@ +--- +"@biomejs/biome": minor +--- + +Added new CLI options to the commands `lsp-proxy` and `start` that allow to control the Biome file watcher. + +#### `--watcher-kind` + +Controls how the Biome file watcher should behave. By default, Biome chooses the best watcher strategy for the +current OS, however sometimes this could result in some issues, such as folders locked. + +The option accepts the current values: +- `recommended`: the default option, which chooses the best watcher for the current platform. +- `polling`: uses the polling strategy. +- `none`: it doesn't enable the watcher. When the watcher is disabled, changes to files aren't recorded anymore by Biome. This might have + repercussions on some lint rules that might rely on updated types or updated paths. + +The environment variable `BIOME_WATCHER_KIND` can be used as alias. + +#### `--watcher-polling-interval` + +The polling interval in milliseconds. This is only applicable when using the `polling` watcher. It defaults to `2000` milliseconds. + +The environment variable `BIOME_WATCHER_POLLING_INTERVAL` can be used as alias. diff --git a/.changeset/fast-months-move.md b/.changeset/fast-months-move.md new file mode 100644 index 000000000000..053b4fe9a164 --- /dev/null +++ b/.changeset/fast-months-move.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#901](https://github.com/biomejs/biome-vscode/issues/901). Biome now allows trailing commas in inside Zed `settings.json` and VSCode `settings.json`. diff --git a/.changeset/fifty-webs-sneeze.md b/.changeset/fifty-webs-sneeze.md new file mode 100644 index 000000000000..05b23016ac74 --- /dev/null +++ b/.changeset/fifty-webs-sneeze.md @@ -0,0 +1,19 @@ +--- +"@biomejs/biome": minor +--- + +Added the `useHtmlLang` lint rule for HTML. The rule enforces that the `html` element has a `lang` attribute. + +Invalid: + +```html + + + +``` + +Valid: + +```html + +``` diff --git a/.changeset/fix-component-export-tanstack-router.md b/.changeset/fix-component-export-tanstack-router.md new file mode 100644 index 000000000000..e9ae0c58074e --- /dev/null +++ b/.changeset/fix-component-export-tanstack-router.md @@ -0,0 +1,13 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8628](https://github.com/biomejs/biome/issues/8628): [`useComponentExportOnlyModules`](https://biomejs.dev/linter/rules/use-component-export-only-modules/) now allows components referenced as object property values in exported expressions. This fixes false positives for TanStack Router patterns. + +```jsx +export const Route = createFileRoute('/')({ + component: HomeComponent, +}) + +function HomeComponent() { ... } // no longer reported as "should be exported" +``` diff --git a/.changeset/fix-diagnostic-offset-in-embedded-files.md b/.changeset/fix-diagnostic-offset-in-embedded-files.md new file mode 100644 index 000000000000..d27fc4a5eaef --- /dev/null +++ b/.changeset/fix-diagnostic-offset-in-embedded-files.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9057](https://github.com/biomejs/biome/issues/9057): Incorrect diagnostic spans for suppression comments and other raw diagnostics in HTML-ish files (Vue, Svelte, Astro). Previously, diagnostics like "unused suppression" pointed to the wrong location in the document due to the diagnostic offset not being applied. diff --git a/.changeset/full-berries-follow.md b/.changeset/full-berries-follow.md new file mode 100644 index 000000000000..999a0664816b --- /dev/null +++ b/.changeset/full-berries-follow.md @@ -0,0 +1,41 @@ +--- +"@biomejs/biome": minor +--- + +Added support for formatting and linting embedded GraphQL snippets in JavaScript. + +For example, the following snippets are now formatted: + +```js +import gql from "graphql-tag"; + +const PeopleCountQuery = gql` + query PeopleCount { + allPeople { + totalCount + } + } +`; +``` + +```js +import { graphql } from "./graphql"; + +const PeopleCountQuery = graphql(` + query PeopleCount { + allPeople { + totalCount + } + } +`); +``` + +This feature is experimental and must be enabled explicitly in the configuration: + +```json +{ + "javascript": { + "experimentalEmbeddedSnippetsEnabled": true + } +} +``` diff --git a/.changeset/group-by-nesting-feature.md b/.changeset/group-by-nesting-feature.md new file mode 100644 index 000000000000..328be09b523e --- /dev/null +++ b/.changeset/group-by-nesting-feature.md @@ -0,0 +1,51 @@ +--- +"@biomejs/biome": minor +--- + +Added `groupByNesting` option to the `useSortedKeys` assist. When enabled, object keys are grouped by their value's nesting depth before sorting alphabetically. + +Simple values (primitives, single-line arrays, and single-line objects) are sorted first, followed by nested values (multi-line arrays and multi-line objects). + +#### Example + +To enable this option, configure it in your `biome.json`: + +```json +{ + "linter": { + "rules": { + "source": { + "useSortedKeys": { + "options": { + "groupByNesting": true + } + } + } + } + } +} +``` + +With this option, the following unsorted object: + +```js +const object = { + "name": "Sample", + "details": { + "description": "nested" + }, + "id": 123 +} +``` + +Will be sorted as: + +```js +const object ={ + "id": 123, + "name": "Sample", + "details": { + "description": "nested" + } +} +``` diff --git a/.changeset/html-no-autofocus.md b/.changeset/html-no-autofocus.md new file mode 100644 index 000000000000..e2bacce5a640 --- /dev/null +++ b/.changeset/html-no-autofocus.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added the [`noAutofocus`](https://biomejs.dev/linter/rules/no-autofocus/) lint rule for HTML. This rule enforces that the `autofocus` attribute is not used on elements, as it can cause usability issues for sighted and non-sighted users. The rule allows `autofocus` inside `dialog` elements or elements with the `popover` attribute, as these are modal contexts where autofocus is expected. diff --git a/.changeset/html-no-positive-tabindex.md b/.changeset/html-no-positive-tabindex.md new file mode 100644 index 000000000000..3b6ffdc33041 --- /dev/null +++ b/.changeset/html-no-positive-tabindex.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added [`noPositiveTabindex`](https://biomejs.dev/linter/rules/no-positive-tabindex/) to HTML. This rule prevents the usage of positive integers on the `tabindex` attribute, which can disrupt natural keyboard navigation order. diff --git a/.changeset/html-use-alt-text.md b/.changeset/html-use-alt-text.md new file mode 100644 index 000000000000..768eb5d84453 --- /dev/null +++ b/.changeset/html-use-alt-text.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added the [`useAltText`](https://biomejs.dev/linter/rules/use-alt-text/) lint rule for HTML. This rule enforces that elements requiring alternative text (``, ``, ``, ``) provide meaningful information for screen reader users via `alt`, `title` (for objects), `aria-label`, or `aria-labelledby` attributes. Elements with `aria-hidden="true"` are exempt. diff --git a/.changeset/huge-cycles-dance.md b/.changeset/huge-cycles-dance.md new file mode 100644 index 000000000000..c167c3984d72 --- /dev/null +++ b/.changeset/huge-cycles-dance.md @@ -0,0 +1,36 @@ +--- +"@biomejs/biome": minor +--- + +Implements [#1984](https://github.com/biomejs/biome/issues/1984). Updated [`useHookAtTopLevel`](https://biomejs.dev/linter/rules/use-hook-at-top-level/) to better catch invalid hook usage. + +This rule is now capable of finding invalid hook usage in more locations. A diagnostic will now be generated if: +- A hook is used at the module level (top of the file, outside any function). +- A hook is used within a function or method which is not a hook or component, unless it is a function expression (such as arrow functions commonly used in tests). + +**Invalid:** + +```js +// Invalid: hooks cannot be called at the module level. +useHook(); +``` + +```js +// Invalid: hooks must be called from another hook or component. +function notAHook() { + useHook(); +} +``` + +**Valid:** + +```js +// Valid: hooks may be called from function expressions, such as in tests. +test("my hook", () => { + renderHook(() => useHook()); + + renderHook(function() { + return useHook(); + }); +}); +``` diff --git a/.changeset/hungry-jokes-crash.md b/.changeset/hungry-jokes-crash.md new file mode 100644 index 000000000000..f3bd2edca754 --- /dev/null +++ b/.changeset/hungry-jokes-crash.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed the `noSvgWithoutTitle` rule not to recursively traverse `title` elements. diff --git a/.changeset/lucky-snails-happen.md b/.changeset/lucky-snails-happen.md new file mode 100644 index 000000000000..b17426c92a51 --- /dev/null +++ b/.changeset/lucky-snails-happen.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added the `useValidAriaRole` lint rule for HTML. The rule enforces that elements with ARIA roles must use a valid, non-abstract ARIA role. diff --git a/.changeset/mean-buses-press.md b/.changeset/mean-buses-press.md new file mode 100644 index 000000000000..768147616f6f --- /dev/null +++ b/.changeset/mean-buses-press.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added `ignore` option to [`noUnknownFunction`](https://biomejs.dev/linter/rules/no-unknown-function). If an unknown function name matches any of the items provided in `ignore`, a diagnostic won't be emitted. diff --git a/.changeset/nine-ends-wink.md b/.changeset/nine-ends-wink.md new file mode 100644 index 000000000000..9b71ab05be65 --- /dev/null +++ b/.changeset/nine-ends-wink.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added `ignore` option to [`noUnknownPseudoClass`](https://biomejs.dev/linter/rules/no-unknown-pseudo-class). If an unknown pseudo-class name matches any of the items provided in `ignore`, a diagnostic won't be emitted. diff --git a/.changeset/ninety-rice-like.md b/.changeset/ninety-rice-like.md new file mode 100644 index 000000000000..07319338b8ab --- /dev/null +++ b/.changeset/ninety-rice-like.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Added proper parsing for spread attributes `{...props}` in Svelte and Astro files. diff --git a/.changeset/no-duplicate-classes-rule.md b/.changeset/no-duplicate-classes-rule.md new file mode 100644 index 000000000000..c6648673374d --- /dev/null +++ b/.changeset/no-duplicate-classes-rule.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": minor +--- + +Added the [`noDuplicateClasses`](https://biomejs.dev/assist/actions/no-duplicate-classes/) assist action to detect and remove duplicate CSS classes. + +**For JSX files:** Supports `class`, `className` attributes and utility functions like `clsx`, `cn`, `cva`. + +**For HTML files:** Checks `class` attributes. This is the first assist action for HTML. + +```jsx +// Before +
; + +// After +
; +``` diff --git a/.changeset/odd-flies-nail.md b/.changeset/odd-flies-nail.md new file mode 100644 index 000000000000..d2de2e8572f4 --- /dev/null +++ b/.changeset/odd-flies-nail.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": minor +--- + +Improved the CSS parser for CSS modules. Biome now automatically enables CSS modules parsing for `*.module.css` files. + +If your codebase has only `*.module.css` files, you can remove the parser feature as follows, because now Biome does it for you: + +```diff +{ + "css": { + "parser": { +- "cssModules": true + } + } +} +``` diff --git a/.changeset/odd-schools-behave.md b/.changeset/odd-schools-behave.md new file mode 100644 index 000000000000..49a21ba42bb0 --- /dev/null +++ b/.changeset/odd-schools-behave.md @@ -0,0 +1,14 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#4927](https://github.com/biomejs/biome/issues/4927), [#6407](https://github.com/biomejs/biome/issues/6407): The HTML formatter will now correctly break a block-like element if it has more than 2 children, and at least one of them is another block-like element. + +```diff +-
a
b
c
++
++ a ++
b
++ c ++
+``` diff --git a/.changeset/petite-mice-say.md b/.changeset/petite-mice-say.md new file mode 100644 index 000000000000..b0d8c6cba57c --- /dev/null +++ b/.changeset/petite-mice-say.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [`#7912`](https://github.com/biomejs/biome/issues/7912), where Biome incorrectly added a leading newline to the code contained inside the Astro frontmatter. diff --git a/.changeset/plenty-hornets-hide.md b/.changeset/plenty-hornets-hide.md new file mode 100644 index 000000000000..f999a1285b0e --- /dev/null +++ b/.changeset/plenty-hornets-hide.md @@ -0,0 +1,7 @@ +--- +"@biomejs/biome": minor +--- + +Added support for parsing `:global` and `:local` inside `.astro`, `.svelte` and `.vue` files, in ` "# .as_bytes(), @@ -748,3 +756,348 @@ fn does_not_throw_parse_error_for_return_full_support() { result, )); } + +#[test] +fn embedded_bindings_are_tracked_correctly() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.astro"); + fs.insert( + file.into(), + r#"--- +import { Component } from "./component.svelte"; +let hello = "Hello World"; +let array = []; +let props = []; +--- + + + {hello} + {notDefined} + { array.map(item => ({item})) } + + + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUnusedVariables", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "embedded_bindings_are_tracked_correctly", + fs, + console, + result, + )); +} + +#[test] +fn use_const_not_triggered_in_snippet_sources() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.astro"); + fs.insert( + file.into(), + r#"--- +let hello = "Hello World"; +--- + + + {hello} + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=useConst", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "use_const_not_triggered_in_snippet_sources", + fs, + console, + result, + )); +} + +#[test] +fn no_unused_imports_is_not_triggered_in_snippet_sources() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.astro"); + fs.insert( + file.into(), + r#"--- +import Component from "./Component.vue" +--- + + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUnusedImports", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_unused_imports_is_not_triggered_in_snippet_sources", + fs, + console, + result, + )); +} + +#[test] +fn issue_7912() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "experimentalFullSupportEnabled": true, "formatter": { "enabled": true } } }"#.as_bytes(), + ); + + let astro_file_path = Utf8Path::new("file.astro"); + fs.insert( + astro_file_path.into(), + r#"--- + const title = "Hello World"; +--- + + + + {title} + + +

{title}

+ +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from( + [ + "lint", + "--write", + "--only=suspicious/noDebugger", + astro_file_path.as_str(), + ] + .as_slice(), + ), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "issue_7912", + fs, + console, + result, + )); +} + +const ASTRO_ENUM_IN_TEMPLATE: &str = r#"--- +import { Component, FooEnum } from './types'; +--- +
+ + {FooEnum.Foo} +
"#; + +#[test] +fn use_import_type_not_triggered_for_enum_in_template() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file = Utf8Path::new("file.astro"); + fs.insert(file.into(), ASTRO_ENUM_IN_TEMPLATE.as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=useImportType", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "use_import_type_not_triggered_for_enum_in_template", + fs, + console, + result, + )); +} + +#[test] +fn use_import_type_not_triggered_for_enum_in_template_v2() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.astro"); + fs.insert( + file.into(), + r#"--- +import { Avatar as AvatarPrimitive } from "bits-ui"; +import { cn } from "$lib/utils.js"; + +let { + ref = $bindable(null), + class: className, +}: AvatarPrimitive.FallbackProps = $props(); +--- + + + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=useImportType", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "use_import_type_not_triggered_for_enum_in_template_v2", + fs, + console, + result, + )); +} + +#[test] +fn no_useless_lone_block_statements_is_not_triggered() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.astro"); + fs.insert( + file.into(), + r#"--- +{ + const x = 1; +} +--- + +
{x}
+"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUselessLoneBlockStatements", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_useless_lone_block_statements_is_not_triggered", + fs, + console, + result, + )); +} + +#[test] +fn return_in_template_expression_should_error() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let astro_file_path = Utf8Path::new("file.astro"); + fs.insert(astro_file_path.into(), ASTRO_RETURN_IN_TEMPLATE.as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", astro_file_path.as_str()].as_slice()), + ); + + // The result should have errors because return is not allowed in template expressions + // We expect the check to fail with errors + assert!( + result.is_err(), + "Expected errors but run_cli returned {result:?}" + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "return_in_template_expression_should_error", + fs, + console, + result, + )); +} diff --git a/crates/biome_cli/tests/cases/handle_svelte_files.rs b/crates/biome_cli/tests/cases/handle_svelte_files.rs index 161bc3d36f9e..0272395975d6 100644 --- a/crates/biome_cli/tests/cases/handle_svelte_files.rs +++ b/crates/biome_cli/tests/cases/handle_svelte_files.rs @@ -239,6 +239,9 @@ schema + sure() "# .as_bytes(), @@ -539,3 +542,368 @@ fn check_stdin_write_unsafe_successfully() { result, )); } + +#[test] +fn embedded_bindings_are_tracked_correctly() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.svelte"); + fs.insert( + file.into(), + r#" + + + {hello} + {notDefined} + {#each array as item} + {/each} + + + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUnusedVariables", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "embedded_bindings_are_tracked_correctly", + fs, + console, + result, + )); +} + +#[test] +fn use_const_not_triggered_in_snippet_sources() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.svelte"); + fs.insert( + file.into(), + r#" + + + {hello} + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=useConst", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "use_const_not_triggered_in_snippet_sources", + fs, + console, + result, + )); +} + +#[test] +fn no_unused_imports_is_not_triggered_in_snippet_sources() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.svelte"); + fs.insert( + file.into(), + r#" + + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUnusedImports", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_unused_imports_is_not_triggered_in_snippet_sources", + fs, + console, + result, + )); +} + +const SVELTE_ENUM_IN_TEMPLATE: &str = r#" +
+ + {FooEnum.Foo} +
"#; + +#[test] +fn use_import_type_not_triggered_for_enum_in_template() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + let file = Utf8Path::new("file.svelte"); + fs.insert(file.into(), SVELTE_ENUM_IN_TEMPLATE.as_bytes()); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=useImportType", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "use_import_type_not_triggered_for_enum_in_template", + fs, + console, + result, + )); +} + +#[test] +fn use_import_type_not_triggered_for_enum_in_template_v2() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.svelte"); + fs.insert( + file.into(), + r#" + + + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=useImportType", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "use_import_type_not_triggered_for_enum_in_template_v2", + fs, + console, + result, + )); +} +#[test] +fn no_useless_lone_block_statements_is_not_triggered() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.svelte"); + fs.insert( + file.into(), + r#" + {#snippet child({ props, open })} + {#if open} +
+ {@render children?.()} +
+ {/if} + {/snippet} +
+"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUselessLoneBlockStatements", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_useless_lone_block_statements_is_not_triggered", + fs, + console, + result, + )); +} + +#[test] +fn supports_ts_in_embedded_expressions() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.svelte"); + fs.insert( + file.into(), + r#" + +
{ + if ((e.target as HTMLElement).closest("button")) { + return; + } + e.currentTarget.parentElement?.querySelector("input")?.focus(); + }} +> +
+"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUselessLoneBlockStatements", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "supports_ts_in_embedded_expressions", + fs, + console, + result, + )); +} + +#[test] +fn no_unused_variables_in_svelte_directives() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let file = Utf8Path::new("file.svelte"); + fs.insert( + file.into(), + r#" + +
+ + + + + + + +
Active
+ + +
Styled
+ + +

{inputValue}

+

{isChecked}

+
+"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["lint", "--only=noUnusedVariables", file.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_unused_variables_in_svelte_directives", + fs, + console, + result, + )); +} diff --git a/crates/biome_cli/tests/cases/handle_vue_files.rs b/crates/biome_cli/tests/cases/handle_vue_files.rs index 852d7ad5c078..52445d9bcd64 100644 --- a/crates/biome_cli/tests/cases/handle_vue_files.rs +++ b/crates/biome_cli/tests/cases/handle_vue_files.rs @@ -1,7 +1,7 @@ use crate::run_cli; use crate::snap_test::{SnapshotPayload, assert_cli_snapshot}; use biome_console::BufferConsole; -use biome_fs::MemoryFileSystem; +use biome_fs::{FileSystemExt, MemoryFileSystem}; use bpaf::Args; use camino::Utf8Path; @@ -493,6 +493,12 @@ schema + sure() "# .as_bytes(), @@ -515,6 +521,50 @@ schema + sure() )); } +#[test] +fn parse_vue_css_v_bind_function() { + let fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + fs.insert( + "biome.json".into(), + r#"{ "html": { "formatter": {"enabled": true}, "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"# + .as_bytes(), + ); + + let vue_file_path = Utf8Path::new("file.vue"); + fs.insert( + vue_file_path.into(), + r#" + + +"# + .as_bytes(), + ); + + let (fs, result) = run_cli( + fs, + &mut console, + Args::from(["check", "--write", "--unsafe", vue_file_path.as_str()].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "parse_vue_css_v_bind_function", + fs, + console, + result, + )); +} + #[test] fn full_support_ts() { let fs = MemoryFileSystem::default(); @@ -581,7 +631,7 @@ fn full_support_enabled_and_scss_is_skipped() { let astro_file_path = Utf8Path::new("file.vue"); fs.insert( astro_file_path.into(), - r#"Svelte + r#"Vue ``` @@ -57,15 +61,39 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━ # Emitted Messages ```block -file.astro:16:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:10:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a lang attribute when using the html element. + + 8 │ --- + 9 │ + > 10 │ + │ ^^^^^^ + > 11 │ + > 12 │ Astro + > 13 │ + > 14 │ + > 15 │ + │ ^^^^^^^ + 16 │ + 17 │ - 18 │ + 17 │ ``` @@ -57,15 +61,39 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━ # Emitted Messages ```block -file.svelte:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:10:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a lang attribute when using the html element. + + 8 │ + 9 │ + > 10 │ + │ ^^^^^^ + > 11 │ + > 12 │ Svelte + > 13 │ + > 14 │ + > 15 │ + │ ^^^^^^^ + 16 │ + 17 │ - 14 │ + 17 │ - 19 │ + 22 │ ``` @@ -57,15 +64,39 @@ check ━━━━━━━━━━━━━━━━━━━━━━━━ # Emitted Messages ```block -file.vue:12:20 lint/a11y/useGenericFontNames ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.vue:10:1 lint/a11y/useHtmlLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a lang attribute when using the html element. + + 8 │ + 9 │ + > 10 │ + │ ^^^^^^ + > 11 │ + > 12 │ Svelte + > 13 │ + > 14 │ + > 15 │ + │ ^^^^^^^ + 16 │ + 17 │ - 14 │ + 17 │ - 19 │ + 22 │ + +``` + +# Emitted Messages + +```block +Checked 1 file in
+ ``` diff --git a/crates/biome_html_formatter/tests/specs/html/long-inline-elements.html.snap b/crates/biome_html_formatter/tests/specs/html/long-inline-elements.html.snap index 6a89a17652d5..08a95d007528 100644 --- a/crates/biome_html_formatter/tests/specs/html/long-inline-elements.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/long-inline-elements.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: long-inline-elements.html --- + # Input ```html @@ -40,6 +41,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -60,4 +62,5 @@ ut risus vel sollicitudin. Maecenas eu bibendum lorem.Sed porttitor commodo commodo. Morbi luctus consequat maximus. Vestibulum viverra libero quis lacus euismod, ut consequat ante convallis. + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/many-children.html.snap b/crates/biome_html_formatter/tests/specs/html/many-children.html.snap index 255db12ef96f..68e48e1f9627 100644 --- a/crates/biome_html_formatter/tests/specs/html/many-children.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/many-children.html.snap @@ -1,8 +1,8 @@ --- source: crates/biome_formatter_test/src/snapshot_builder.rs info: many-children.html -snapshot_kind: text --- + # Input ```html @@ -30,6 +30,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -43,4 +44,5 @@ Self close void elements: never
Corge
Grault
+ ``` diff --git a/crates/biome_html_formatter/tests/specs/html/self-closing.html.snap b/crates/biome_html_formatter/tests/specs/html/self-closing.html.snap index 8a1b70eb20c9..3bdc370198a8 100644 --- a/crates/biome_html_formatter/tests/specs/html/self-closing.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/self-closing.html.snap @@ -1,8 +1,8 @@ --- source: crates/biome_formatter_test/src/snapshot_builder.rs info: self-closing.html -snapshot_kind: text --- + # Input ```html @@ -43,6 +43,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -63,4 +64,5 @@ Self close void elements: never + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/self-closing/always.html.snap b/crates/biome_html_formatter/tests/specs/html/self-closing/always.html.snap index 98c115dafdf1..f2a390350f86 100644 --- a/crates/biome_html_formatter/tests/specs/html/self-closing/always.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/self-closing/always.html.snap @@ -1,8 +1,8 @@ --- source: crates/biome_formatter_test/src/snapshot_builder.rs info: self-closing/always.html -snapshot_kind: text --- + # Input ```html @@ -40,6 +40,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -57,6 +58,7 @@ Self close void elements: never + ``` ## Output 1 @@ -71,6 +73,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: always +Trailing newline: true ----- ```html @@ -88,4 +91,5 @@ Self close void elements: always + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/self-closing/never.html.snap b/crates/biome_html_formatter/tests/specs/html/self-closing/never.html.snap index 2ed75d81fe30..0037c2e90097 100644 --- a/crates/biome_html_formatter/tests/specs/html/self-closing/never.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/self-closing/never.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: self-closing/never.html --- + # Input ```html @@ -39,6 +40,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -56,6 +58,7 @@ Self close void elements: never + ``` ## Output 1 @@ -70,6 +73,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: always +Trailing newline: true ----- ```html @@ -87,4 +91,5 @@ Self close void elements: always + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression-2.html.snap b/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression-2.html.snap index c23873ef1e33..6b2824e4c3cb 100644 --- a/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression-2.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression-2.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: suppressions/basic-suppression-2.html --- + # Input ```html @@ -30,6 +31,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -37,4 +39,5 @@ Self close void elements: never

Test Test Test

+ ``` diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression.html.snap b/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression.html.snap index 6beb599f5c76..d25c14ff2b34 100644 --- a/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/basic-suppression.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: suppressions/basic-suppression.html --- + # Input ```html @@ -26,11 +27,13 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html
This is some really long content that should be broken on multiple lines. However, it won't because there is a suppression comment. This is the desired behavior.
+ ``` # Lines exceeding max width of 80 characters diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html b/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html new file mode 100644 index 000000000000..c8841f92d607 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html.snap b/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html.snap new file mode 100644 index 000000000000..20e754c7c645 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html.snap @@ -0,0 +1,57 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: suppressions/global_suppression.html +--- + +# Input + +```html + + + + + + + + + + + + + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```html + + + + + + + + + + + + +``` diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/inline-content.html.snap b/crates/biome_html_formatter/tests/specs/html/suppressions/inline-content.html.snap index 879f185dabf1..ba17799a58da 100644 --- a/crates/biome_html_formatter/tests/specs/html/suppressions/inline-content.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/inline-content.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: suppressions/inline-content.html --- + # Input ```html @@ -26,8 +27,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html 1234 56 + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/inline-elements.html.snap b/crates/biome_html_formatter/tests/specs/html/suppressions/inline-elements.html.snap index 8180dfb95ec1..7b79959ae3e9 100644 --- a/crates/biome_html_formatter/tests/specs/html/suppressions/inline-elements.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/inline-elements.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: suppressions/inline-elements.html --- + # Input ```html @@ -25,8 +26,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html 12 3 + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/suppress-inside-element.html.snap b/crates/biome_html_formatter/tests/specs/html/suppressions/suppress-inside-element.html.snap index 29dcf9e2f0b7..dab737a46af6 100644 --- a/crates/biome_html_formatter/tests/specs/html/suppressions/suppress-inside-element.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/suppress-inside-element.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: suppressions/suppress-inside-element.html --- + # Input ```html @@ -29,6 +30,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -36,6 +38,7 @@ Self close void elements: never This is some really long content that should be broken on multiple lines. However, it won't because there is a suppression comment. This is the desired behavior. + ``` # Lines exceeding max width of 80 characters diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap index 05711b97d927..62d149cb75d9 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/attach.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/attach.svelte --- + # Input ```svelte @@ -41,6 +42,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -58,4 +60,5 @@ Self close void elements: never }); }} > + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_array_destructuring.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_array_destructuring.svelte.snap index 78aa2a4b7c1e..cb7b232570b9 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_array_destructuring.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_array_destructuring.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_array_destructuring.svelte --- + # Input ```svelte @@ -29,6 +30,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -36,4 +38,5 @@ Self close void elements: never

First: {first}, Second: {second}

Rest: {rest.join(', ')}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_basic.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_basic.svelte.snap index f541c9f40a7b..baddd68b7cd3 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_basic.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_basic.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_basic.svelte --- + # Input ```svelte @@ -32,6 +33,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -42,4 +44,5 @@ Self close void elements: never {:catch error}

Something went wrong: {error.message}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_catch_only.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_catch_only.svelte.snap index d410ad7864c5..3ed72b56323d 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_catch_only.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_catch_only.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_catch_only.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#await promise catch error}

Error: {error.message}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_complex_expression.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_complex_expression.svelte.snap index a4cc81c3f10a..5236ffd22616 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_complex_expression.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_complex_expression.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_complex_expression.svelte --- + # Input ```svelte @@ -35,6 +36,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -48,4 +50,5 @@ Self close void elements: never {:catch error}

Failed to load profile: {error.message}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_destructuring.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_destructuring.svelte.snap index 015fd7f76a34..276a259d3b09 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_destructuring.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_destructuring.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_destructuring.svelte --- + # Input ```svelte @@ -32,6 +33,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -42,4 +44,5 @@ Self close void elements: never

Address: {address}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_dynamic_import.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_dynamic_import.svelte.snap index b044e6eb54f8..eddd586e1fc3 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_dynamic_import.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_dynamic_import.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_dynamic_import.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#await import('./Component.svelte') then { default: Component }} {/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_inline.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_inline.svelte.snap index aad40bcbbd53..965205accd9d 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_inline.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_inline.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_inline.svelte --- + # Input ```svelte @@ -26,6 +27,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -34,4 +36,5 @@ Self close void elements: never {value} {/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_long_expression.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_long_expression.svelte.snap index a9939f30eafd..8f4e9d218e6b 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_long_expression.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_long_expression.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_long_expression.svelte --- + # Input ```svelte @@ -30,6 +31,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -38,6 +40,7 @@ Self close void elements: never {:then profile}
{profile.name}
{/await} + ``` # Lines exceeding max width of 80 characters diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_multiline.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_multiline.svelte.snap index bd66de8c6aa1..0221b7a41090 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_multiline.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_multiline.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_multiline.svelte --- + # Input ```svelte @@ -42,6 +43,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -62,6 +64,7 @@ Self close void elements: never {/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_multiple.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_multiple.svelte.snap index 6cd5a42d92c4..6560e1acb6f6 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_multiple.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_multiple.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_multiple.svelte --- + # Input ```svelte @@ -46,6 +47,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -70,4 +72,5 @@ Self close void elements: never {/each} {/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_nested.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_nested.svelte.snap index 5cfb9cf0aa70..263cc11fb18b 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_nested.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_nested.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_nested.svelte --- + # Input ```svelte @@ -41,6 +42,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -60,4 +62,5 @@ Self close void elements: never {:catch error}

Error: {error.message}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_no_catch.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_no_catch.svelte.snap index f66ab54002f5..a627b17c506c 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_no_catch.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_no_catch.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_no_catch.svelte --- + # Input ```svelte @@ -30,6 +31,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -38,4 +40,5 @@ Self close void elements: never {:then value}

The value is {value}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_one_liner.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_one_liner.svelte.snap index 7809d6be175a..2ce8bf1508ec 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_one_liner.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_one_liner.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_one_liner.svelte --- + # Input ```svelte @@ -26,6 +27,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -36,4 +38,5 @@ Self close void elements: never {:catch name}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_shorthand.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_shorthand.svelte.snap index a02760cf5207..d3e4f8be2d1b 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_shorthand.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_shorthand.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_shorthand.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#await promise then value}

The value is {value}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_with_comments.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_with_comments.svelte.snap index 6a2765690018..a57088305a5d 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_with_comments.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_with_comments.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_with_comments.svelte --- + # Input ```svelte @@ -36,6 +37,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -50,4 +52,5 @@ Self close void elements: never

Error: {error.message}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/await_with_whitespace.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/await_with_whitespace.svelte.snap index 092dad326f6e..ab31df438b66 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/await_with_whitespace.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/await_with_whitespace.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/await_with_whitespace.svelte --- + # Input ```svelte @@ -32,6 +33,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -42,4 +44,5 @@ Self close void elements: never {:catch error}

Error: {error.message}

{/await} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/bind_component.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/bind_component.svelte new file mode 100644 index 000000000000..9acca3b20493 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/bind_component.svelte @@ -0,0 +1 @@ + diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/bind_component.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/bind_component.svelte.snap new file mode 100644 index 000000000000..fa474bac460c --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/bind_component.svelte.snap @@ -0,0 +1,36 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/bind_component.svelte +--- + +# Input + +```svelte + + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte + + +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/compat_binding.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/compat_binding.svelte new file mode 100644 index 000000000000..175c25eedb7a --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/compat_binding.svelte @@ -0,0 +1,3 @@ +
+
+
diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/compat_binding.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/compat_binding.svelte.snap new file mode 100644 index 000000000000..17bdbf05572a --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/compat_binding.svelte.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/compat_binding.svelte +--- + +# Input + +```svelte +
+
+
+ +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte +
+
+
+ +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/component.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/component.svelte.snap index 1b4d73bc6253..f8635eb9dfca 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/component.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/component.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/component.svelte --- + # Input ```svelte @@ -27,8 +28,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte - + + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap index 4771a7a1bcdc..6a9567238dff 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/const.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/const.svelte --- + # Input ```svelte @@ -26,8 +27,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {@const area = box.width * box.height} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/debug.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/debug.svelte.snap index d4284605562b..2392600bd92d 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/debug.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/debug.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/debug.svelte --- + # Input ```svelte @@ -31,6 +32,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -40,4 +42,5 @@ Self close void elements: never {@debug foo, bar} {@debug foo, bar, baz} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_bind_basic.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/directive_bind_basic.svelte new file mode 100644 index 000000000000..56aeb620b97f --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_bind_basic.svelte @@ -0,0 +1,2 @@ + + diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_bind_basic.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/directive_bind_basic.svelte.snap new file mode 100644 index 000000000000..5d62e5fb261b --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_bind_basic.svelte.snap @@ -0,0 +1,38 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/directive_bind_basic.svelte +--- + +# Input + +```svelte + + + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte + + + +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_class_basic.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/directive_class_basic.svelte new file mode 100644 index 000000000000..958eaaa91fe1 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_class_basic.svelte @@ -0,0 +1,3 @@ +
Toggle class
+
Shorthand class
+

Multiple classes

diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_class_basic.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/directive_class_basic.svelte.snap new file mode 100644 index 000000000000..05e366fd90f5 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_class_basic.svelte.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/directive_class_basic.svelte +--- + +# Input + +```svelte +
Toggle class
+
Shorthand class
+

Multiple classes

+ +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte +
Toggle class
+
Shorthand class
+

Multiple classes

+ +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_multiple.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/directive_multiple.svelte new file mode 100644 index 000000000000..d01be0ab81dd --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_multiple.svelte @@ -0,0 +1,2 @@ + +
Multiple directives
diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_multiple.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/directive_multiple.svelte.snap new file mode 100644 index 000000000000..c852da1fcc6a --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_multiple.svelte.snap @@ -0,0 +1,44 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/directive_multiple.svelte +--- + +# Input + +```svelte + +
Multiple directives
+ +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte + +
+ Multiple directives +
+ +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_style_important.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/directive_style_important.svelte new file mode 100644 index 000000000000..4a0a93e46f49 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_style_important.svelte @@ -0,0 +1,3 @@ +
Normal style
+
Important style
+

Multiple styles

diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_style_important.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/directive_style_important.svelte.snap new file mode 100644 index 000000000000..a4daccf86bd2 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_style_important.svelte.snap @@ -0,0 +1,42 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/directive_style_important.svelte +--- + +# Input + +```svelte +
Normal style
+
Important style
+

Multiple styles

+ +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte +
Normal style
+
Important style
+

+ Multiple styles +

+ +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_transition_modifiers.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/directive_transition_modifiers.svelte new file mode 100644 index 000000000000..4454278503cd --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_transition_modifiers.svelte @@ -0,0 +1,3 @@ +
Content
+

Global transition

+Complex diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_transition_modifiers.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/directive_transition_modifiers.svelte.snap new file mode 100644 index 000000000000..fbc44e2e8135 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_transition_modifiers.svelte.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/directive_transition_modifiers.svelte +--- + +# Input + +```svelte +
Content
+

Global transition

+Complex + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte +
Content
+

Global transition

+Complex + +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_use_basic.svelte b/crates/biome_html_formatter/tests/specs/html/svelte/directive_use_basic.svelte new file mode 100644 index 000000000000..cf00110a770c --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_use_basic.svelte @@ -0,0 +1,2 @@ +
Click outside handler
+ diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/directive_use_basic.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/directive_use_basic.svelte.snap new file mode 100644 index 000000000000..9c25828a1c6c --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/svelte/directive_use_basic.svelte.snap @@ -0,0 +1,38 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: svelte/directive_use_basic.svelte +--- + +# Input + +```svelte +
Click outside handler
+ + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```svelte +
Click outside handler
+ + +``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap index 618b68817d29..60da7b4746b6 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_basic.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_basic.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#each items as item}
{item}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap index 7b368862e09a..4e2acc50b754 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_combinations.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_complex_combinations.svelte --- + # Input ```svelte @@ -38,6 +39,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -54,4 +56,5 @@ Self close void elements: never {#each products as product (product.sku)}
{product.title}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap index ca3bbc8f812f..f7a9209eea96 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_complex_expression.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_complex_expression.svelte --- + # Input ```svelte @@ -36,6 +37,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -50,4 +52,5 @@ Self close void elements: never {#each items.filter(x => x.active) as item}
{item}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap index 1db774eb961a..cb279d61c3a0 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_nested.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_nested.svelte --- + # Input ```svelte @@ -39,6 +40,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -52,6 +54,9 @@ Self close void elements: never {/each} {#each matrix as row} - {#each row as cell}{cell}{/each} + {#each row as cell} + {cell} + {/each} {/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_one_liner.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_one_liner.svelte.snap index 75ec2660afdf..225786a0850c 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_one_liner.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_one_liner.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_one_liner.svelte --- + # Input ```svelte @@ -26,6 +27,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -34,4 +36,5 @@ Self close void elements: never {:else}
Empty list
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap index a9bb8158c96e..d257d2f95886 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_destructuring.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_with_destructuring.svelte --- + # Input ```svelte @@ -40,6 +41,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -51,11 +53,12 @@ Self close void elements: never
{i}: {name} ({email})
{/each} -{#each products as [id, title]} +{#each products as [ id, title ]}
{id}: {title}
{/each} {#each items as { id, ...rest }}
{id}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap index 3be2c48ee4d7..95267d89b331 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_else.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_with_else.svelte --- + # Input ```svelte @@ -30,6 +31,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -38,4 +40,5 @@ Self close void elements: never {:else}
No items found
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap index 0f79f7799183..5f5ebf148aa5 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_with_index.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#each items as item, i}
{i}: {item}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap index 2ffcf0650203..cc4185ba40fc 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_index_and_key.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_with_index_and_key.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#each items as item, i (item.id)}
{i}: {item.name}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap index 3f879eef8f38..1e836314bdc6 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_key.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_with_key.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#each items as item (item.id)}
{item.name}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap index 84345a135f07..fa2cacdae9dc 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/each_with_whitespace.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/each_with_whitespace.svelte --- + # Input ```svelte @@ -36,6 +37,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -50,4 +52,5 @@ Self close void elements: never {#each items as item (item.id)}
{item}
{/each} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap index 9f8450071298..3ee050aa09ea 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/html.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/html.svelte --- + # Input ```svelte @@ -30,10 +31,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte
{@html content}
{@html '
'}content{@html '
'} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/if.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/if.svelte.snap index 06c626477d80..b60ff114fc13 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/if.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/if.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/if.svelte --- + # Input ```svelte @@ -29,10 +30,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#if answer === 42}

what was the question?

{/if} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/if_else.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/if_else.svelte.snap index 1a4431589c96..03309520104d 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/if_else.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/if_else.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/if_else.svelte --- + # Input ```svelte @@ -31,6 +32,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -40,4 +42,5 @@ Self close void elements: never {:else}

too cold!

{/if} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/if_else_if_else.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/if_else_if_else.svelte.snap index e511f0bbee49..ef86651501ba 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/if_else_if_else.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/if_else_if_else.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/if_else_if_else.svelte --- + # Input ```svelte @@ -35,6 +36,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -48,4 +50,5 @@ Self close void elements: never {:else}

just right!

{/if} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/if_nested.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/if_nested.svelte.snap index 63865657cb0f..a895a9978ebf 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/if_nested.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/if_nested.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/if_nested.svelte --- + # Input ```svelte @@ -34,6 +35,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -46,4 +48,5 @@ Self close void elements: never {/if} {/if} {/if} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/issue_8515.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/issue_8515.svelte.snap index 72c0d9319fc6..b6560c0a62af 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/issue_8515.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/issue_8515.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/issue_8515.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#snippet Fallback()} {page.value} {/snippet} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/key.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/key.svelte.snap index bd1b12f96932..6d64078d323f 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/key.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/key.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/key.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#key expression}
{/key} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap index 571e953fd65c..f68ddec79d69 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/render.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/render.svelte --- + # Input ```svelte @@ -26,8 +27,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {@render sum(1, 2)} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/snippet_basic.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/snippet_basic.svelte.snap index ad2da63617b6..1308b126abfc 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/snippet_basic.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/snippet_basic.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/snippet_basic.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#snippet greeting()}

Hello!

{/snippet} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/snippet_nested.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/snippet_nested.svelte.snap index 622bfaa5af7f..ae89c1b446f9 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/snippet_nested.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/snippet_nested.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/snippet_nested.svelte --- + # Input ```svelte @@ -32,6 +33,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -42,4 +44,5 @@ Self close void elements: never {/snippet} {/snippet} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/snippet_with_whitespace.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/snippet_with_whitespace.svelte.snap index 695b4610879c..091dab112656 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/snippet_with_whitespace.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/snippet_with_whitespace.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/snippet_with_whitespace.svelte --- + # Input ```svelte @@ -28,10 +29,12 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte {#snippet greeting( name )}

Hello, {name}!

{/snippet} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584-w-newline.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584-w-newline.svelte.snap index 1d3e82ed8645..8a7eeda579f5 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584-w-newline.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584-w-newline.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/whitespace/issue-8584-w-newline.svelte --- + # Input ```svelte @@ -33,6 +34,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -44,6 +46,7 @@ Self close void elements: never Hello, {framework} and Svelte! + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584.svelte.snap b/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584.svelte.snap index 5a6a5dca609e..c01bf0ee3e3f 100644 --- a/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584.svelte.snap +++ b/crates/biome_html_formatter/tests/specs/html/svelte/whitespace/issue-8584.svelte.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: svelte/whitespace/issue-8584.svelte --- + # Input ```svelte @@ -30,6 +31,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```svelte @@ -38,6 +40,7 @@ Self close void elements: never

Hello, {framework} and Svelte!

+ ``` diff --git a/crates/biome_html_formatter/tests/specs/html/text_expressions/expressions.vue.snap b/crates/biome_html_formatter/tests/specs/html/text_expressions/expressions.vue.snap index c7c1c5ff7250..4b832a4434af 100644 --- a/crates/biome_html_formatter/tests/specs/html/text_expressions/expressions.vue.snap +++ b/crates/biome_html_formatter/tests/specs/html/text_expressions/expressions.vue.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: text_expressions/expressions.vue --- + # Input ```vue @@ -31,6 +32,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```vue @@ -39,4 +41,5 @@ Self close void elements: never return "hello" } }} + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/trailing_newline/options.json b/crates/biome_html_formatter/tests/specs/html/trailing_newline/options.json new file mode 100644 index 000000000000..93b06e563c36 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/trailing_newline/options.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../../../../../packages/@biomejs/biome/configuration_schema.json", + "html": { + "formatter": { + "trailingNewline": false + } + } +} diff --git a/crates/biome_html_formatter/tests/specs/html/trailing_newline/simple.html b/crates/biome_html_formatter/tests/specs/html/trailing_newline/simple.html new file mode 100644 index 000000000000..c6d5a17c02e8 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/trailing_newline/simple.html @@ -0,0 +1,9 @@ + + + + Test + + +

Hello

+ + diff --git a/crates/biome_html_formatter/tests/specs/html/trailing_newline/simple.html.snap b/crates/biome_html_formatter/tests/specs/html/trailing_newline/simple.html.snap new file mode 100644 index 000000000000..a54b233e5cba --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/trailing_newline/simple.html.snap @@ -0,0 +1,79 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: trailing_newline/simple.html +--- + +# Input + +```html + + + + Test + + +

Hello

+ + + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```html + + + + Test + + +

Hello

+ + + +``` + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: false +----- + +```html + + + + Test + + +

Hello

+ + +``` diff --git a/crates/biome_html_formatter/tests/specs/html/vue/component.vue.snap b/crates/biome_html_formatter/tests/specs/html/vue/component.vue.snap index 65b51841ec3e..9e05afd82d26 100644 --- a/crates/biome_html_formatter/tests/specs/html/vue/component.vue.snap +++ b/crates/biome_html_formatter/tests/specs/html/vue/component.vue.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: vue/component.vue --- + # Input ```vue @@ -27,8 +28,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```vue - + + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap b/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap index b510d61418fd..d775e3fd8e51 100644 --- a/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap +++ b/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: vue/event-with-colon.vue --- + # Input ```vue @@ -26,8 +27,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```vue + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/vue/issue-8174.vue.snap b/crates/biome_html_formatter/tests/specs/html/vue/issue-8174.vue.snap index 05b5f9313a50..1393c4e37bc5 100644 --- a/crates/biome_html_formatter/tests/specs/html/vue/issue-8174.vue.snap +++ b/crates/biome_html_formatter/tests/specs/html/vue/issue-8174.vue.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: vue/issue-8174.vue --- + # Input ```vue @@ -27,9 +28,11 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```vue + ``` diff --git a/crates/biome_html_formatter/tests/specs/html/whitespace/force-break-nontext-and-non-sensitive-sibling.html b/crates/biome_html_formatter/tests/specs/html/whitespace/force-break-nontext-and-non-sensitive-sibling.html new file mode 100644 index 000000000000..5f55ad19fb2f --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/whitespace/force-break-nontext-and-non-sensitive-sibling.html @@ -0,0 +1 @@ +
a
b
c
diff --git a/crates/biome_html_formatter/tests/specs/html/whitespace/force-break-nontext-and-non-sensitive-sibling.html.snap b/crates/biome_html_formatter/tests/specs/html/whitespace/force-break-nontext-and-non-sensitive-sibling.html.snap new file mode 100644 index 000000000000..b81f9b0681d1 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/whitespace/force-break-nontext-and-non-sensitive-sibling.html.snap @@ -0,0 +1,40 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: whitespace/force-break-nontext-and-non-sensitive-sibling.html +--- + +# Input + +```html +
a
b
c
+ +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```html +
+ a +
b
+ c +
+ +``` diff --git a/crates/biome_html_formatter/tests/specs/html/whitespace/no-break-display-none.html b/crates/biome_html_formatter/tests/specs/html/whitespace/no-break-display-none.html new file mode 100644 index 000000000000..f0012d0730a8 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/whitespace/no-break-display-none.html @@ -0,0 +1 @@ +
123456
diff --git a/crates/biome_html_formatter/tests/specs/html/whitespace/no-break-display-none.html.snap b/crates/biome_html_formatter/tests/specs/html/whitespace/no-break-display-none.html.snap new file mode 100644 index 000000000000..c83aff9a4ac2 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/whitespace/no-break-display-none.html.snap @@ -0,0 +1,36 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: whitespace/no-break-display-none.html +--- + +# Input + +```html +
123456
+ +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +Trailing newline: true +----- + +```html +
123456
+ +``` diff --git a/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-newline-after-element.html.snap b/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-newline-after-element.html.snap index 02f7d5e52aaf..70b3a9f20039 100644 --- a/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-newline-after-element.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-newline-after-element.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: whitespace/preserve-newline-after-element.html --- + # Input ```html @@ -29,6 +30,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -36,4 +38,5 @@ Self close void elements: never Foo Bar

+ ``` diff --git a/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-space-after-element.html.snap b/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-space-after-element.html.snap index 0d4f732e9127..cbbf2c56e844 100644 --- a/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-space-after-element.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/whitespace/preserve-space-after-element.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: whitespace/preserve-space-after-element.html --- + # Input ```html @@ -28,8 +29,10 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html

Foo Bar

+ ``` diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/attributes/attributes.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/attributes/attributes.html.snap index 0f2884e263d9..85ed1363941d 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/attributes/attributes.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/attributes/attributes.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/attributes/attributes.html --- + # Input ```html @@ -90,19 +91,11 @@ and HTML5 Apps. It also documents Mozilla products, like Firefox OS."> ```diff --- Prettier +++ Biome -@@ -61,8 +61,14 @@ - data-index-number="12314" - data-parent="cars" - > -- -- -- -- +@@ -65,4 +65,10 @@ + + + -

-+ -+ -+ -+ +

- - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -1,3 +1 @@ -- -+ -``` - -# Output - -```html - -``` diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/basics/comment.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/basics/comment.html.snap index dff88a3b2bcf..ce70743560b8 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/basics/comment.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/basics/comment.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/basics/comment.html --- + # Input ```html diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/basics/hello-world.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/basics/hello-world.html.snap index 63f73fbe14db..7463bedabd56 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/basics/hello-world.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/basics/hello-world.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/basics/hello-world.html --- + # Input ```html diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/basics/html5-boilerplate.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/basics/html5-boilerplate.html.snap index 455cf2a70b1d..54de48ec1d7a 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/basics/html5-boilerplate.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/basics/html5-boilerplate.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/basics/html5-boilerplate.html --- + # Input ```html diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/basics/void-elements-2.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/basics/void-elements-2.html.snap index df5b02c0ed05..bf13b98abf74 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/basics/void-elements-2.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/basics/void-elements-2.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/basics/void-elements-2.html --- + # Input ```html @@ -29,14 +30,13 @@ info: html/basics/void-elements-2.html ```diff --- Prettier +++ Biome -@@ -1,12 +1,16 @@ +@@ -1,12 +1,12 @@ text after + -+ -+text after ++text after @@ -46,11 +46,7 @@ info: html/basics/void-elements-2.html + +1 --1 -+ -+ 1 + 1 ``` # Output @@ -59,8 +55,7 @@ info: html/basics/void-elements-2.html -text after +text after @@ -68,8 +63,5 @@ text after 1 - - 1 +1 ``` diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/basics/with-colon.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/basics/with-colon.html.snap index 2de68aaa57a5..86298e49d6e2 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/basics/with-colon.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/basics/with-colon.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/basics/with-colon.html --- + # Input ```html @@ -141,15 +142,10 @@ e-wrap ```diff --- Prettier +++ Biome -@@ -4,38 +4,37 @@ -

- looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block -
--
block
--
BLOCK
--
block
--
block
--
block
+@@ -9,14 +9,9 @@ +
block
+
block
+
block
-
 - pre pr
 -e
- pre-wrap pr -e-wrap -+
block
-+
BLOCK
-+
block
-+
block
-+
block
+
 pre pr
 +e
-+ ++ looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline -- inline inline inline inline -+ inline inline -+ inline inline - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block - -- block BLOCK -- block block block -- pre pr e -- pre-wrap pr e-wrap -+ block -+ BLOCK -+ block -+ block -+ block -+ pre pr e -+ pre-wrap pr e-wrap - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline - -- inline inline -- inline inline -+ inline -+ inline -+ inline -+ inline -+ - - - -@@ -44,38 +43,37 @@ -
- looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block -
--
block
--
BLOCK
--
block
--
block
--
block
+@@ -49,14 +44,9 @@ +
block
+
block
+
block
-
 - pre pr
 -e
- pre-wrap pr -e-wrap -+
block
-+
BLOCK
-+
block
-+
block
-+
block
+
 pre pr
 +e
-+ ++ looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline -- inline inline inline inline -+ inline inline -+ inline inline - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block - -- block BLOCK -- block block block -- pre pr e -- pre-wrap pr e-wrap -+ block -+ BLOCK -+ block -+ block -+ block -+ pre pr e -+ pre-wrap pr e-wrap - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline - -- inline inline -- inline inline -+ inline -+ inline -+ inline -+ inline -+ - - - -@@ -84,38 +82,37 @@ -
- looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block -
--
block
--
BLOCK
--
block
--
block
--
block
+@@ -89,14 +79,9 @@ +
block
+
block
+
block
-
 - pre pr
 -e
- pre-wrap pr -e-wrap -+
block
-+
BLOCK
-+
block
-+
block
-+
block
+
 pre pr
 +e
-+ ++ looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline -- inline inline inline inline -+ inline inline -+ inline inline - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block - -- block BLOCK -- block block block -- pre pr e -- pre-wrap pr e-wrap -+ block -+ BLOCK -+ block -+ block -+ block -+ pre pr e -+ pre-wrap pr e-wrap - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline - -- inline inline -- inline inline -+ inline -+ inline -+ inline -+ inline -+ - - - -@@ -124,38 +121,37 @@ -
- looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block -
--
block
--
BLOCK
--
block
--
block
--
block
+@@ -129,14 +114,9 @@ +
block
+
block
+
block
-
 - pre pr
 -e
- pre-wrap pr -e-wrap -+
block
-+
BLOCK
-+
block
-+
block
-+
block
+
 pre pr
 +e
-+ ++ looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline -- inline inline inline inline -+ inline inline -+ inline inline - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block - -- block BLOCK -- block block block -- pre pr e -- pre-wrap pr e-wrap -+ block -+ BLOCK -+ block -+ block -+ block -+ pre pr e -+ pre-wrap pr e-wrap - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline - -- inline inline -- inline inline -+ inline -+ inline -+ inline -+ inline -+ - - - -@@ -164,37 +160,36 @@ -
- looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block -
--
block
--
BLOCK
--
block
--
block
--
block
+@@ -169,14 +149,9 @@ +
block
+
block
+
block
-
 - pre pr
 -e
- pre-wrap pr -e-wrap -+
block
-+
BLOCK
-+
block
-+
block
-+
block
+
 pre pr
 +e
-+ ++ looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline -- inline inline inline inline -+ inline inline -+ inline inline - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog block - -- block BLOCK -- block block block -- pre pr e -- pre-wrap pr e-wrap -+ block -+ BLOCK -+ block -+ block -+ block -+ pre pr e -+ pre-wrap pr e-wrap - - looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog inline - -- inline inline -- inline inline -+ inline -+ inline -+ inline -+ inline - - - -@@ -206,53 +201,29 @@ +@@ -206,53 +181,25 @@ - - const func = function() { console.log('Hello, there');} - + const func = function() { console.log('Hello, there');} .a { color:#f00} - - - - - - + + - - const func = function() { console.log('Hello, there');} - +const func = function() { console.log('Hello, there');} .a { color:#f00} ``` diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/embed.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/embed.html.snap index 5635b6a01b5b..cd3ac7a025b9 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/embed.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/embed.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/bracket-same-line/embed.html --- + # Input ```html diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/inline.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/inline.html.snap deleted file mode 100644 index 56b7c7d1791d..000000000000 --- a/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/inline.html.snap +++ /dev/null @@ -1,80 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: html/bracket-same-line/inline.html ---- -# Input - -```html - - -text - - -text - -text - -text -text - -texttexttexttexttext - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -12,11 +12,11 @@ - > - text - --texttext - text - --texttexttexttexttexttexttexttext -``` - -# Output - -```html - - text - - -text - - text - -text - text - -texttexttexttexttext -``` - -# Lines exceeding max width of 80 characters -``` - 2: long_long_attribute="long_long_long_long_long_long_long_long_long_long_long_value" - 7: long_long_attribute="long_long_long_long_long_long_long_long_long_long_long_value" - 11: long_long_attribute="long_long_long_long_long_long_long_long_long_long_long_value" - 16: long_long_attribute="long_long_long_long_long_long_long_long_long_long_long_value" -``` diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/void-elements.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/void-elements.html.snap index 6bcd36284824..561a7a5d91e4 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/void-elements.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/bracket-same-line/void-elements.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/bracket-same-line/void-elements.html --- + # Input ```html @@ -16,18 +17,15 @@ info: html/bracket-same-line/void-elements.html ```diff --- Prettier +++ Biome -@@ -2,6 +2,8 @@ +@@ -2,6 +2,5 @@ long_long_attribute="long_long_long_long_long_long_long_long_long_long_long_value" src="./1.jpg" /> - -+ -+ -+ -+ -+ ++ ++ ``` # Output @@ -37,11 +35,8 @@ info: html/bracket-same-line/void-elements.html long_long_attribute="long_long_long_long_long_long_long_long_long_long_long_value" src="./1.jpg" /> - - - - - + + ``` # Lines exceeding max width of 80 characters diff --git a/crates/biome_html_formatter/tests/specs/prettier/html/case/case.html.snap b/crates/biome_html_formatter/tests/specs/prettier/html/case/case.html.snap index e682cba9f41c..26502e79f112 100644 --- a/crates/biome_html_formatter/tests/specs/prettier/html/case/case.html.snap +++ b/crates/biome_html_formatter/tests/specs/prettier/html/case/case.html.snap @@ -2,6 +2,7 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: html/case/case.html --- + # Input ```html @@ -36,14 +37,8 @@ info: html/case/case.html -@@ -7,17 +7,12 @@ - - -

-- Hello world!
-- This is HTML5 Boilerplate. -+ Hello world! -+
This is HTML5 Boilerplate. +@@ -11,13 +11,8 @@ + This is HTML5 Boilerplate.

", + Type1Tag::Pre => "", + Type1Tag::Style => "", + Type1Tag::Textarea => "", + }; + parse_until_terminator(p, terminator, true); + } + HtmlBlockKind::Type2 => parse_until_terminator(p, "-->", false), + HtmlBlockKind::Type3 => parse_until_terminator(p, "?>", false), + HtmlBlockKind::Type4 => parse_until_terminator(p, ">", false), + HtmlBlockKind::Type5 => parse_until_terminator(p, "]]>", false), + HtmlBlockKind::Type6 | HtmlBlockKind::Type7 => parse_until_blank_line(p), + } + + content_m.complete(p, MD_INLINE_ITEM_LIST); + Present(m.complete(p, MD_HTML_BLOCK)) +} + +fn parse_until_blank_line(p: &mut MarkdownParser) { + while !p.at(EOF) { + if p.at(NEWLINE) { + if p.at_blank_line() { + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + break; + } + // Consume the newline first, then check if the next line exits the container + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + + if at_container_boundary(p) { + break; + } + skip_container_prefixes(p); + continue; + } + + // For non-newline tokens, check container boundary (handles virtual line start) + if at_container_boundary(p) { + break; + } + + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + } +} + +fn parse_until_terminator(p: &mut MarkdownParser, terminator: &str, case_insensitive: bool) { + let mut line = String::new(); + + while !p.at(EOF) { + let text = p.cur_text(); + let is_newline = p.at(NEWLINE); + line.push_str(text); + + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + + if is_newline { + if line_contains(&line, terminator, case_insensitive) { + break; + } + line.clear(); + // Check container boundary after consuming newline + if at_container_boundary(p) { + break; + } + skip_container_prefixes(p); + } + } +} + +fn line_contains(line: &str, needle: &str, case_insensitive: bool) -> bool { + if !case_insensitive { + return line.contains(needle); + } + + let hay = line.as_bytes(); + let needle = needle.as_bytes(); + if needle.is_empty() || hay.len() < needle.len() { + return false; + } + + for i in 0..=hay.len() - needle.len() { + if hay[i..i + needle.len()] + .iter() + .zip(needle.iter()) + .all(|(a, b)| a.eq_ignore_ascii_case(b)) + { + return true; + } + } + + false +} + +fn skip_container_prefixes(p: &mut MarkdownParser) { + let quote_depth = p.state().block_quote_depth; + if quote_depth > 0 && has_quote_prefix(p, quote_depth) { + consume_quote_prefix_without_virtual(p, quote_depth); + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + } + + let required_indent = p.state().list_item_required_indent; + if required_indent > 0 { + p.skip_line_indent(required_indent); + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + } +} + +fn at_container_boundary(p: &mut MarkdownParser) -> bool { + let quote_depth = p.state().block_quote_depth; + if quote_depth > 0 && p.at_line_start() && !has_quote_prefix(p, quote_depth) { + // Skip if at virtual line start — the quote prefix was already consumed + // by the container parser that set this virtual start position. + if p.state() + .virtual_line_start + .is_none_or(|vls| vls != p.cur_range().start()) + { + return true; + } + } + + let required_indent = p.state().list_item_required_indent; + if required_indent > 0 && p.at_line_start() { + let indent = p.line_start_leading_indent(); + if indent < required_indent { + return true; + } + } + + false +} diff --git a/crates/biome_markdown_parser/src/syntax/inline/code_span.rs b/crates/biome_markdown_parser/src/syntax/inline/code_span.rs new file mode 100644 index 000000000000..64a8dfe01962 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/inline/code_span.rs @@ -0,0 +1,240 @@ +use biome_markdown_syntax::T; +use biome_markdown_syntax::kind::MarkdownSyntaxKind::*; +use biome_parser::Parser; +use biome_parser::prelude::ParsedSyntax::{self, *}; + +use crate::MarkdownParser; +use crate::lexer::MarkdownLexContext; +use crate::syntax::{at_block_interrupt, at_setext_underline_after_newline}; + +/// Parse a hard line break. +/// +/// Grammar: MdHardLine = value: 'md_hard_line_literal' +/// +/// A hard line break is created by either: +/// - Two or more trailing spaces followed by a newline +/// - A backslash followed by a newline +pub(crate) fn parse_hard_line(p: &mut MarkdownParser) -> ParsedSyntax { + if !p.at(MD_HARD_LINE_LITERAL) { + return Absent; + } + + let m = p.start(); + p.bump(MD_HARD_LINE_LITERAL); + Present(m.complete(p, MD_HARD_LINE)) +} + +/// Check if there's a matching closing backtick sequence before EOF/blank line. +/// +/// Per CommonMark §6.1, a code span opener must have a matching closer with the +/// same number of backticks. If no match exists, the opener should be treated +/// as literal text, not an unclosed code span. +/// +/// Returns false if no match found (opener should become literal text). +fn has_matching_code_span_closer(p: &mut MarkdownParser, opening_count: usize) -> bool { + p.lookahead(|p| { + // Skip the opening backticks (handle both BACKTICK and TRIPLE_BACKTICK) + if p.at(T!["```"]) { + p.bump(T!["```"]); + } else { + p.bump(BACKTICK); + } + + loop { + // EOF = no matching closer found + if p.at(T![EOF]) { + return false; + } + + // Blank line = paragraph boundary, terminates search + if p.at(NEWLINE) && p.at_blank_line() { + return false; + } + + // Per CommonMark §4.3, setext heading underlines take priority over + // inline code spans. If crossing a newline would land on a setext + // underline, the code span is invalid — the underline forms a heading. + if p.at(NEWLINE) { + p.bump_remap_with_context(MD_TEXTUAL_LITERAL, MarkdownLexContext::CodeSpan); + if at_setext_underline_after_newline(p).is_some() { + return false; + } + // Per CommonMark, block interrupts (including list markers) can + // terminate paragraphs. A code span cannot cross a block boundary. + if at_block_interrupt(p) || is_at_list_marker_after_newline(p) { + return false; + } + continue; + } + + // Found backticks - check if they match (handle both BACKTICK and TRIPLE_BACKTICK) + if p.at(BACKTICK) || p.at(T!["```"]) { + let closing_count = p.cur_text().len(); + if closing_count == opening_count { + return true; + } + // Not matching - continue searching + if p.at(T!["```"]) { + p.bump(T!["```"]); + } else { + p.bump(BACKTICK); + } + continue; + } + + // Consume token and continue (use CodeSpan context for proper backslash handling) + p.bump_remap_with_context(MD_TEXTUAL_LITERAL, MarkdownLexContext::CodeSpan); + } + }) +} + +/// Check if we're at a list marker after a newline. +/// This is used to detect when a code span would cross a list item boundary. +fn is_at_list_marker_after_newline(p: &mut MarkdownParser) -> bool { + // List markers can be indented up to 3 spaces; 4+ means indented code block. + const LIST_MARKER_MAX_INDENT: usize = 4; + + // Skip up to 3 spaces of indent (list markers can be indented 0-3 spaces) + let mut columns = 0usize; + while columns < LIST_MARKER_MAX_INDENT + && p.at(MD_TEXTUAL_LITERAL) + && p.cur_text().chars().all(|c| c == ' ' || c == '\t') + { + for c in p.cur_text().chars() { + match c { + ' ' => columns += 1, + '\t' => columns += 4 - (columns % 4), + _ => {} + } + } + if columns >= LIST_MARKER_MAX_INDENT { + return false; // Indented code block, not a list marker + } + p.bump(MD_TEXTUAL_LITERAL); + } + + // Check for bullet list markers: -, *, + + if p.at(T![-]) || p.at(T![*]) || p.at(T![+]) { + let marker_text = p.cur_text(); + // Only a single -, *, or + is a list marker; longer runs are not. + if marker_text.len() == 1 { + p.bump_any(); + return is_list_marker_followed_by_space_or_eol(p); + } + return false; + } + + // Check for ordered list marker: digits followed by . or ) + if p.at(MD_ORDERED_LIST_MARKER) { + p.bump(MD_ORDERED_LIST_MARKER); + return is_list_marker_followed_by_space_or_eol(p); + } + + // Check for textual bullet markers (lexed as MD_TEXTUAL_LITERAL in some contexts) + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == "-" || text == "*" || text == "+" { + p.bump(MD_TEXTUAL_LITERAL); + return is_list_marker_followed_by_space_or_eol(p); + } + } + + false +} + +/// A list marker must be followed by space, tab, or end of line/input. +fn is_list_marker_followed_by_space_or_eol(p: &MarkdownParser) -> bool { + if p.at(NEWLINE) || p.at(T![EOF]) { + return true; + } + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + return text.starts_with(' ') || text.starts_with('\t'); + } + false +} + +/// Parse inline code span (`` `code` `` or ``` `` `code` `` ```). +/// +/// Grammar: MdInlineCode = l_tick: '`' content: MdInlineItemList r_tick: '`' +/// +/// Per CommonMark §6.1: +/// - Code spans can use multiple backticks to allow literal backticks inside +/// - The opening and closing backtick strings must be the same length +/// - Backslash escapes are NOT processed inside code spans (\` is literal `\``) +/// - If no matching closer exists, the opener is treated as literal text +pub(crate) fn parse_inline_code(p: &mut MarkdownParser) -> ParsedSyntax { + // Handle both BACKTICK and TRIPLE_BACKTICK (T!["```"] ) as code span openers. + // TRIPLE_BACKTICK can appear when backticks are at line start but info string + // contains backticks, making it not a fenced code block (CommonMark examples 138, 145). + let is_backtick = p.at(BACKTICK); + let is_triple_backtick = p.at(T!["```"]); + if !is_backtick && !is_triple_backtick { + return Absent; + } + + let opening_count = p.cur_text().len(); + + // DESIGN PRINCIPLE #2 & #4: Check for matching closer BEFORE creating any nodes. + // If no match exists, return Absent so backticks become literal text. + // This avoids synthesizing MD_INLINE_CODE with missing r_tick_token. + if !has_matching_code_span_closer(p, opening_count) { + return Absent; // Caller will treat backtick as literal MD_TEXTUAL + } + + // We have a valid code span - now parse it + let m = p.start(); + + // Opening backtick(s) - remap TRIPLE_BACKTICK to BACKTICK for consistency + if is_triple_backtick { + p.bump_remap(BACKTICK); + } else { + p.bump(BACKTICK); + } + + // Content - parse until we find matching closing backticks + // Per CommonMark, code spans can span multiple lines (newlines become spaces in output) + // All content is lexed in CodeSpan context to keep backslash literal and avoid + // hard-line-break detection. + let content = p.start(); + loop { + // EOF should not happen (lookahead guaranteed a closer), but handle defensively + if p.at(T![EOF]) { + break; + } + + // DESIGN PRINCIPLE #3: Terminate on blank line (paragraph boundary) + if p.at(NEWLINE) { + if p.at_blank_line() { + break; // Paragraph boundary - stop + } + // Soft line break - consume NEWLINE as content and continue + // Use CodeSpan context so next token is also lexed without escape processing + let text_m = p.start(); + p.bump_remap_with_context(MD_TEXTUAL_LITERAL, MarkdownLexContext::CodeSpan); + text_m.complete(p, MD_TEXTUAL); + continue; + } + + // Found matching closing backticks (handle both BACKTICK and TRIPLE_BACKTICK) + if (p.at(BACKTICK) || p.at(T!["```"])) && p.cur_text().len() == opening_count { + break; + } + + // DESIGN PRINCIPLE #1: Use CodeSpan context so backslash is literal + let text_m = p.start(); + p.bump_remap_with_context(MD_TEXTUAL_LITERAL, MarkdownLexContext::CodeSpan); + text_m.complete(p, MD_TEXTUAL); + } + content.complete(p, MD_INLINE_ITEM_LIST); + + // Closing backticks (guaranteed to exist due to lookahead check) + // Remap TRIPLE_BACKTICK to BACKTICK for consistency + if p.at(T!["```"]) { + p.bump_remap(BACKTICK); + } else { + p.bump(BACKTICK); + } + + Present(m.complete(p, MD_INLINE_CODE)) +} diff --git a/crates/biome_markdown_parser/src/syntax/inline/emphasis.rs b/crates/biome_markdown_parser/src/syntax/inline/emphasis.rs new file mode 100644 index 000000000000..81ffef162965 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/inline/emphasis.rs @@ -0,0 +1,701 @@ +use biome_markdown_syntax::MarkdownSyntaxKind; +use biome_markdown_syntax::T; +use biome_markdown_syntax::kind::MarkdownSyntaxKind::*; +use biome_parser::Parser; +use biome_parser::prelude::ParsedSyntax::{self, *}; +use biome_unicode_table::is_unicode_punctuation; + +use crate::MarkdownParser; +use crate::syntax::parse_error::unclosed_emphasis; +use crate::syntax::reference::normalize_reference_label; + +// ============================================================================ +// Delimiter Stack Types for Emphasis Parsing +// ============================================================================ + +/// Kind of emphasis delimiter (* or _) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DelimKind { + Star, + Underscore, +} + +/// A delimiter run collected during the first pass +#[derive(Debug, Clone)] +struct DelimRun { + /// The delimiter character kind + kind: DelimKind, + /// Number of delimiter characters in this run + count: usize, + /// Whether this can open emphasis (left-flanking) + can_open: bool, + /// Whether this can close emphasis (right-flanking) + can_close: bool, + /// Byte offset in the source where this run starts + start_offset: usize, + /// Bracket nesting depth for scoping emphasis within link text. + /// Delimiters inside brackets (links) should only match with each other, + /// not with delimiters outside the brackets. 0 = outside brackets. + label_id: usize, +} + +/// A matched emphasis span (opener + closer) +#[derive(Debug, Clone)] +struct EmphasisMatch { + /// Byte offset where the opener delimiter starts + opener_start: usize, + /// Byte offset where the closer delimiter starts + closer_start: usize, + /// Whether this is strong (2 chars) or regular (1 char) emphasis + is_strong: bool, +} + +/// Check if a character is Unicode whitespace for flanking rules. +fn is_whitespace(c: char) -> bool { + c.is_whitespace() +} + +fn is_emphasis_marker(c: char) -> bool { + matches!(c, '*' | '_') +} + +/// Check if a character is Unicode punctuation for flanking rules. +/// Per CommonMark spec, this includes ASCII punctuation and Unicode punctuation categories. +fn is_punctuation(c: char) -> bool { + is_unicode_punctuation(c) +} + +fn backtick_run_len(bytes: &[u8], start: usize) -> usize { + let mut count = 1; + while start + count < bytes.len() && bytes[start + count] == b'`' { + count += 1; + } + count +} + +fn skip_code_span(bytes: &[u8], i: &mut usize) { + let backtick_count = backtick_run_len(bytes, *i); + *i += backtick_count; + + while *i < bytes.len() { + if bytes[*i] == b'`' { + let close_count = backtick_run_len(bytes, *i); + *i += close_count; + if close_count == backtick_count { + break; + } + } else { + *i += 1; + } + } +} + +fn skip_angle_bracket(bytes: &[u8], i: &mut usize) { + *i += 1; + while *i < bytes.len() && bytes[*i] != b'>' && bytes[*i] != b'\n' { + *i += 1; + } + if *i < bytes.len() && bytes[*i] == b'>' { + *i += 1; + } +} + +fn is_flanking_delimiter(primary: Option, secondary: Option) -> bool { + match primary { + None => false, // At start/end of input, can't be flanking + Some(c) if is_whitespace(c) => false, // Next to whitespace + Some(c) if is_emphasis_marker(c) => true, + Some(c) if is_punctuation(c) => { + // Only flanking if the other side is whitespace or punctuation + match secondary { + None => true, // Boundary counts as whitespace + Some(s) => is_whitespace(s) || is_punctuation(s), + } + } + Some(_) => true, // Not next to whitespace or punctuation = flanking + } +} + +/// Check if an opening delimiter is left-flanking per CommonMark rules. +/// A left-flanking delimiter run is one that is: +/// - Not followed by Unicode whitespace, AND +/// - Either (a) not followed by punctuation, OR (b) preceded by whitespace/punctuation +fn is_left_flanking_delimiter(char_after: Option, char_before: Option) -> bool { + is_flanking_delimiter(char_after, char_before) +} + +/// Check if a closing delimiter is right-flanking per CommonMark rules. +/// A right-flanking delimiter run is one that is: +/// - Not preceded by Unicode whitespace, AND +/// - Either (a) not preceded by punctuation, OR (b) followed by whitespace/punctuation +fn is_right_flanking_delimiter(char_before: Option, char_after: Option) -> bool { + is_flanking_delimiter(char_before, char_after) +} + +/// Check if underscore can open emphasis (stricter rules than asterisk). +/// Per CommonMark 6.2, underscore can open emphasis iff it is left-flanking AND either: +/// - Not part of a right-flanking delimiter run, OR +/// - Preceded by a punctuation character +fn can_underscore_open(char_before: Option, char_after: Option) -> bool { + // Must be left-flanking + if !is_left_flanking_delimiter(char_after, char_before) { + return false; + } + // If also right-flanking, must be preceded by punctuation + if is_right_flanking_delimiter(char_before, char_after) { + return matches!(char_before, Some(c) if is_punctuation(c)); + } + true +} + +/// Check if underscore can close emphasis (stricter rules than asterisk). +/// Per CommonMark 6.2, underscore can close emphasis iff it is right-flanking AND either: +/// - Not part of a left-flanking delimiter run, OR +/// - Followed by a punctuation character +fn can_underscore_close(char_before: Option, char_after: Option) -> bool { + // Must be right-flanking + if !is_right_flanking_delimiter(char_before, char_after) { + return false; + } + // If also left-flanking, must be followed by punctuation + if is_left_flanking_delimiter(char_after, char_before) { + return matches!(char_after, Some(c) if is_punctuation(c)); + } + true +} + +// ============================================================================ +// Delimiter Stack Algorithm Implementation +// ============================================================================ + +/// Collect all delimiter runs from source text. +/// +/// This is the first pass of the CommonMark emphasis algorithm. It scans +/// the source text and identifies all potential delimiter runs (sequences +/// of `*` or `_`), computing their flanking status. +/// Result of checking if a bracket forms a valid link. +/// Contains the closing bracket position if found. +struct BracketCheckResult { + /// Position of the closing `]` (or 0 if not found) + close_pos: usize, + /// Whether this is a valid inline link `[...](` or full reference `[...][]` + is_inline_or_full_ref: bool, +} + +/// Check if a bracket at position `start` forms a valid link pattern. +/// Returns the closing bracket position and whether it's an inline link or full reference. +fn check_bracket_pattern(bytes: &[u8], start: usize) -> Option { + if start >= bytes.len() || bytes[start] != b'[' { + return None; + } + + // Find matching ] with proper nesting + let mut depth = 1; + let mut i = start + 1; + while i < bytes.len() && depth > 0 { + match bytes[i] { + b'[' => depth += 1, + b']' => depth -= 1, + b'\\' if i + 1 < bytes.len() => i += 1, // Skip escaped char + b'`' => { + // Skip code spans + skip_code_span(bytes, &mut i); + continue; + } + b'<' => { + // Skip potential HTML/autolinks + skip_angle_bracket(bytes, &mut i); + continue; + } + _ => {} + } + i += 1; + } + + if depth != 0 { + return None; + } + + // i now points to position after `]` + let close_pos = i - 1; + let is_inline_or_full_ref = i < bytes.len() && (bytes[i] == b'(' || bytes[i] == b'['); + + Some(BracketCheckResult { + close_pos, + is_inline_or_full_ref, + }) +} + +/// Extract label text from a bracket pattern for reference lookup. +fn extract_label_text(source: &str, start: usize, close_pos: usize) -> &str { + if start < close_pos && close_pos <= source.len() { + &source[start + 1..close_pos] + } else { + "" + } +} + +fn collect_delimiter_runs(source: &str, reference_checker: impl Fn(&str) -> bool) -> Vec { + let mut runs = Vec::new(); + let bytes = source.as_bytes(); + let mut i = 0; + + // Pre-compute valid link bracket positions. + // A bracket is considered a valid link if: + // 1. It's followed by `(` (inline link) or `[` (full reference), OR + // 2. It's a shortcut reference with a defined reference (checked via reference_checker) + let mut link_bracket_starts = Vec::new(); + for pos in 0..bytes.len() { + if bytes[pos] == b'[' + && let Some(result) = check_bracket_pattern(bytes, pos) + { + if result.is_inline_or_full_ref { + // Inline link or full reference link + link_bracket_starts.push(pos); + } else { + // Could be a shortcut reference - check if definition exists + let label = extract_label_text(source, pos, result.close_pos); + let normalized = normalize_reference_label(label); + if !normalized.is_empty() && reference_checker(normalized.as_ref()) { + link_bracket_starts.push(pos); + } + } + } + } + + // Track bracket depth, but only for valid link brackets + let mut bracket_depth = 0usize; + let mut active_link_brackets: Vec = Vec::new(); + + while i < bytes.len() { + let b = bytes[i]; + + // Track bracket depth for valid links only + if b == b'[' && link_bracket_starts.contains(&i) { + bracket_depth += 1; + active_link_brackets.push(i); + i += 1; + continue; + } + if b == b']' && !active_link_brackets.is_empty() { + bracket_depth = bracket_depth.saturating_sub(1); + active_link_brackets.pop(); + i += 1; + continue; + } + + // Check for delimiter characters + if b == b'*' || b == b'_' { + let kind = if b == b'*' { + DelimKind::Star + } else { + DelimKind::Underscore + }; + let start_offset = i; + + // Count consecutive delimiter characters + let mut count = 1; + while i + count < bytes.len() && bytes[i + count] == b { + count += 1; + } + let end_offset = i + count; + + // Get character before delimiter run + let char_before = if start_offset > 0 { + // Get the char ending at start_offset + let before_slice = &source[..start_offset]; + before_slice.chars().next_back() + } else { + None + }; + + // Get character after delimiter run + let char_after = source[end_offset..].chars().next(); + + // Compute flanking status + let (can_open, can_close) = if kind == DelimKind::Underscore { + ( + can_underscore_open(char_before, char_after), + can_underscore_close(char_before, char_after), + ) + } else { + // Asterisk: can open if left-flanking, can close if right-flanking + ( + is_left_flanking_delimiter(char_after, char_before), + is_right_flanking_delimiter(char_before, char_after), + ) + }; + + runs.push(DelimRun { + kind, + count, + can_open, + can_close, + start_offset, + // Only scope by bracket depth when inside a valid link pattern. + // This prevents emphasis from spanning link boundaries, but allows + // emphasis to span brackets that don't form valid links. + label_id: bracket_depth, + }); + + i = end_offset; + } else if b == b'`' { + // Skip code spans - they block emphasis + skip_code_span(bytes, &mut i); + } else if b == b'<' { + // Skip potential HTML tags and autolinks + skip_angle_bracket(bytes, &mut i); + } else if b == b'\\' && i + 1 < bytes.len() { + // Skip escaped characters + i += 2; + } else { + i += 1; + } + } + + runs +} + +/// Match delimiter runs using the CommonMark algorithm. +/// +/// This is the second pass. It processes closers from left to right, +/// searching backward for matching openers. Returns a list of matched +/// emphasis spans sorted by opener position. +fn match_delimiters(runs: &mut [DelimRun]) -> Vec { + let mut matches = Vec::new(); + let mut opener_stack: Vec = Vec::new(); + + for idx in 0..runs.len() { + if runs[idx].can_close && runs[idx].count > 0 { + loop { + let mut opener_stack_pos = None; + + // Search backward for the closest matching opener. + // Per CommonMark spec, we find any matching opener first, + // then determine strong vs regular based on both counts. + for (pos, &opener_idx) in opener_stack.iter().enumerate().rev() { + let opener = &runs[opener_idx]; + let closer = &runs[idx]; + + // Only match within same bracket scope (label_id). + // This prevents emphasis from spanning link boundaries. + if opener.label_id != closer.label_id { + continue; + } + + if opener.kind != closer.kind || !opener.can_open || opener.count == 0 { + continue; + } + + // Rule of 3: if (opener_count + closer_count) % 3 == 0 and + // the closer can open or the opener can close, skip unless + // both counts are divisible by 3 + let opener_count = opener.count; + let closer_count = closer.count; + if (opener.can_close || closer.can_open) + && !closer_count.is_multiple_of(3) + && (opener_count + closer_count).is_multiple_of(3) + { + continue; + } + + opener_stack_pos = Some(pos); + break; + } + + let Some(pos) = opener_stack_pos else { break }; + let opener_idx = opener_stack[pos]; + let use_count = if runs[opener_idx].count >= 2 && runs[idx].count >= 2 { + 2 + } else { + 1 + }; + + // Openers consume from END of run (leftover stays at beginning). + // This ensures for `***foo***`, the inner `**` is consumed leaving `*` at start. + let opener_start = + runs[opener_idx].start_offset + runs[opener_idx].count - use_count; + // Closers consume from BEGINNING of what remains. + let closer_start = runs[idx].start_offset; + + matches.push(EmphasisMatch { + opener_start, + closer_start, + is_strong: use_count == 2, + }); + + // Opener: reduce count but keep start_offset (leftover is at beginning) + runs[opener_idx].count -= use_count; + // Closer: reduce count and advance start_offset (leftover is at end) + runs[idx].count -= use_count; + runs[idx].start_offset += use_count; + + // Remove openers between the matched opener and this closer. + opener_stack.truncate(pos + 1); + if runs[opener_idx].count == 0 { + opener_stack.pop(); + } + + // Note: With the "consume from END" algorithm for openers, + // crossing matches are no longer an issue because the leftover + // chars end up at the beginning of the opener run (wrapping + // around the inner match), not at the end (which would cross). + + if runs[idx].count == 0 { + break; + } + } + } + + if runs[idx].can_open && runs[idx].count > 0 { + opener_stack.push(idx); + } + } + + // Sort matches by opener position for nested processing + matches.sort_by_key(|m| m.opener_start); + + matches +} + +/// Context for emphasis-aware inline parsing +#[derive(Debug)] +pub(crate) struct EmphasisContext { + /// Matched emphasis spans, sorted by opener_start + matches: Vec, + /// Base offset of the inline content in the source + base_offset: usize, +} + +/// Information about a match found within a token's range. +/// Used when the opener doesn't start at the exact token boundary. +#[derive(Debug)] +struct OpenerMatch<'a> { + /// The matched emphasis span + matched: &'a EmphasisMatch, + /// How many chars before opener_start (literal prefix to emit) + prefix_len: usize, +} + +impl EmphasisContext { + /// Create a new emphasis context by analyzing the source text. + /// The reference_checker function is used to determine if a bracket pattern + /// is a valid shortcut reference link. + pub(crate) fn new( + source: &str, + base_offset: usize, + reference_checker: impl Fn(&str) -> bool, + ) -> Self { + let mut runs = collect_delimiter_runs(source, reference_checker); + let matches = match_delimiters(&mut runs); + Self { + matches, + base_offset, + } + } + + /// Find the *earliest* match whose opener_start is within [token_start, token_end) + /// and matches the expected `is_strong` value. + /// Returns None if no match found, or the match plus prefix length. + /// + /// This is used instead of exact offset matching because with the "consume from END" + /// algorithm, an opener might start in the middle of a DOUBLE_STAR token. + fn opener_within( + &self, + token_start: usize, + token_len: usize, + expect_strong: bool, + ) -> Option> { + let token_end = token_start + token_len; + let mut best: Option> = None; + + for m in &self.matches { + // Filter by expected emphasis type + if m.is_strong != expect_strong { + continue; + } + + let abs_opener = m.opener_start + self.base_offset; + if abs_opener >= token_start && abs_opener < token_end { + let candidate = OpenerMatch { + matched: m, + prefix_len: abs_opener - token_start, + }; + // Pick the earliest match (smallest prefix_len) + if best + .as_ref() + .is_none_or(|b| candidate.prefix_len < b.prefix_len) + { + best = Some(candidate); + } + } + } + + best + } +} + +/// Parse emphasis using the delimiter stack matches. +fn parse_emphasis_from_context(p: &mut MarkdownParser, expect_strong: bool) -> ParsedSyntax { + let context = match p.emphasis_context() { + Some(context) => context, + None => return Absent, + }; + + // Must be at an emphasis token + if !p.at(DOUBLE_STAR) && !p.at(DOUBLE_UNDERSCORE) && !p.at(T![*]) && !p.at(UNDERSCORE) { + return Absent; + } + + // Get current token info BEFORE any re-lex + let token_start = u32::from(p.cur_range().start()) as usize; + let token_len: usize = p.cur_range().len().into(); + + // Find match within current token's range that has the expected is_strong value + let opener_match = match context.opener_within(token_start, token_len, expect_strong) { + Some(m) => m, + None => return Absent, + }; + + // If the opener doesn't start at the exact token boundary, return Absent. + // The caller (parse_any_inline) will emit literal text, advancing the parser position. + // On subsequent calls, we'll eventually be at the correct position with prefix_len == 0. + if opener_match.prefix_len > 0 { + return Absent; + } + + // Extract values before dropping the borrow on context + let use_count = if expect_strong { 2 } else { 1 }; + let closer_offset = opener_match.matched.closer_start + context.base_offset; + // Use the correct delimiter character for error messages + let is_underscore = p.at(DOUBLE_UNDERSCORE) || p.at(UNDERSCORE); + let opener_text = match (expect_strong, is_underscore) { + (true, true) => "__", + (true, false) => "**", + (false, true) => "_", + (false, false) => "*", + }; + + let m = p.start(); + let opening_range = p.cur_range(); + + // Consume opener tokens + // For strong emphasis (use_count=2), we can bump DOUBLE_* directly if at one. + // Only re-lex when we need to consume a partial token or single chars. + if use_count == 2 && (p.at(DOUBLE_STAR) || p.at(DOUBLE_UNDERSCORE)) { + // Bump the double token as a single unit + p.bump_any(); + } else { + // Consume individual tokens + for _ in 0..use_count { + if p.at(DOUBLE_STAR) || p.at(DOUBLE_UNDERSCORE) { + p.force_relex_emphasis_inline(); + } + p.bump_any(); + } + } + + // Parse content until we reach the closer + let content = p.start(); + loop { + // EOF always ends content + if p.at(T![EOF]) { + break; + } + + let current_offset = u32::from(p.cur_range().start()) as usize; + let current_len: usize = p.cur_range().len().into(); + + // Check if closer is AT or WITHIN current token + if closer_offset >= current_offset && closer_offset < current_offset + current_len { + break; + } + + // Check if we've passed the closer (can happen when link parsing consumes past it) + if current_offset > closer_offset { + break; + } + + // Parse nested inline content + let result = super::parse_any_inline(p); + if result.is_absent() { + break; + } + } + content.complete(p, MD_INLINE_ITEM_LIST); + + // Consume closer tokens + let mut consumed_closer = 0; + while consumed_closer < use_count { + if use_count == 2 && (p.at(DOUBLE_STAR) || p.at(DOUBLE_UNDERSCORE)) { + p.bump_any(); + consumed_closer += 2; + continue; + } + if p.at(DOUBLE_STAR) || p.at(DOUBLE_UNDERSCORE) { + p.force_relex_emphasis_inline(); + } + if p.at(T![*]) || p.at(UNDERSCORE) { + p.bump_any(); + consumed_closer += 1; + } else { + break; + } + } + + if consumed_closer < use_count { + p.error(unclosed_emphasis(p, opening_range, opener_text)); + } + + if expect_strong { + Present(m.complete(p, MD_INLINE_EMPHASIS)) + } else { + Present(m.complete(p, MD_INLINE_ITALIC)) + } +} + +/// Parse inline emphasis (bold: `**text**` or `__text__`). +pub(crate) fn parse_inline_emphasis(p: &mut MarkdownParser) -> ParsedSyntax { + parse_emphasis_from_context(p, true) +} + +/// Parse inline italic (`*text*` or `_text_`). +pub(crate) fn parse_inline_italic(p: &mut MarkdownParser) -> ParsedSyntax { + parse_emphasis_from_context(p, false) +} + +pub(crate) fn set_inline_emphasis_context_until( + p: &mut MarkdownParser, + stop: MarkdownSyntaxKind, +) -> Option { + let source_len = inline_list_source_len_until(p, stop); + let source = p.source_after_current(); + let inline_source = if source_len <= source.len() { + &source[..source_len] + } else { + source + }; + let base_offset = u32::from(p.cur_range().start()) as usize; + // Create a reference checker closure that uses the parser's link reference definitions + let context = EmphasisContext::new(inline_source, base_offset, |label| { + p.has_link_reference_definition(label) + }); + p.set_emphasis_context(Some(context)) +} + +fn inline_list_source_len_until(p: &mut MarkdownParser, stop: MarkdownSyntaxKind) -> usize { + p.lookahead(|p| { + let mut len = 0usize; + + loop { + if p.at(T![EOF]) || p.at(stop) || p.at_inline_end() { + break; + } + + len += p.cur_text().len(); + p.bump(p.cur()); + } + + len + }) +} diff --git a/crates/biome_markdown_parser/src/syntax/inline/entities.rs b/crates/biome_markdown_parser/src/syntax/inline/entities.rs new file mode 100644 index 000000000000..94340865c529 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/inline/entities.rs @@ -0,0 +1,26 @@ +use biome_markdown_syntax::kind::MarkdownSyntaxKind::*; +use biome_parser::Parser; +use biome_parser::prelude::ParsedSyntax::{self, *}; + +use crate::MarkdownParser; + +/// Parse entity or numeric character reference per CommonMark §6.2. +/// +/// Grammar: MdEntityReference = value: 'md_entity_literal' +/// +/// Valid patterns: +/// - Named entity: `&name;` where name is 2-31 alphanumeric chars starting with letter +/// - Decimal numeric: `&#digits;` where digits is 1-7 decimal digits +/// - Hexadecimal: `&#xhex;` or `&#Xhex;` where hex is 1-6 hex digits +/// +/// The lexer has already validated and tokenized valid entity references as +/// MD_ENTITY_LITERAL tokens. Invalid patterns remain as textual. +pub(crate) fn parse_entity_reference(p: &mut MarkdownParser) -> ParsedSyntax { + if !p.at(MD_ENTITY_LITERAL) { + return Absent; + } + + let m = p.start(); + p.bump(MD_ENTITY_LITERAL); + Present(m.complete(p, MD_ENTITY_REFERENCE)) +} diff --git a/crates/biome_markdown_parser/src/syntax/inline/html.rs b/crates/biome_markdown_parser/src/syntax/inline/html.rs new file mode 100644 index 000000000000..5ae709e28d8f --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/inline/html.rs @@ -0,0 +1,485 @@ +use biome_markdown_syntax::T; +use biome_markdown_syntax::kind::MarkdownSyntaxKind::*; +use biome_parser::Parser; +use biome_parser::prelude::ParsedSyntax::{self, *}; + +use crate::MarkdownParser; +use crate::lexer::MarkdownLexContext; +use crate::syntax::inline_span_crosses_setext; + +/// Check if text starting with `<` is valid inline HTML per CommonMark §6.8. +/// Returns the length of the HTML element if valid, None otherwise. +/// +/// Valid patterns: +/// - Open tags: ``, ``, `` +/// - Close tags: `` +/// - Comments: `` +/// - Processing instructions: `` +/// - Declarations: `` +/// - CDATA: `` +pub(crate) fn is_inline_html(text: &str) -> Option { + let bytes = text.as_bytes(); + if bytes.len() < 2 || bytes[0] != b'<' { + return None; + } + + // HTML comment: + // Per CommonMark 0.31.2 §6.8, an HTML comment consists of ``, + // where text does not start with `>` or `->`, and does not end with `-`. + // Additionally, `` and `` are valid (degenerate) comments. + if bytes.starts_with(b" and + if rest.starts_with(b">") { + return Some(5); // + } + if rest.starts_with(b"->") { + return Some(6); // + } + // Find closing --> after ") { + let body = &text[4..4 + pos]; + // Body must not end with '-' + if body.ends_with('-') { + return None; + } + return Some(4 + pos + 3); + } + return None; + } + + // Processing instruction: + if bytes.len() >= 2 && bytes[1] == b'?' { + // Find closing ?> + if let Some(pos) = text[2..].find("?>") { + return Some(2 + pos + 2); + } + return None; + } + + // CDATA section: + if bytes.starts_with(b" + if let Some(pos) = text[9..].find("]]>") { + return Some(9 + pos + 3); + } + return None; + } + + // Declaration: + // e.g., + if bytes.len() >= 3 && bytes[1] == b'!' && bytes[2].is_ascii_alphabetic() { + // Find closing > + if let Some(pos) = text[2..].find('>') { + return Some(2 + pos + 1); + } + return None; + } + + // Close tag: + if bytes.len() >= 4 && bytes[1] == b'/' { + if !bytes[2].is_ascii_alphabetic() { + return None; + } + // Tag name: [A-Za-z][A-Za-z0-9-]* + let mut i = 3; + while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'-') { + i += 1; + } + // Skip optional whitespace + while i < bytes.len() + && (bytes[i] == b' ' + || bytes[i] == b'\t' + || bytes[i] == b'\n' + || bytes[i] == b'\r' + || bytes[i] == b'\x0c') + { + i += 1; + } + // Must end with > + if i < bytes.len() && bytes[i] == b'>' { + return Some(i + 1); + } + return None; + } + + // Open tag: or + // Defensive bounds check - should be guaranteed by earlier len check but be explicit + if bytes.len() < 2 || !bytes[1].is_ascii_alphabetic() { + return None; + } + + // Tag name: [A-Za-z][A-Za-z0-9-]* + // Note: tag names cannot contain `.` (so is NOT a valid tag) + let mut i = 2; + while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'-') { + i += 1; + } + + // After tag name, must have valid boundary: whitespace, >, or / + // This prevents from being treated as HTML + if i >= bytes.len() { + return None; + } + let boundary = bytes[i]; + if boundary != b' ' + && boundary != b'\t' + && boundary != b'\n' + && boundary != b'\r' + && boundary != b'\x0c' + && boundary != b'>' + && boundary != b'/' + { + return None; + } + + // Handle immediate close or self-close + if boundary == b'>' { + return Some(i + 1); + } + if boundary == b'/' { + if i + 1 < bytes.len() && bytes[i + 1] == b'>' { + return Some(i + 2); + } + return None; + } + + // Has attributes - validate per CommonMark §6.8 + + let skip_spaces = |i: &mut usize| -> Option { + let mut skipped = false; + while *i < bytes.len() { + match bytes[*i] { + b' ' | b'\t' | b'\n' | b'\r' | b'\x0c' => { + skipped = true; + *i += 1; + } + _ => break, + } + } + Some(skipped) + }; + + let is_attr_name_start = |b: u8| b.is_ascii_alphabetic() || b == b'_' || b == b':'; + let is_attr_name_continue = + |b: u8| b.is_ascii_alphanumeric() || b == b'_' || b == b':' || b == b'.' || b == b'-'; + + let mut need_space = true; + // We already know the boundary char was whitespace, so first iteration has space. + let mut had_space = true; + + loop { + if need_space { + let s = skip_spaces(&mut i)?; + had_space = had_space || s; + } + need_space = true; + + if i >= bytes.len() { + return None; + } + + // End or self-close + if bytes[i] == b'>' { + return Some(i + 1); + } + if bytes[i] == b'/' { + if i + 1 < bytes.len() && bytes[i + 1] == b'>' { + return Some(i + 2); + } + return None; + } + + // Attributes must be separated by whitespace + if !had_space { + return None; + } + + // Parse attribute name + if !is_attr_name_start(bytes[i]) { + return None; + } + i += 1; + while i < bytes.len() && is_attr_name_continue(bytes[i]) { + i += 1; + } + + // Optional whitespace and value + had_space = skip_spaces(&mut i)?; + if i < bytes.len() && bytes[i] == b'=' { + i += 1; + skip_spaces(&mut i)?; + if i >= bytes.len() { + return None; + } + + match bytes[i] { + b'"' => { + i += 1; + while i < bytes.len() && bytes[i] != b'"' { + i += 1; + } + if i >= bytes.len() { + return None; + } + i += 1; + } + b'\'' => { + i += 1; + while i < bytes.len() && bytes[i] != b'\'' { + i += 1; + } + if i >= bytes.len() { + return None; + } + i += 1; + } + _ => { + let start = i; + while i < bytes.len() { + let b = bytes[i]; + if b <= b' ' + || b == b'"' + || b == b'\'' + || b == b'=' + || b == b'<' + || b == b'>' + || b == b'`' + { + break; + } + i += 1; + } + if i == start { + return None; + } + } + } + // After value, need to find whitespace at top of loop + had_space = false; + } + // If no '=' was found, `had_space` from skip_spaces above carries over + // as the separator for the next attribute (boolean attribute case). + } +} + +/// Parse raw inline HTML per CommonMark §6.8. +/// +/// Grammar: MdInlineHtml = value: MdInlineItemList +/// +/// Includes: open tags, close tags, comments, processing instructions, +/// declarations, and CDATA sections. +pub(crate) fn parse_inline_html(p: &mut MarkdownParser) -> ParsedSyntax { + if !p.at(L_ANGLE) { + return Absent; + } + + // Get the source text starting from current position + let source = p.source_after_current(); + + // Check if this is valid inline HTML + let html_len = match is_inline_html(source) { + Some(len) => len, + None => return Absent, + }; + + // Per CommonMark §4.3, setext heading underlines take priority over inline HTML. + // If this HTML tag spans across a line that is a setext underline, treat `<` as literal. + if inline_span_crosses_setext(p, html_len) { + return Absent; + } + + // Valid inline HTML - create the node + // Use checkpoint so we can rewind if token boundaries don't align + let checkpoint = p.checkpoint(); + let m = p.start(); + + // Create content as inline item list containing textual nodes + let content = p.start(); + + // Track remaining bytes to consume + let mut remaining = html_len; + + while remaining > 0 && !p.at(T![EOF]) { + let token_len = p.cur_text().len(); + + // If the current token is larger than remaining bytes, token boundaries + // don't align with our validated HTML - rewind and treat as text + if token_len > remaining { + m.abandon(p); + p.rewind(checkpoint); + return Absent; + } + + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + remaining -= token_len; + } + + content.complete(p, MD_INLINE_ITEM_LIST); + + Present(m.complete(p, MD_INLINE_HTML)) +} + +/// Check if the text after `<` looks like a URI autolink. +/// Per CommonMark §6.4: scheme must be 2-32 chars, start with letter, +/// followed by letters/digits/+/-/., then `:`. +fn is_uri_autolink(text: &str) -> bool { + let bytes = text.as_bytes(); + if bytes.is_empty() { + return false; + } + + // Must start with a letter + if !bytes[0].is_ascii_alphabetic() { + return false; + } + + // Find the colon + let mut colon_pos = None; + for (i, &b) in bytes.iter().enumerate().skip(1) { + if b == b':' { + colon_pos = Some(i); + break; + } + // Scheme chars: letters, digits, +, -, . + if !b.is_ascii_alphanumeric() && b != b'+' && b != b'-' && b != b'.' { + return false; + } + } + + // Scheme must be 2-32 chars and followed by colon + match colon_pos { + Some(pos) if (2..=32).contains(&pos) => { + // Must have content after the colon and no whitespace/< in URI + let rest = &text[pos + 1..]; + !rest.is_empty() + && !rest.contains('<') + && !rest.contains('>') + && !rest.chars().any(|c| c.is_whitespace()) + } + _ => false, + } +} + +/// Check if the text after `<` looks like an email autolink. +/// Per CommonMark §6.5: local@domain pattern with specific char restrictions. +fn is_email_autolink(text: &str) -> bool { + // Must contain exactly one @ not at start or end + let at_pos = match text.find('@') { + Some(pos) if pos > 0 && pos < text.len() - 1 => pos, + _ => return false, + }; + + // Check no second @ + if text[at_pos + 1..].contains('@') { + return false; + } + + // Local part: alphanumerics and .!#$%&'*+/=?^_`{|}~- + let local = &text[..at_pos]; + for c in local.chars() { + if !c.is_ascii_alphanumeric() + && !matches!( + c, + '.' | '!' + | '#' + | '$' + | '%' + | '&' + | '\'' + | '*' + | '+' + | '/' + | '=' + | '?' + | '^' + | '_' + | '`' + | '{' + | '|' + | '}' + | '~' + | '-' + ) + { + return false; + } + } + + // Domain part: alphanumerics and hyphens, dots for subdomains + let domain = &text[at_pos + 1..]; + if domain.is_empty() || domain.starts_with('.') || domain.ends_with('.') { + return false; + } + + for c in domain.chars() { + if !c.is_ascii_alphanumeric() && c != '-' && c != '.' { + return false; + } + } + + true +} + +/// Parse an autolink (`` or ``). +/// +/// Grammar: MdAutolink = '<' value: MdInlineItemList '>' +/// +/// Per CommonMark §6.4 and §6.5, autolinks are URIs or email addresses +/// wrapped in angle brackets. +pub(crate) fn parse_autolink(p: &mut MarkdownParser) -> ParsedSyntax { + if !p.at(L_ANGLE) { + return Absent; + } + + // Look ahead to find the closing > and check if content is valid + let source = p.source_after_current(); + + // Skip the < and find > + let after_open = &source[1..]; + let close_pos = match after_open.find('>') { + Some(pos) => pos, + None => return Absent, // No closing > + }; + + // Check for newline before > (not allowed in autolinks) + let content = &after_open[..close_pos]; + if content.contains('\n') || content.contains('\r') { + return Absent; + } + + // Must be either URI or email autolink + if !is_uri_autolink(content) && !is_email_autolink(content) { + return Absent; + } + + // Valid autolink - parse it + let m = p.start(); + + // < + p.bump(L_ANGLE); + + // Content as inline item list containing textual nodes. + // Autolinks don't process backslash escapes, but the lexer may combine + // `\>` into a single escape token. We re-lex in CodeSpan context where + // backslash is literal, so `\` and `>` are separate tokens. + p.relex_code_span(); + + let content_m = p.start(); + while !p.at(R_ANGLE) && !p.at(T![EOF]) && !p.at_inline_end() { + let text_m = p.start(); + p.bump_remap_with_context(MD_TEXTUAL_LITERAL, MarkdownLexContext::CodeSpan); + text_m.complete(p, MD_TEXTUAL); + } + content_m.complete(p, MD_INLINE_ITEM_LIST); + + // > + p.expect(R_ANGLE); + + // Re-lex back to regular context + p.force_relex_regular(); + + Present(m.complete(p, MD_AUTOLINK)) +} diff --git a/crates/biome_markdown_parser/src/syntax/inline/links.rs b/crates/biome_markdown_parser/src/syntax/inline/links.rs new file mode 100644 index 000000000000..4414590ce546 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/inline/links.rs @@ -0,0 +1,857 @@ +use biome_markdown_syntax::MarkdownSyntaxKind; +use biome_markdown_syntax::T; +use biome_markdown_syntax::kind::MarkdownSyntaxKind::*; +use biome_parser::Parser; +use biome_parser::prelude::ParsedSyntax::{self, *}; +use biome_rowan::TextRange; + +use crate::MarkdownParser; +use crate::lexer::MarkdownLexContext; +use crate::syntax::inline::{parse_inline_item_list_until, parse_inline_item_list_until_no_links}; +use crate::syntax::parse_error::{unclosed_image, unclosed_link}; +use crate::syntax::reference::normalize_reference_label; +use crate::syntax::{ + LinkDestinationKind, MAX_LINK_DESTINATION_PAREN_DEPTH, ParenDepthResult, + ends_with_unescaped_close, try_update_paren_depth, validate_link_destination_text, +}; + +/// Parse link starting with `[` - dispatches to inline link or reference link. +/// +/// After parsing `[text]`: +/// - If followed by `(` → inline link `[text](url)` +/// - If followed by `[` → reference link `[text][label]` or `[text][]` +/// - Otherwise → shortcut reference `[text]` +pub(crate) fn parse_link_or_reference(p: &mut MarkdownParser) -> ParsedSyntax { + parse_link_or_image(p, LinkParseKind::Link) +} + +/// Parse reference link label `[label]` or `[]`. +/// +/// Grammar: `MdReferenceLinkLabel = '[' label: MdInlineItemList ']'` +/// +/// Returns Present if `[` and `]` are found (even if empty for collapsed reference). +/// On failure (missing `]`), rewinds to the checkpoint so no tokens are consumed. +fn parse_reference_label(p: &mut MarkdownParser) -> ParsedSyntax { + if !p.at(L_BRACK) { + return Absent; + } + + // Checkpoint so we can rewind if ] is missing + let checkpoint = p.checkpoint(); + let m = p.start(); + + // [ + p.bump(L_BRACK); + + // Label content (may be empty for collapsed reference) + let label = p.start(); + while !p.at(R_BRACK) && !p.at_inline_end() { + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + } + label.complete(p, MD_INLINE_ITEM_LIST); + + // ] + if !p.eat(R_BRACK) { + // Missing closing bracket - abandon and rewind to not consume tokens + m.abandon(p); + p.rewind(checkpoint); + return Absent; + } + + Present(m.complete(p, MD_REFERENCE_LINK_LABEL)) +} + +/// Parse inline link (`[text](url)`). +/// +/// Grammar: `MdInlineLink = '[' text: MdInlineItemList ']' '(' source: MdInlineItemList ')'` +/// +/// Note: This is kept for backwards compatibility but `parse_link_or_reference` +/// is the preferred entry point for link parsing. +pub(crate) fn parse_inline_link(p: &mut MarkdownParser) -> ParsedSyntax { + parse_link_or_reference(p) +} + +/// Parse image starting with `![` - dispatches to inline image or reference image. +/// +/// After parsing `![alt]`: +/// - If followed by `(` → inline image `![alt](url)` +/// - If followed by `[` → reference image `![alt][label]` or `![alt][]` +/// - Otherwise → shortcut reference image `![alt]` +pub(crate) fn parse_image_or_reference(p: &mut MarkdownParser) -> ParsedSyntax { + parse_link_or_image(p, LinkParseKind::Image) +} + +#[derive(Copy, Clone)] +enum LinkParseKind { + Link, + Image, +} + +impl LinkParseKind { + fn starts_here(self, p: &mut MarkdownParser) -> bool { + match self { + Self::Link => p.at(L_BRACK), + Self::Image => p.at(BANG) && p.nth_at(1, L_BRACK), + } + } + + fn bump_opening(self, p: &mut MarkdownParser) { + if matches!(self, Self::Image) { + p.bump(BANG); + } + p.bump(L_BRACK); + } + + fn lookahead_reference(self, p: &mut MarkdownParser) -> Option { + match self { + Self::Link => lookahead_reference_link(p), + Self::Image => lookahead_reference_image(p), + } + } + + fn inline_kind(self) -> MarkdownSyntaxKind { + match self { + Self::Link => MD_INLINE_LINK, + Self::Image => MD_INLINE_IMAGE, + } + } + + fn reference_kind(self) -> MarkdownSyntaxKind { + match self { + Self::Link => MD_REFERENCE_LINK, + Self::Image => MD_REFERENCE_IMAGE, + } + } + + fn report_unclosed_destination(self, p: &mut MarkdownParser, opening_range: TextRange) { + match self { + Self::Link => p.error(unclosed_link(p, opening_range, "expected `)` to close URL")), + Self::Image => p.error(unclosed_image( + p, + opening_range, + "expected `)` to close image URL", + )), + } + } +} + +fn parse_link_or_image(p: &mut MarkdownParser, kind: LinkParseKind) -> ParsedSyntax { + if !kind.starts_here(p) { + return Absent; + } + + let checkpoint = p.checkpoint(); + let m = p.start(); + let opening_range = p.cur_range(); + let reference = kind.lookahead_reference(p); + // Clear any cached lookahead tokens before switching lexing context. + p.reset_lookahead(); + + kind.bump_opening(p); + + // Link text / alt text + let has_nested_link = if matches!(kind, LinkParseKind::Image) { + // For images, allow full inline parsing (including links) in alt text. + // This lets nested links/images be parsed so their text can be extracted for alt. + parse_inline_item_list_until(p, R_BRACK); + false + } else { + parse_inline_item_list_until_no_links(p, R_BRACK) + }; + + // ] - if missing, rewind and treat [ as literal text. + // Per CommonMark, if there's no valid ] to close the link (e.g., all ] + // characters are inside code spans or HTML), the [ is literal text. + // NOTE: We intentionally do NOT emit an "unclosed link" diagnostic here. + // CommonMark treats unmatched `[` as literal text, not an error. + if !p.eat(R_BRACK) { + m.abandon(p); + p.rewind(checkpoint); + return Absent; + } + + // Per CommonMark, a link (not image) whose text contains another link must fail. + // The inner link wins and the outer `[` becomes literal text. + if matches!(kind, LinkParseKind::Link) && has_nested_link { + m.abandon(p); + p.rewind(checkpoint); + return Absent; + } + + // Now decide based on what follows ] + let link_validation = if p.at(L_PAREN) { + inline_link_is_valid(p) + } else { + InlineLinkValidation::Invalid + }; + + if matches!( + link_validation, + InlineLinkValidation::Valid | InlineLinkValidation::DepthExceeded + ) { + // Inline link/image: [text](url) or ![alt](url) + // Bump past ( and lex the following tokens in LinkDefinition context + // so whitespace separates destination and title. + p.expect_with_context(L_PAREN, MarkdownLexContext::LinkDefinition); + + let destination = p.start(); + let destination_result = parse_inline_link_destination_tokens(p); + + // When depth exceeded, destination is truncated but link is still valid. + // Complete the destination and link immediately without looking for closing paren. + if destination_result == DestinationScanResult::DepthExceeded { + destination.complete(p, MD_INLINE_ITEM_LIST); + p.force_relex_regular(); + return Present(m.complete(p, kind.inline_kind())); + } + + let has_title = inline_title_starts_after_whitespace_tokens(p); + while is_title_separator_token(p) { + bump_link_def_separator(p); + } + if destination_result == DestinationScanResult::Invalid { + destination.abandon(p); + m.abandon(p); + p.rewind(checkpoint); + p.force_relex_regular(); + return Absent; + } + destination.complete(p, MD_INLINE_ITEM_LIST); + + if has_title { + let title_m = p.start(); + let list_m = p.start(); + parse_title_content(p, get_title_close_char(p)); + list_m.complete(p, MD_INLINE_ITEM_LIST); + title_m.complete(p, MD_LINK_TITLE); + } + + // Skip trailing whitespace/newlines before closing paren without creating nodes + // (creating nodes would violate the MD_INLINE_LINK grammar which expects exactly 7 children) + while is_title_separator_token(p) { + skip_link_def_separator_tokens(p); + } + + if !p.eat(R_PAREN) { + if p.at_inline_end() { + kind.report_unclosed_destination(p, opening_range); + } + m.abandon(p); + p.rewind(checkpoint); + p.force_relex_regular(); + return Absent; + } + + Present(m.complete(p, kind.inline_kind())) + } else if p.at(L_BRACK) { + // Reference link/image: [text][label] or [text][] + let label = parse_reference_label(p); + let reference = reference.filter(|reference| { + if label.is_absent() { + reference.is_shortcut + } else { + true + } + }); + + if let Some(reference) = reference + && !reference.is_defined(p) + { + m.abandon(p); + p.rewind(checkpoint); + // Return Absent - the caller will treat `[` as textual. + // Don't consume the whole bracket sequence to avoid consuming + // past emphasis closers. + return Absent; + } + + Present(m.complete(p, kind.reference_kind())) + } else { + // Shortcut reference: [text] or ![alt] + // No label part - the text/alt IS the label for resolution + if let Some(reference) = reference + && reference.is_shortcut + && !reference.is_defined(p) + { + m.abandon(p); + p.rewind(checkpoint); + // Return Absent - the caller will treat `[` as textual. + // Don't consume the whole bracket sequence to avoid consuming + // past emphasis closers. + return Absent; + } + Present(m.complete(p, kind.reference_kind())) + } +} + +struct ReferenceLinkLookahead { + label_raw: String, + is_shortcut: bool, +} + +impl ReferenceLinkLookahead { + fn is_defined(&self, p: &MarkdownParser) -> bool { + let normalized = normalize_reference_label(&self.label_raw); + p.has_link_reference_definition(normalized.as_ref()) + } +} + +fn lookahead_reference_link(p: &mut MarkdownParser) -> Option { + lookahead_reference_common(p, false) +} + +fn lookahead_reference_image(p: &mut MarkdownParser) -> Option { + lookahead_reference_common(p, true) +} + +fn lookahead_reference_common( + p: &mut MarkdownParser, + is_image: bool, +) -> Option { + p.lookahead(|p| { + if is_image { + if !p.at(BANG) || !p.nth_at(1, L_BRACK) { + return None; + } + p.bump(BANG); + } + + if !p.at(L_BRACK) { + return None; + } + + p.bump(L_BRACK); + + let link_text = collect_link_text(p)?; + + // Link text must be non-empty after normalization (e.g., `[\n ]` normalizes to empty) + let normalized_link = normalize_reference_label(&link_text); + if normalized_link.is_empty() { + return None; + } + + p.bump(R_BRACK); + + if p.at(L_PAREN) { + return None; + } + + if p.at(L_BRACK) { + p.bump(L_BRACK); + let label_text = collect_label_text_simple(p); + if let Some(label_text) = label_text { + let label = if label_text.is_empty() { + link_text.clone() + } else { + // Explicit label must also normalize to non-empty + let normalized_label = normalize_reference_label(&label_text); + if normalized_label.is_empty() { + return None; + } + label_text + }; + p.bump(R_BRACK); + return Some(ReferenceLinkLookahead { + label_raw: label, + is_shortcut: false, + }); + } + } + + Some(ReferenceLinkLookahead { + label_raw: link_text, + is_shortcut: true, + }) + }) +} + +/// Collect text for a link label (e.g., the `label` in `[text][label]`). +/// +/// Per CommonMark §4.7, link labels have specific rules: +/// - Unescaped square brackets are NOT allowed inside labels (see example 555) +/// - Backslash escapes ARE allowed (e.g., `\]` is a literal `]` in the label) +/// - No inline parsing (backticks, HTML, etc. are literal characters) +/// +/// We stop at the first R_BRACK token (unescaped `]`). Escaped brackets like `\]` +/// are lexed as MD_TEXTUAL_LITERAL, not R_BRACK, so they're included in the label. +fn collect_label_text_simple(p: &mut MarkdownParser) -> Option { + let mut text = String::new(); + + loop { + if p.at(T![EOF]) || p.at_inline_end() { + return None; + } + + // Blank lines terminate + if p.at(NEWLINE) && p.at_blank_line() { + return None; + } + + // R_BRACK token = unescaped `]` closes the label. + // Note: Escaped brackets (`\]`) are lexed as MD_TEXTUAL_LITERAL, + // not R_BRACK, so they're correctly included in the label text. + if p.at(R_BRACK) { + return Some(text); + } + + text.push_str(p.cur_text()); + p.bump(p.cur()); + } +} + +/// Collect text for link text (e.g., the `text` in `[text](url)` or `[text][label]`). +/// Per CommonMark, link text CAN contain inline elements - code spans, autolinks, HTML. +/// `]` inside these constructs does NOT close the link text. +fn collect_link_text(p: &mut MarkdownParser) -> Option { + let mut text = String::new(); + let mut bracket_depth = 0usize; + + loop { + if p.at(T![EOF]) || p.at_inline_end() { + return None; + } + + // Per CommonMark, blank lines terminate link text + if p.at(NEWLINE) && p.at_blank_line() { + return None; + } + + // Code spans can contain `]` - skip them entirely. + // Per CommonMark, `]` inside code spans doesn't terminate link text. + if p.at(BACKTICK) { + let opening_count = p.cur_text().len(); + text.push_str(p.cur_text()); + p.bump(p.cur()); + + // Find matching closing backticks + let mut found_close = false; + while !p.at(T![EOF]) && !p.at_inline_end() { + if p.at(NEWLINE) && p.at_blank_line() { + break; // Blank line terminates + } + if p.at(BACKTICK) && p.cur_text().len() == opening_count { + text.push_str(p.cur_text()); + p.bump(p.cur()); + found_close = true; + break; + } + text.push_str(p.cur_text()); + p.bump(p.cur()); + } + if !found_close { + // Unclosed code span - treat opening backticks as literal + // (already added to text, continue normally) + } + continue; + } + + // Autolinks and inline HTML can contain `]` - skip them entirely. + // Per CommonMark, `]` inside `<...>` constructs doesn't terminate link text. + if p.at(L_ANGLE) { + text.push_str(p.cur_text()); + p.bump(p.cur()); + + // Consume until `>` or newline + while !p.at(T![EOF]) && !p.at_inline_end() && !p.at(R_ANGLE) { + if p.at(NEWLINE) { + // Newlines end autolinks/HTML tags + break; + } + text.push_str(p.cur_text()); + p.bump(p.cur()); + } + if p.at(R_ANGLE) { + text.push_str(p.cur_text()); + p.bump(p.cur()); + } + continue; + } + + if p.at(L_BRACK) { + bracket_depth += 1; + text.push_str(p.cur_text()); + p.bump(p.cur()); + continue; + } + + if p.at(R_BRACK) { + if bracket_depth == 0 { + return Some(text); + } + bracket_depth -= 1; + text.push_str(p.cur_text()); + p.bump(p.cur()); + continue; + } + + text.push_str(p.cur_text()); + p.bump(p.cur()); + } +} + +fn bump_textual_link_def(p: &mut MarkdownParser) { + let item = p.start(); + p.bump_remap_with_context(MD_TEXTUAL_LITERAL, MarkdownLexContext::LinkDefinition); + item.complete(p, MD_TEXTUAL); +} +fn is_whitespace_token(p: &MarkdownParser) -> bool { + let text = p.cur_text(); + !text.is_empty() && text.chars().all(|c| c == ' ' || c == '\t') +} + +fn inline_title_starts_after_whitespace_tokens(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + let mut saw_whitespace = false; + while is_title_separator_token(p) { + bump_link_def_separator(p); + saw_whitespace = true; + } + saw_whitespace && get_title_close_char(p).is_some() + }) +} + +/// Result of validating an inline link. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum InlineLinkValidation { + /// Link is valid with complete destination + Valid, + /// Link is invalid + Invalid, + /// Link is valid but destination was truncated due to paren depth limit. + /// The link should be closed immediately without looking for `)`. + DepthExceeded, +} + +fn inline_link_is_valid(p: &mut MarkdownParser) -> InlineLinkValidation { + p.lookahead(|p| { + if !p.at(L_PAREN) { + return InlineLinkValidation::Invalid; + } + + p.bump(L_PAREN); + p.re_lex_link_definition(); + + let destination_result = scan_inline_link_destination_tokens(p); + + // If depth exceeded, link is valid but truncated - no need to check for closing paren + if destination_result == DestinationScanResult::DepthExceeded { + return InlineLinkValidation::DepthExceeded; + } + + if destination_result == DestinationScanResult::Invalid { + return InlineLinkValidation::Invalid; + } + + let mut saw_separator = false; + while is_title_separator_token(p) { + skip_link_def_separator_tokens(p); + saw_separator = true; + } + let has_title = saw_separator && get_title_close_char(p).is_some(); + while is_title_separator_token(p) { + skip_link_def_separator_tokens(p); + } + + if has_title { + scan_title_content(p, get_title_close_char(p)); + } + + while is_title_separator_token(p) { + skip_link_def_separator_tokens(p); + } + + if p.at(R_PAREN) { + InlineLinkValidation::Valid + } else { + InlineLinkValidation::Invalid + } + }) +} + +/// Result of scanning a link destination. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DestinationScanResult { + /// Destination is valid and complete + Valid, + /// Destination is invalid (contains invalid characters, etc.) + Invalid, + /// Destination was truncated because paren depth exceeded the limit. + /// In this case, the link is considered valid but closed at the truncation point. + DepthExceeded, +} + +fn scan_inline_link_destination_tokens(p: &mut MarkdownParser) -> DestinationScanResult { + const MAX_PAREN_DEPTH: i32 = MAX_LINK_DESTINATION_PAREN_DEPTH; + // Skip leading whitespace to match parse_inline_link_destination_tokens behavior + while is_title_separator_token(p) { + skip_link_def_separator_tokens(p); + } + if p.at(L_ANGLE) { + p.bump_link_definition(); + let mut pending_escape = false; + loop { + if p.at(EOF) || p.at(NEWLINE) { + return DestinationScanResult::Invalid; + } + if p.at(R_ANGLE) { + if pending_escape { + if !validate_link_destination_text( + p.cur_text(), + LinkDestinationKind::Enclosed, + &mut pending_escape, + ) { + return DestinationScanResult::Invalid; + } + p.bump_link_definition(); + continue; + } + p.bump_link_definition(); + return DestinationScanResult::Valid; + } + if !validate_link_destination_text( + p.cur_text(), + LinkDestinationKind::Enclosed, + &mut pending_escape, + ) { + return DestinationScanResult::Invalid; + } + p.bump_link_definition(); + } + } + + let mut paren_depth: i32 = 0; + let mut pending_escape = false; + while !p.at(EOF) && !p.at(NEWLINE) { + if is_whitespace_token(p) { + break; + } + let text = p.cur_text(); + if !validate_link_destination_text(text, LinkDestinationKind::Raw, &mut pending_escape) { + return DestinationScanResult::Invalid; + } + match try_update_paren_depth(text, paren_depth, MAX_PAREN_DEPTH) { + ParenDepthResult::Ok(next_depth) => { + paren_depth = next_depth; + p.bump_link_definition(); + } + ParenDepthResult::DepthExceeded => { + // Paren depth exceeded - destination is truncated at this point. + // Per CommonMark/cmark, the link is still valid but closed here. + return DestinationScanResult::DepthExceeded; + } + ParenDepthResult::UnmatchedClose => { + // Unmatched closing paren - destination ends here normally. + // The `)` belongs to the enclosing construct (inline link closer). + break; + } + } + } + if p.at(EOF) { + return DestinationScanResult::Invalid; + } + if p.at(NEWLINE) { + return if p.at_blank_line() { + DestinationScanResult::Invalid + } else { + DestinationScanResult::Valid + }; + } + DestinationScanResult::Valid +} + +fn scan_title_content(p: &mut MarkdownParser, close_char: Option) { + let Some(close_char) = close_char else { + return; + }; + + let text = p.cur_text(); + let is_complete = text.len() >= 2 && ends_with_unescaped_close(text, close_char); + + p.bump_link_definition(); + if is_complete { + return; + } + + loop { + // Stop on EOF or blank line (titles cannot span blank lines per CommonMark) + if p.at(EOF) || p.at_blank_line() { + return; + } + + // Continue through single newlines (titles can span non-blank lines) + if p.at(NEWLINE) { + skip_link_def_separator_tokens(p); + continue; + } + + let text = p.cur_text(); + if ends_with_unescaped_close(text, close_char) { + p.bump_link_definition(); + return; + } + + p.bump_link_definition(); + } +} + +fn skip_link_def_separator_tokens(p: &mut MarkdownParser) { + if p.at(NEWLINE) { + p.bump(NEWLINE); + } else { + p.bump_link_definition(); + } +} + +fn is_title_separator_token(p: &MarkdownParser) -> bool { + is_whitespace_token(p) || (p.at(NEWLINE) && !p.at_blank_line()) +} + +fn bump_link_def_separator(p: &mut MarkdownParser) { + if p.at(NEWLINE) { + let item = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + item.complete(p, MD_TEXTUAL); + } else { + bump_textual_link_def(p); + } +} + +fn parse_inline_link_destination_tokens(p: &mut MarkdownParser) -> DestinationScanResult { + p.re_lex_link_definition(); + const MAX_PAREN_DEPTH: i32 = MAX_LINK_DESTINATION_PAREN_DEPTH; + + if p.at(L_ANGLE) { + bump_textual_link_def(p); + let mut pending_escape = false; + loop { + if p.at(EOF) || p.at(NEWLINE) { + return DestinationScanResult::Invalid; + } + if p.at(R_ANGLE) { + if pending_escape { + if !validate_link_destination_text( + p.cur_text(), + LinkDestinationKind::Enclosed, + &mut pending_escape, + ) { + return DestinationScanResult::Invalid; + } + bump_textual_link_def(p); + continue; + } + bump_textual_link_def(p); + return DestinationScanResult::Valid; + } + if !validate_link_destination_text( + p.cur_text(), + LinkDestinationKind::Enclosed, + &mut pending_escape, + ) { + return DestinationScanResult::Invalid; + } + bump_textual_link_def(p); + } + } + + let mut paren_depth: i32 = 0; + let mut pending_escape = false; + while is_title_separator_token(p) { + bump_link_def_separator(p); + } + while !p.at(EOF) && !p.at(NEWLINE) { + if is_whitespace_token(p) { + break; + } + + let text = p.cur_text(); + if !validate_link_destination_text(text, LinkDestinationKind::Raw, &mut pending_escape) { + return DestinationScanResult::Invalid; + } + match try_update_paren_depth(text, paren_depth, MAX_PAREN_DEPTH) { + ParenDepthResult::Ok(next_depth) => { + paren_depth = next_depth; + bump_textual_link_def(p); + } + ParenDepthResult::DepthExceeded => { + // Paren depth exceeded - destination is truncated at this point. + return DestinationScanResult::DepthExceeded; + } + ParenDepthResult::UnmatchedClose => { + // Unmatched closing paren - destination ends here normally. + // The `)` belongs to the enclosing construct (inline link closer). + break; + } + } + } + if p.at(EOF) { + return DestinationScanResult::Invalid; + } + if p.at(NEWLINE) { + return if p.at_blank_line() { + DestinationScanResult::Invalid + } else { + DestinationScanResult::Valid + }; + } + DestinationScanResult::Valid +} + +fn get_title_close_char(p: &MarkdownParser) -> Option { + let text = p.cur_text(); + if text.starts_with('"') { + Some('"') + } else if text.starts_with('\'') { + Some('\'') + } else if p.at(L_PAREN) { + Some(')') + } else { + None + } +} + +fn parse_title_content(p: &mut MarkdownParser, close_char: Option) { + let Some(close_char) = close_char else { + return; + }; + + let text = p.cur_text(); + let is_complete = text.len() >= 2 && ends_with_unescaped_close(text, close_char); + + bump_textual_link_def(p); + if is_complete { + return; + } + + loop { + // Stop on EOF or blank line (titles cannot span blank lines per CommonMark) + if p.at(EOF) || p.at_blank_line() { + return; + } + + // Continue through single newlines (titles can span non-blank lines) + if p.at(NEWLINE) { + bump_link_def_separator(p); + continue; + } + + let text = p.cur_text(); + if ends_with_unescaped_close(text, close_char) { + bump_textual_link_def(p); + return; + } + + bump_textual_link_def(p); + } +} + +/// Parse inline image (`![alt](url)`). +/// +/// Grammar: `MdInlineImage = '!' '[' alt: MdInlineItemList ']' '(' source: MdInlineItemList ')'` +/// +/// Note: This is kept for backwards compatibility but `parse_image_or_reference` +/// is the preferred entry point for image parsing. +pub(crate) fn parse_inline_image(p: &mut MarkdownParser) -> ParsedSyntax { + parse_image_or_reference(p) +} diff --git a/crates/biome_markdown_parser/src/syntax/inline/mod.rs b/crates/biome_markdown_parser/src/syntax/inline/mod.rs new file mode 100644 index 000000000000..884968711386 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/inline/mod.rs @@ -0,0 +1,301 @@ +//! Inline element parsing for Markdown. +//! +//! Handles inline code spans, emphasis (bold/italic), links, images, line breaks, and raw HTML. +//! +//! # CommonMark Specification References +//! +//! This module implements the following CommonMark 0.31.2 sections: +//! +//! - **§6.1 Code spans**: Backtick-delimited inline code (`code`) +//! - **§6.2 Emphasis and strong emphasis**: `*italic*`, `**bold**`, `_italic_`, `__bold__` +//! - **§6.3 Links**: `[text](url)` inline links +//! - **§6.4 Autolinks (URI)**: `` +//! - **§6.5 Autolinks (email)**: `` +//! - **§6.6 Hard line breaks**: Trailing spaces or backslash before newline +//! - **§6.7 Soft line breaks**: Single newline within paragraph +//! - **§6.8 Raw HTML**: ``, ``, ``, ``, ``, `` +//! +//! # Emphasis Algorithm (§6.4) +//! +//! This module implements the CommonMark delimiter stack algorithm for emphasis: +//! +//! 1. **First pass**: Collect delimiter runs from the inline content +//! 2. **Second pass**: Match openers and closers using the delimiter stack algorithm +//! 3. **Rule of 3**: If (opener_count + closer_count) % 3 == 0 and both can open/close, +//! skip the match unless both counts are divisible by 3 +//! +//! # Emphasis Flanking Rules (§6.2) +//! +//! A delimiter run is **left-flanking** if: +//! 1. Not followed by Unicode whitespace, AND +//! 2. Not followed by punctuation, OR preceded by whitespace/punctuation +//! +//! A delimiter run is **right-flanking** if: +//! 1. Not preceded by Unicode whitespace, AND +//! 2. Not preceded by punctuation, OR followed by whitespace/punctuation +//! +//! Underscore (`_`) has additional intraword restrictions (§6.2 rules 2, 5, 7, 8). + +use biome_markdown_syntax::MarkdownSyntaxKind; +use biome_markdown_syntax::T; +use biome_markdown_syntax::kind::MarkdownSyntaxKind::*; +use biome_parser::Parser; +use biome_parser::prelude::ParsedSyntax; + +use crate::MarkdownParser; + +mod code_span; +mod emphasis; +mod entities; +mod html; +mod links; + +pub(crate) use emphasis::EmphasisContext; +pub(crate) use html::is_inline_html; + +enum InlineLinksPolicy { + NoLinks, + FullLinks, +} + +struct InlineListUntilResult { + has_nested_link: bool, +} + +fn parse_inline_item_list_until_impl( + p: &mut MarkdownParser, + stop: MarkdownSyntaxKind, + policy: InlineLinksPolicy, +) -> InlineListUntilResult { + let m = p.start(); + let prev_context = emphasis::set_inline_emphasis_context_until(p, stop); + let mut bracket_depth = 0usize; + let mut has_nested_link = false; + + loop { + // Per CommonMark, link text can span lines, but blank lines end the link. + // Check for blank line (NEWLINE followed by NEWLINE or EOF after optional whitespace) + if p.at(NEWLINE) { + if p.at_blank_line() { + break; // Blank line ends link text + } + // Single newline inside link text - consume and continue + let _ = super::parse_textual(p); + continue; + } + + if p.at(T![EOF]) { + break; + } + + // IMPORTANT: Parse constructs that can contain `]` BEFORE checking for stop token. + // Per CommonMark, `]` inside code spans, autolinks, and HTML doesn't terminate links. + + // Code spans can contain `]` + if p.at(BACKTICK) { + if code_span::parse_inline_code(p).is_present() { + continue; + } + let _ = super::parse_textual(p); + continue; + } + + // Autolinks and inline HTML can contain `]` + if p.at(L_ANGLE) { + if html::parse_autolink(p).is_present() { + continue; + } + if html::parse_inline_html(p).is_present() { + continue; + } + let _ = super::parse_textual(p); + continue; + } + + // NOW check for stop token (after constructs that can contain it) + if p.at(stop) { + if bracket_depth == 0 { + break; + } + bracket_depth = bracket_depth.saturating_sub(1); + let _ = super::parse_textual(p); + continue; + } + + if p.at(L_BRACK) { + match policy { + InlineLinksPolicy::NoLinks => { + if !has_nested_link && nested_link_starts_here(p) { + has_nested_link = true; + } + bracket_depth += 1; + let _ = super::parse_textual(p); + continue; + } + InlineLinksPolicy::FullLinks => { + let result = links::parse_link_or_reference(p); + if result.is_present() { + continue; + } + bracket_depth += 1; + let _ = super::parse_textual(p); + continue; + } + } + } + + if matches!(policy, InlineLinksPolicy::FullLinks) && p.at(BANG) && p.nth_at(1, L_BRACK) { + let result = links::parse_image_or_reference(p); + if result.is_present() { + continue; + } + let _ = super::parse_textual(p); + continue; + } + + let parsed = match policy { + InlineLinksPolicy::NoLinks => parse_any_inline_no_links(p), + InlineLinksPolicy::FullLinks => parse_any_inline(p), + }; + if parsed.is_absent() { + break; + } + } + + m.complete(p, MD_INLINE_ITEM_LIST); + p.set_emphasis_context(prev_context); + InlineListUntilResult { has_nested_link } +} + +fn parse_inline_item_list_until_no_links(p: &mut MarkdownParser, stop: MarkdownSyntaxKind) -> bool { + parse_inline_item_list_until_impl(p, stop, InlineLinksPolicy::NoLinks).has_nested_link +} + +/// Parse inline items until `stop` token, allowing full inline parsing including links. +/// Used for image alt text where nested links/images should be fully parsed +/// so their text content can be extracted for the alt attribute. +fn parse_inline_item_list_until(p: &mut MarkdownParser, stop: MarkdownSyntaxKind) { + let _ = parse_inline_item_list_until_impl(p, stop, InlineLinksPolicy::FullLinks); +} + +fn nested_link_starts_here(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + if !p.at(L_BRACK) { + return false; + } + + p.bump(L_BRACK); + let mut depth = 0usize; + + loop { + if p.at(EOF) || p.at_inline_end() { + return false; + } + + if p.at(L_BRACK) { + depth += 1; + p.bump(L_BRACK); + continue; + } + + if p.at(R_BRACK) { + if depth > 0 { + depth -= 1; + p.bump(R_BRACK); + continue; + } + p.bump(R_BRACK); + return p.at(L_PAREN) || p.at(L_BRACK); + } + + p.bump(p.cur()); + } + }) +} + +fn parse_any_inline_no_links(p: &mut MarkdownParser) -> ParsedSyntax { + if p.at(L_BRACK) { + return super::parse_textual(p); + } + + if p.at(BANG) && p.nth_at(1, L_BRACK) { + return links::parse_inline_image(p); + } + + parse_any_inline(p) +} + +/// Dispatch to the appropriate inline parser based on current token. +pub(crate) fn parse_any_inline(p: &mut MarkdownParser) -> ParsedSyntax { + if p.at(MD_HARD_LINE_LITERAL) { + code_span::parse_hard_line(p) + } else if p.at(BACKTICK) || p.at(T!["```"]) { + // Try code span, fall back to literal text if no matching closer exists. + // T!["```"] can appear when backticks are at line start but info string + // contains backticks, making it not a fenced code block (CommonMark examples 138, 145). + let result = code_span::parse_inline_code(p); + if result.is_absent() { + super::parse_textual(p) + } else { + result + } + } else if p.at(DOUBLE_STAR) || p.at(DOUBLE_UNDERSCORE) { + // For cases like `***foo***`, the em match starts at the exact token boundary + // (prefix_len=0) while the strong match starts at offset 1 (prefix_len=1). + // Try italic first to handle nested emphasis correctly, then try strong. + let result = emphasis::parse_inline_italic(p); + if result.is_present() { + return result; + } + let result = emphasis::parse_inline_emphasis(p); + if result.is_present() { + return result; + } + // Neither matched - re-lex to single token and emit just one char as literal. + // This handles cases like `**foo*` where opener is at offset 1. + p.force_relex_emphasis_inline(); + super::parse_textual(p) + } else if p.at(T![*]) || p.at(UNDERSCORE) { + // Try italic, fall back to literal text if flanking rules fail + let result = emphasis::parse_inline_italic(p); + if result.is_absent() { + super::parse_textual(p) + } else { + result + } + } else if p.at(BANG) && p.nth_at(1, L_BRACK) { + // Try image, fall back to literal text if parsing fails + let result = links::parse_inline_image(p); + if result.is_absent() { + super::parse_textual(p) + } else { + result + } + } else if p.at(L_BRACK) { + // Try link, fall back to literal text if parsing fails + let result = links::parse_inline_link(p); + if result.is_absent() { + super::parse_textual(p) + } else { + result + } + } else if p.at(L_ANGLE) { + // Try autolink first (takes priority per CommonMark) + let result = html::parse_autolink(p); + if result.is_present() { + return result; + } + // Then try inline HTML + let result = html::parse_inline_html(p); + if result.is_present() { + return result; + } + // Fall back to textual + super::parse_textual(p) + } else if p.at(MD_ENTITY_LITERAL) { + // Entity or numeric character reference (already validated by lexer) + entities::parse_entity_reference(p) + } else { + super::parse_textual(p) + } +} diff --git a/crates/biome_markdown_parser/src/syntax/link_block.rs b/crates/biome_markdown_parser/src/syntax/link_block.rs new file mode 100644 index 000000000000..8ee0f1e4e291 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/link_block.rs @@ -0,0 +1,722 @@ +//! Link reference definition parsing for Markdown (CommonMark §4.7). +//! +//! A link reference definition is a block-level construct that defines a label +//! for later reference. The syntax is: +//! +//! ```markdown +//! [label]: url "optional title" +//! [label]: url 'optional title' +//! [label]: url (optional title) +//! [label]: +//! ``` +//! +//! These definitions are not rendered but provide targets for reference links. +//! +//! # CommonMark Spec §4.7 Requirements +//! +//! 1. Label is enclosed in `[` and `]`, followed by `:` +//! 2. Label may contain up to 999 characters (no unescaped `]`) +//! 3. Destination can be angle-bracketed `` or bare URL (no whitespace) +//! 4. Optional title can be quoted with `"`, `'`, or `()` +//! 5. Labels are case-insensitive and whitespace-normalized for matching + +use biome_markdown_syntax::MarkdownSyntaxKind::*; +use biome_parser::Parser; +use biome_parser::prelude::ParsedSyntax::{self, *}; + +use crate::MarkdownParser; +use crate::lexer::MarkdownLexContext; +use crate::syntax::reference::normalize_reference_label; +use crate::syntax::{ + LinkDestinationKind, MAX_LINK_DESTINATION_PAREN_DEPTH, ParenDepthResult, + ends_with_unescaped_close, try_update_paren_depth, validate_link_destination_text, +}; + +/// Maximum label length per CommonMark spec (999 characters). +const MAX_LABEL_LENGTH: usize = 999; + +/// Check if we're at the start of a link reference definition. +/// +/// A link reference definition starts with `[` at the beginning of a line +/// (with up to 3 spaces of indentation allowed). +/// +/// We use token-based lookahead to verify the pattern: `[label]: destination` +/// where label doesn't contain unescaped `]` or `[`, and is followed by `:`. +pub(crate) fn at_link_block(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + // Must be at line start (or start of input) + if !p.at_line_start() && !p.at_start_of_input() { + return false; + } + + // Check for up to 3 spaces of indentation (more means indented code block) + if p.line_start_leading_indent() > 3 { + return false; + } + + p.skip_line_indent(3); + + // Must start with `[` + if !p.at(L_BRACK) { + return false; + } + + // Use token-based lookahead to verify this is a valid link reference definition + is_valid_link_definition_lookahead(p) + }) +} + +/// Token-based lookahead to verify a link reference definition. +/// +/// This advances tokens to check: `[label]: destination [title]?` +/// Returns true if the pattern is valid, false otherwise. +/// Does NOT build nodes - just validates the structure. +fn is_valid_link_definition_lookahead(p: &mut MarkdownParser) -> bool { + // Expect [ + if !p.at(L_BRACK) { + return false; + } + p.bump_any(); + + // Parse label: consume tokens until ] or invalid state + // Also collect the label text for normalization check. + let mut label_len = 0; + let mut label_text = String::new(); + loop { + if p.at(EOF) { + return false; + } + if p.at(NEWLINE) && p.at_blank_line() { + return false; // Blank line ends link definition + } + if p.at(R_BRACK) { + break; + } + if p.at(L_BRACK) { + return false; // Unescaped [ inside label not allowed + } + + let text = p.cur_text(); + label_text.push_str(text); + + // Check for escape sequences + if text.starts_with('\\') && text.len() > 1 { + label_len += 1; // Count escaped char + } else { + label_len += text.chars().count(); + } + + if label_len > MAX_LABEL_LENGTH { + return false; + } + p.bump_any(); + } + + // Label must be non-empty + if label_len == 0 { + return false; + } + + // Label must also be non-empty after normalization (e.g., `[\n ]` normalizes to empty) + let normalized = normalize_reference_label(&label_text); + if normalized.is_empty() { + return false; + } + + // Expect ] + if !p.at(R_BRACK) { + return false; + } + p.bump_any(); + + // Expect : immediately (no whitespace allowed per CommonMark) + if !p.at(COLON) { + return false; + } + p.bump_any(); + + // Re-lex the current token in LinkDefinition context so whitespace is tokenized. + p.re_lex_link_definition(); + + // Skip optional whitespace after colon (before destination or newline) + skip_whitespace_tokens(p); + + // Per CommonMark §4.7, destination can be on the next line if there's a + // single non-blank newline after the colon. + if p.at(NEWLINE) { + if p.at_blank_line() { + return false; // Blank line = no destination + } + // Single newline - allow destination on next line + p.bump_link_definition(); + skip_whitespace_tokens(p); + } + + // Destination is required (can be on same line or next line now) + if p.at(EOF) || p.at_blank_line() { + return false; + } + + // Skip destination and track whether there was whitespace after it + let dest_result = skip_destination_tokens(p); + if dest_result == DestinationResult::Invalid { + return false; + } + let had_separator = dest_result == DestinationResult::ValidWithSeparator; + + // Check what follows destination + if p.at(EOF) { + return true; // Valid: destination only, EOF + } + + if p.at(NEWLINE) { + // Check for title on next line (newline counts as separator) + p.bump_link_definition(); + skip_whitespace_tokens(p); + + if at_title_start(p) { + // If title looks valid, it's included in the definition. + // If title has trailing content, it's invalid - but the definition + // is still valid (destination-only). The invalid title line will + // be parsed as a paragraph. Per CommonMark §4.7. + let _ = skip_title_tokens(p); // Ignore result - definition is valid either way + } + // Destination-only is valid, or destination+valid_title is valid + return true; + } + + // Check for optional title on same line - MUST be preceded by whitespace + if at_title_start(p) { + if !had_separator { + // Title without preceding whitespace is invalid (e.g., `(baz)`) + return false; + } + return skip_title_tokens(p); + } + + // Non-whitespace, non-title after destination = invalid trailing text + false +} + +/// Skip whitespace tokens (spaces/tabs) in lookahead. +fn skip_whitespace_tokens(p: &mut MarkdownParser) { + skip_whitespace_tokens_tracked(p); +} + +/// Skip whitespace tokens (spaces/tabs) in lookahead and return whether any were skipped. +fn skip_whitespace_tokens_tracked(p: &mut MarkdownParser) -> bool { + let mut skipped = false; + while !p.at(EOF) && !p.at(NEWLINE) { + let text = p.cur_text(); + if text.chars().all(|c| c == ' ' || c == '\t') && !text.is_empty() { + p.bump_link_definition(); + skipped = true; + } else { + break; + } + } + skipped +} + +/// Check if at a title start token. +fn at_title_start(p: &MarkdownParser) -> bool { + let text = p.cur_text(); + text.starts_with('"') || text.starts_with('\'') || p.at(L_PAREN) +} + +/// Result of skipping destination tokens. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DestinationResult { + /// Invalid destination + Invalid, + /// Valid destination, no trailing whitespace found before title + ValidNoSeparator, + /// Valid destination with trailing whitespace (separator before potential title) + ValidWithSeparator, +} + +/// Skip destination tokens in lookahead. Returns the destination result. +fn skip_destination_tokens(p: &mut MarkdownParser) -> DestinationResult { + // Skip optional leading whitespace before destination + while !p.at(EOF) && !p.at(NEWLINE) { + let text = p.cur_text(); + if text.chars().all(|c| c == ' ' || c == '\t') && !text.is_empty() { + p.bump_link_definition(); + } else { + break; + } + } + + if p.at(L_ANGLE) { + // Angle-bracketed destination + p.bump_link_definition(); + let mut pending_escape = false; + loop { + if p.at(EOF) || p.at(NEWLINE) { + return DestinationResult::Invalid; // Unterminated angle bracket + } + if p.at(R_ANGLE) { + if pending_escape { + if !validate_link_destination_text( + p.cur_text(), + LinkDestinationKind::Enclosed, + &mut pending_escape, + ) { + return DestinationResult::Invalid; + } + p.bump_link_definition(); + continue; + } else { + p.bump_link_definition(); + // Check for trailing whitespace (separator) + let had_sep = skip_whitespace_tokens_tracked(p); + return if had_sep { + DestinationResult::ValidWithSeparator + } else { + DestinationResult::ValidNoSeparator + }; + } + } + if !validate_link_destination_text( + p.cur_text(), + LinkDestinationKind::Enclosed, + &mut pending_escape, + ) { + return DestinationResult::Invalid; + } + p.bump_link_definition(); + } + } else { + // Bare destination with balanced parentheses + let mut paren_depth = 0i32; + let mut has_content = false; + let mut saw_separator = false; + let mut pending_escape = false; + + while !p.at(EOF) && !p.at(NEWLINE) { + let text = p.cur_text(); + // Stop at whitespace + if text.chars().all(|c| c == ' ' || c == '\t') && !text.is_empty() { + if has_content { + saw_separator = true; + } + p.bump_link_definition(); + continue; + } + + if at_title_start(p) && has_content && saw_separator { + // Break here - we've found separator before title + break; + } + + if !validate_link_destination_text(text, LinkDestinationKind::Raw, &mut pending_escape) + { + return DestinationResult::Invalid; + } + + match try_update_paren_depth(text, paren_depth, MAX_LINK_DESTINATION_PAREN_DEPTH) { + ParenDepthResult::Ok(next_depth) => { + has_content = true; + saw_separator = false; + paren_depth = next_depth; + p.bump_link_definition(); + } + ParenDepthResult::DepthExceeded | ParenDepthResult::UnmatchedClose => { + // For link reference definitions, both cases end the destination + break; + } + } + } + if !has_content { + DestinationResult::Invalid + } else if saw_separator { + DestinationResult::ValidWithSeparator + } else { + DestinationResult::ValidNoSeparator + } + } +} + +/// Skip title tokens in lookahead. Returns true if valid (ends at EOL/EOF). +fn skip_title_tokens(p: &mut MarkdownParser) -> bool { + let close_char = if p.cur_text().starts_with('"') { + '"' + } else if p.cur_text().starts_with('\'') { + '\'' + } else if p.at(L_PAREN) { + ')' + } else { + return false; + }; + + // Check if first token is complete (e.g., `"title"`) + let first_text = p.cur_text(); + if first_text.len() >= 2 && ends_with_unescaped_close(first_text, close_char) { + p.bump_link_definition(); + skip_whitespace_tokens(p); + return p.at(EOF) || p.at(NEWLINE); + } + + p.bump_link_definition(); + + // Multi-token title: find closing delimiter + loop { + if p.at(EOF) { + return false; // Unterminated title + } + + // Check for closing delimiter + let is_close = ends_with_unescaped_close(p.cur_text(), close_char); + + if is_close { + p.bump_link_definition(); + skip_whitespace_tokens(p); + return p.at(EOF) || p.at(NEWLINE); + } + + // Titles can span lines, but blank line ends them + if p.at(NEWLINE) && p.at_blank_line() { + return false; + } + + p.bump_link_definition(); + } +} + +/// Parse a link reference definition. +/// +/// Grammar: `MdLinkReferenceDefinition = '[' label: MdLinkLabel ']' ':' destination: MdLinkDestination title: MdLinkTitle?` +/// +/// Returns `Absent` if the current position is not a valid link reference definition. +pub(crate) fn parse_link_block(p: &mut MarkdownParser) -> ParsedSyntax { + if !at_link_block(p) { + return Absent; + } + + let m = p.start(); + + p.skip_line_indent(3); + + // [ - opening bracket + p.expect(L_BRACK); + + // Label - parse until ] + parse_link_label(p); + + // ] - closing bracket + p.expect(R_BRACK); + + // : - separator + p.expect(COLON); + + // Re-lex the current token in LinkDefinition context so whitespace produces + // separate tokens, allowing proper destination/title parsing. + p.re_lex_link_definition(); + + // Destination (required) - in LinkDefinition context, whitespace is separate + parse_link_destination(p); + + // Optional title - can be on same line or next line per CommonMark §4.7 + // First, check for title on same line (at_link_title skips whitespace in lookahead) + if at_link_title(p) { + parse_link_title(p); + } else { + // Check for title on next line - need to skip trailing whitespace first + // Also validate that the title is complete and has no trailing content + let has_valid_title_after_newline = p.lookahead(|p| { + while is_whitespace_token(p) { + p.bump_link_definition(); + } + if p.at(NEWLINE) && !p.at_blank_line() { + // Check if there's a title starter on next line + if !title_on_next_line(p) { + return false; + } + // Also validate that the title is complete (no trailing content) + p.bump_link_definition(); // consume newline + skip_whitespace_tokens(p); // skip leading whitespace on title line + skip_title_tokens(p) // returns true only if title ends at EOL/EOF + } else { + false + } + }); + + if has_valid_title_after_newline { + // Title is on the next line per CommonMark §4.7 + // Include trailing whitespace + newline + leading whitespace as part of title + parse_link_title_with_trailing_ws(p); + } + } + + Present(m.complete(p, MD_LINK_REFERENCE_DEFINITION)) +} + +/// Parse the label part of a link reference definition. +/// +/// Grammar: MdLinkLabel = content: MdInlineItemList +/// +/// The label is everything between `[` and `]`, excluding the brackets themselves. +fn parse_link_label(p: &mut MarkdownParser) { + let m = p.start(); + let list = p.start(); + + while !p.at(R_BRACK) && !p.at(EOF) { + if p.at(NEWLINE) && p.at_blank_line() { + break; + } + bump_textual(p); + } + + list.complete(p, MD_INLINE_ITEM_LIST); + m.complete(p, MD_LINK_LABEL); +} + +/// Parse the destination part of a link reference definition. +/// +/// Grammar: MdLinkDestination = content: MdInlineItemList +/// +/// Destination can be: +/// - Angle-bracketed: `` +/// - Bare URL: `url-without-spaces` (balanced parentheses allowed per CommonMark) +/// +/// Uses LinkDefinition lex context so whitespace produces separate tokens. +fn parse_link_destination(p: &mut MarkdownParser) { + let m = p.start(); + let list = p.start(); + + // Include optional whitespace before destination in the destination node. + while is_whitespace_token(p) { + bump_textual_link_def(p); + } + + // Per CommonMark §4.7, destination can be on the next line + if p.at(NEWLINE) && !p.at_blank_line() { + bump_textual_link_def(p); + while is_whitespace_token(p) { + bump_textual_link_def(p); + } + } + + if p.at(L_ANGLE) { + // Angle-bracketed: consume < ... > + bump_textual_link_def(p); + while !p.at(R_ANGLE) && !p.at(EOF) && !p.at(NEWLINE) { + bump_textual_link_def(p); + } + if p.at(R_ANGLE) { + bump_textual_link_def(p); + } + } else { + // Bare URL with balanced parentheses + let mut paren_depth: i32 = 0; + + while !p.at(EOF) && !p.at(NEWLINE) { + if is_whitespace_token(p) { + break; // Bare destination stops at first whitespace + } + + let text = p.cur_text(); + match try_update_paren_depth(text, paren_depth, MAX_LINK_DESTINATION_PAREN_DEPTH) { + ParenDepthResult::Ok(next_depth) => { + paren_depth = next_depth; + bump_textual_link_def(p); + } + ParenDepthResult::DepthExceeded | ParenDepthResult::UnmatchedClose => { + break; + } + } + } + } + + list.complete(p, MD_INLINE_ITEM_LIST); + m.complete(p, MD_LINK_DESTINATION); +} + +/// Consume the current token as MdTextual using LinkDefinition context. +/// This ensures whitespace produces separate tokens for destination/title parsing. +fn bump_textual_link_def(p: &mut MarkdownParser) { + let item = p.start(); + p.bump_remap_with_context(MD_TEXTUAL_LITERAL, MarkdownLexContext::LinkDefinition); + item.complete(p, MD_TEXTUAL); +} + +/// Check if we're at the start of a link title. +/// +/// Title starts with `"`, `'`, or `(` but may be preceded by whitespace. +/// Uses lookahead to skip whitespace and check for title delimiter. +fn at_link_title(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + // Skip whitespace before title + while is_whitespace_token(p) { + p.bump_link_definition(); + } + let text = p.cur_text(); + text.starts_with('"') || text.starts_with('\'') || p.at(L_PAREN) + }) +} + +/// Check if there's a title on the next line (when at NEWLINE). +/// +/// Per CommonMark §4.7, title can appear on the line following destination. +/// This looks ahead past the newline and whitespace to check for title starter. +fn title_on_next_line(p: &MarkdownParser) -> bool { + if !p.at(NEWLINE) { + return false; + } + + let source = p.source_after_current(); + let newline_len = p.cur_text().len(); + if source.len() <= newline_len { + return false; + } + + let after_newline = &source[newline_len..]; + let trimmed = after_newline.trim_start_matches([' ', '\t']); + + // Check for title starter + trimmed.starts_with('"') || trimmed.starts_with('\'') || trimmed.starts_with('(') +} +/// Parse a link title that appears on next line, including trailing whitespace before newline. +/// +/// This is used when there's trailing whitespace after the destination but before +/// the newline that precedes the title. The trailing whitespace is included in the +/// title node to maintain the grammar structure. +fn parse_link_title_with_trailing_ws(p: &mut MarkdownParser) { + let m = p.start(); + let list = p.start(); + + // Include trailing whitespace after destination + while is_whitespace_token(p) { + bump_textual_link_def(p); + } + + // Include the newline + if p.at(NEWLINE) { + bump_textual_link_def(p); + } + + // Include leading whitespace on title line + while is_whitespace_token(p) { + bump_textual_link_def(p); + } + + // Force re-lex in Regular context so title content doesn't split at whitespace + p.force_relex_regular(); + // Parse the actual title content + parse_title_content(p, get_title_close_char(p)); + + list.complete(p, MD_INLINE_ITEM_LIST); + m.complete(p, MD_LINK_TITLE); +} + +/// Parse the optional title part of a link reference definition. +/// +/// Grammar: MdLinkTitle = content: MdInlineItemList +fn parse_link_title(p: &mut MarkdownParser) { + let m = p.start(); + let list = p.start(); + + // Include optional filler whitespace before title + while is_whitespace_token(p) { + bump_textual_link_def(p); + } + + // Force re-lex in Regular context so title content doesn't split at whitespace + p.force_relex_regular(); + parse_title_content(p, get_title_close_char(p)); + + list.complete(p, MD_INLINE_ITEM_LIST); + m.complete(p, MD_LINK_TITLE); +} + +/// Get the closing character for a title based on current token. +/// Returns None if not at a title start. +fn get_title_close_char(p: &MarkdownParser) -> Option { + let text = p.cur_text(); + if text.starts_with('"') { + Some('"') + } else if text.starts_with('\'') { + Some('\'') + } else if p.at(L_PAREN) { + Some(')') + } else { + None + } +} + +/// Parse title content until closing delimiter, including trailing whitespace. +/// +/// Inside title quotes, we use Regular context so whitespace doesn't split tokens. +/// Trailing whitespace after the title is also consumed to prevent spurious paragraphs. +fn parse_title_content(p: &mut MarkdownParser, close_char: Option) { + let Some(close_char) = close_char else { + return; + }; + + // Check if first token is complete title (e.g., `"title"`) + let text = p.cur_text(); + let is_complete = text.len() >= 2 + && ((close_char == ')' && text.ends_with(')')) + || (close_char != ')' && text.ends_with(close_char))); + + // Bump the opening quote/first token using LinkDefinition context (for whitespace handling before) + bump_textual_link_def(p); + + if is_complete { + // Consume trailing whitespace after title (before newline) + p.re_lex_link_definition(); + while is_whitespace_token(p) { + bump_textual_link_def(p); + } + return; + } + + // Multi-token title: consume until closing delimiter + // Use Regular context inside title so whitespace doesn't split tokens + loop { + if p.at(EOF) { + break; + } + + // Check for closing delimiter (must be unescaped) + let is_close = if close_char == ')' { + p.at(R_PAREN) + } else { + ends_with_unescaped_close(p.cur_text(), close_char) + }; + if is_close { + // Use Regular context for title content + bump_textual(p); + // Consume trailing whitespace after title (before newline) + p.re_lex_link_definition(); + while is_whitespace_token(p) { + bump_textual_link_def(p); + } + break; + } + + // Stop at blank line + if p.at_blank_line() { + break; + } + + // Use Regular context for title content so whitespace doesn't split + bump_textual(p); + } +} + +/// Check if current token is whitespace (space or tab). +fn is_whitespace_token(p: &MarkdownParser) -> bool { + let text = p.cur_text(); + !text.is_empty() && text.chars().all(|c| c == ' ' || c == '\t') +} + +/// Consume the current token as an MdTextual node. +/// +/// This is a helper to reduce boilerplate for the common pattern: +/// `let item = p.start(); p.bump_remap(MD_TEXTUAL_LITERAL); item.complete(p, MD_TEXTUAL);` +fn bump_textual(p: &mut MarkdownParser) { + let item = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + item.complete(p, MD_TEXTUAL); +} diff --git a/crates/biome_markdown_parser/src/syntax/list.rs b/crates/biome_markdown_parser/src/syntax/list.rs new file mode 100644 index 000000000000..89051412a255 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/list.rs @@ -0,0 +1,2319 @@ +//! List parsing for Markdown (CommonMark §5.2-5.3). +//! +//! Supports bullet lists (`-`, `*`, `+`) and ordered lists (`1.`, `2.`, etc.). +//! Also supports multi-line list items via continuation lines and nested lists. +//! +//! # CommonMark Specification References +//! +//! - **§5.2 List items**: A list item is a sequence of blocks that belong to a +//! single list marker. Items can span multiple lines with proper indentation. +//! - **§5.3 Lists**: A list is a sequence of list items of the same type (bullet +//! or ordered) that are not separated by blank lines (tight) or are (loose). +//! +//! ## Bullet List Markers (§5.2) +//! - `-` (hyphen-minus) +//! - `*` (asterisk) +//! - `+` (plus sign) +//! +//! ## Ordered List Markers (§5.2) +//! - `1.` through `999999999.` (1-9 digits followed by `.`) +//! - `1)` through `999999999)` (1-9 digits followed by `)`) +//! +//! ## Depth Limits +//! +//! To prevent stack overflow from pathological input (deeply nested lists), +//! nesting depth is limited by `MarkdownParseOptions::max_nesting_depth` +//! (default: 100). Deeper nesting emits a diagnostic and treats additional +//! list markers as content. +//! +//! ## Current Limitations +//! +//! - **Tight vs loose lists**: Not yet tracked; affects HTML output formatting. +//! - **List interruption rules**: Some constructs can interrupt lists; not all +//! rules from CommonMark are implemented. + +use biome_markdown_syntax::T; +use biome_markdown_syntax::kind::MarkdownSyntaxKind::{self, *}; +use biome_parser::parse_lists::ParseNodeList; +use biome_parser::parse_recovery::{ParseRecoveryTokenSet, RecoveryResult}; +use biome_parser::prelude::ParsedSyntax::{self, *}; +use biome_parser::prelude::{CompletedMarker, Marker, ParseDiagnostic, TokenSet}; +use biome_parser::{Parser, token_set}; + +use biome_rowan::TextRange; + +use crate::MarkdownParser; +use crate::syntax::fenced_code_block::parse_fenced_code_block; +use crate::syntax::parse_any_block_with_indent_code_policy; +use crate::syntax::parse_error::list_nesting_too_deep; +use crate::syntax::quote::{ + consume_quote_prefix, consume_quote_prefix_without_virtual, has_quote_prefix, + parse_quote_block_list, +}; +use crate::syntax::{ + INDENT_CODE_BLOCK_SPACES, TAB_STOP_SPACES, at_block_interrupt, at_indent_code_block, + is_paragraph_like, +}; + +/// Tokens that start a new block (used for recovery) +const BLOCK_RECOVERY_SET: TokenSet = token_set![ + T![-], + T![*], + T![+], + T![>], + T![#], + TRIPLE_BACKTICK, + TRIPLE_TILDE, + MD_ORDERED_LIST_MARKER +]; +/// Compute the marker indent for list parsing. +/// +/// For normal cases, this returns the leading whitespace count from +/// `line_start_leading_indent()`. For virtual line start cases (nested list +/// detection), we compute the actual column position from the source text +/// to ensure correct indented code block detection in nested lists. +/// +/// Raw source scan is required because leading whitespace may be consumed +/// as trivia during list parsing, so token-based lookahead loses the true +/// column needed for CommonMark's indent rules. +fn compute_marker_indent(p: &MarkdownParser) -> usize { + if p.state().virtual_line_start == Some(p.cur_range().start()) { + // Inside block quotes, treat the virtual line start as column 0. + if p.state().block_quote_depth > 0 { + return p.line_start_leading_indent(); + } + + // Virtual line start: compute actual column from source text. + // The leading whitespace was skipped as trivia, but we need the + // real column for indented code block detection in nested lists. + let source = p.source().source_text(); + let pos: usize = p.cur_range().start().into(); + + // Find the start of the current line + let line_start = source[..pos].rfind('\n').map_or(0, |i| i + 1); + + // Count columns from line start to current position + let mut column = 0; + for c in source[line_start..pos].chars() { + match c { + '\t' => column += TAB_STOP_SPACES - (column % TAB_STOP_SPACES), + _ => column += 1, + } + } + column + } else { + // Normal case: use the standard leading indent count + p.source().line_start_leading_indent() + } +} + +/// Check if we're at the start of a bullet list item (`-`, `*`, or `+`). +/// +/// A bullet list marker at line start followed by content is a list item. +/// We check that it's at line start and not a thematic break. +pub(crate) fn at_bullet_list_item(p: &mut MarkdownParser) -> bool { + at_bullet_list_item_with_base_indent(p, list_marker_base_indent(p)) +} + +fn list_marker_base_indent(p: &MarkdownParser) -> usize { + p.state().list_item_required_indent +} + +fn list_item_within_indent(p: &mut MarkdownParser, base_indent: usize) -> bool { + if !p.at_line_start() { + return false; + } + + let indent = p.line_start_leading_indent(); + let base_indent = + if p.state().virtual_line_start == Some(p.cur_range().start()) && base_indent > 0 { + 0 + } else { + base_indent + }; + + if base_indent == 0 { + indent <= 3 + } else { + indent >= base_indent && indent <= base_indent + 3 + } +} + +fn skip_leading_whitespace_tokens(p: &mut MarkdownParser) { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } +} + +fn skip_list_marker_indent(p: &mut MarkdownParser) { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } +} + +fn is_whitespace_only(text: &str) -> bool { + !text.is_empty() && text.chars().all(|c| c == ' ' || c == '\t') +} + +/// Check if the remaining content forms a thematic break pattern. +/// +/// Per CommonMark §4.1, a thematic break is 3 or more matching characters +/// (`*`, `-`, or `_`) on a line by itself, optionally with spaces between them. +/// +/// This function checks the source text directly since the lexer may not +/// produce MD_THEMATIC_BREAK_LITERAL in all contexts (e.g., after list markers). +/// Token lookahead is insufficient here because the marker may be lexed as +/// textual content within list item contexts. +fn is_thematic_break_pattern(p: &mut MarkdownParser) -> bool { + // Get the remaining text on the current line + let source = p.source_after_current(); + + // Find the end of the line + let line_end = source.find('\n').unwrap_or(source.len()); + let line = &source[..line_end]; + + // Determine which character to check for + let first_char = line.trim_start().chars().next(); + let break_char = match first_char { + Some('*' | '-' | '_') => first_char.unwrap(), + _ => return false, + }; + + // Count the break characters (must be at least 3) + let mut count = 0usize; + for c in line.chars() { + if c == break_char { + count += 1; + } else if c != ' ' && c != '\t' { + // Non-whitespace, non-break character - not a thematic break + return false; + } + } + + count >= 3 +} + +fn at_bullet_list_item_with_base_indent(p: &mut MarkdownParser, base_indent: usize) -> bool { + p.lookahead(|p| { + if !list_item_within_indent(p, base_indent) { + return false; + } + + skip_leading_whitespace_tokens(p); + + // Check for -, *, or + at the start of a line + // Thematic breaks (--- or ***) are lexed as MD_THEMATIC_BREAK_LITERAL, + // so if we see MINUS, STAR, or PLUS, it's a single character marker. + // A single-dash setext underline token can also represent an empty list item. + if p.at(MD_SETEXT_UNDERLINE_LITERAL) { + if !is_single_dash_setext_marker(p.cur_text()) { + return false; + } + } else if p.at(MD_TEXTUAL_LITERAL) { + if !is_textual_bullet_marker(p.cur_text()) { + return false; + } + } else if !p.at(T![-]) && !p.at(T![*]) && !p.at(T![+]) { + return false; + } + + if p.at(MD_SETEXT_UNDERLINE_LITERAL) { + p.bump_remap(T![-]); + } else if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == "-" { + p.bump_remap(T![-]); + } else if text == "*" { + p.bump_remap(T![*]); + } else if text == "+" { + p.bump_remap(T![+]); + } else { + return false; + } + } else { + p.bump(p.cur()); + } + marker_followed_by_whitespace_or_eol(p) + }) +} + +pub(crate) fn marker_followed_by_whitespace_or_eol(p: &mut MarkdownParser) -> bool { + if p.at(NEWLINE) || p.at(T![EOF]) { + return true; + } + + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + return text.starts_with(' ') || text.starts_with('\t'); + } + + false +} + +/// Tracks blank-line information for a list item. +#[derive(Default)] +struct ListItemBlankInfo { + /// True if a blank line occurred anywhere within the item content. + has_blank_line: bool, + /// True if the item ended with a blank line. + ends_with_blank_line: bool, +} + +fn skip_blank_lines_between_items( + p: &mut MarkdownParser, + has_item_after_blank_lines: fn(&mut MarkdownParser) -> bool, + is_tight: &mut bool, + last_item_ends_with_blank: &mut bool, +) { + // Skip blank lines between list items. + // Per CommonMark §5.3, blank lines between items make the list loose + // but don't end the list. + // + // Any NEWLINE we see at this position (after the item-terminating newline) + // represents a blank line between items. We don't use at_blank_line() here + // because it checks if what comes AFTER the newline is blank, but we're + // already past one newline - any additional newlines ARE blank lines. + while p.at(NEWLINE) { + // Only skip if there's another list item after the blank lines + if !has_item_after_blank_lines(p) { + break; + } + // Blank lines between items make the list loose + *is_tight = false; + *last_item_ends_with_blank = true; + // Skip the blank line as trivia (no tree node created) + p.parse_as_skipped_trivia_tokens(|p| p.bump(NEWLINE)); + } +} + +fn update_list_tightness( + blank_info: ListItemBlankInfo, + is_tight: &mut bool, + last_item_ends_with_blank: &mut bool, +) { + // Blank line between items makes the list loose + if *last_item_ends_with_blank { + *is_tight = false; + } + + // Blank line inside an item makes the list loose + if blank_info.has_blank_line { + *is_tight = false; + } + + *last_item_ends_with_blank = blank_info.ends_with_blank_line; +} + +fn parse_list_element_common( + p: &mut MarkdownParser, + marker_state: &mut Option, + current_marker: FMarker, + parse_item: FParse, + has_item_after_blank_lines: fn(&mut MarkdownParser) -> bool, + is_tight: &mut bool, + last_item_ends_with_blank: &mut bool, +) -> ParsedSyntax +where + FMarker: Fn(&mut MarkdownParser) -> Option, + FParse: Fn(&mut MarkdownParser) -> (ParsedSyntax, ListItemBlankInfo), +{ + let prev_is_tight = *is_tight; + let prev_last_item_ends_with_blank = *last_item_ends_with_blank; + + skip_blank_lines_between_items( + p, + has_item_after_blank_lines, + is_tight, + last_item_ends_with_blank, + ); + + if marker_state.is_none() { + *marker_state = current_marker(p); + } + + let (parsed, blank_info) = parse_item(p); + + if parsed.is_absent() { + // The blank lines we skipped didn't lead to a valid item in this list. + // Restore tightness — the blank lines belong to a parent context. + *is_tight = prev_is_tight; + *last_item_ends_with_blank = prev_last_item_ends_with_blank; + } else { + update_list_tightness(blank_info, is_tight, last_item_ends_with_blank); + } + + parsed +} + +fn is_at_list_end_common( + p: &mut MarkdownParser, + marker_state: Option, + at_list_item: FAt, + current_marker: FMarker, + has_item_after_blank_lines: fn(&mut MarkdownParser) -> bool, + handle_newline: FNewline, +) -> bool +where + M: Copy + PartialEq, + FAt: Fn(&mut MarkdownParser) -> bool, + FMarker: Fn(&mut MarkdownParser) -> Option, + FNewline: Fn(&mut MarkdownParser, Option) -> Option, +{ + let quote_depth = p.state().block_quote_depth; + let at_virtual_line_start = p.state().virtual_line_start == Some(p.cur_range().start()); + if quote_depth > 0 + && !at_virtual_line_start + && (p.at_line_start() || p.has_preceding_line_break()) + && !has_quote_prefix(p, quote_depth) + { + return true; + } + + // Check if we're directly at a list marker + if at_list_item(p) { + if let (Some(current), Some(next)) = (marker_state, current_marker(p)) + && current != next + { + return true; + } + return false; + } + + // If at a blank line, look ahead to see if there's another list item. + // Per CommonMark §5.3, blank lines between items make the list loose, + // but don't end the list. + if p.at_line_start() && at_blank_line_start(p) { + return !has_item_after_blank_lines(p); + } + + // Also check if we're directly AT a NEWLINE token (blank line) + // This handles the case where we're at the newline itself, not after it + if p.at(NEWLINE) { + if let Some(result) = handle_newline(p, marker_state) { + return result; + } + return !has_item_after_blank_lines(p); + } + + // Not at a marker and not at a blank line with continuation + true +} + +/// Struct implementing `ParseNodeList` for bullet lists. +struct BulletList { + /// A list is tight if there are no blank lines between items or inside items. + is_tight: bool, + /// Whether the last parsed item ended with a blank line. + last_item_ends_with_blank: bool, + /// The marker kind for this list (`-`, `*`, or `+`). + marker_kind: Option, + /// The indentation level of the list marker (0 for top-level). + marker_indent: usize, +} + +impl BulletList { + fn new(marker_indent: usize) -> Self { + Self { + is_tight: true, + last_item_ends_with_blank: false, + marker_kind: None, + marker_indent, + } + } +} + +impl ParseNodeList for BulletList { + type Kind = MarkdownSyntaxKind; + type Parser<'source> = MarkdownParser<'source>; + + const LIST_KIND: Self::Kind = MD_BULLET_LIST; + + fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { + parse_list_element_common( + p, + &mut self.marker_kind, + current_bullet_marker, + parse_bullet, + has_bullet_item_after_blank_lines, + &mut self.is_tight, + &mut self.last_item_ends_with_blank, + ) + } + + fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { + let marker_indent = self.marker_indent; + + // Check blank line at line start with indent awareness BEFORE + // delegating to is_at_list_end_common (which uses non-indent-aware check). + if p.at_line_start() && at_blank_line_start(p) { + let result = !has_bullet_item_after_blank_lines_at_indent(p, marker_indent); + + return result; + } + + is_at_list_end_common( + p, + self.marker_kind, + at_bullet_list_item, + current_bullet_marker, + has_bullet_item_after_blank_lines, + |p, _marker_kind| { + let next_is_bullet_at_indent = p.lookahead(|p| { + p.bump(NEWLINE); + // Count indent before marker (tabs expand to next tab stop) + let mut indent = 0usize; + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " { + indent += 1; + p.bump(MD_TEXTUAL_LITERAL); + } else if text == "\t" { + indent += TAB_STOP_SPACES - (indent % TAB_STOP_SPACES); + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + // Check indent matches this list's marker indent + let indent_ok = if marker_indent == 0 { + indent <= 3 + } else { + indent >= marker_indent && indent <= marker_indent + 3 + }; + if !indent_ok { + return false; + } + if p.at(T![-]) || p.at(T![*]) || p.at(T![+]) { + p.bump(p.cur()); + return marker_followed_by_whitespace_or_eol(p); + } + false + }); + if next_is_bullet_at_indent { + Some(false) + } else { + // Check if bullet after blank lines is at correct indent + let has_item = p.lookahead(|p| { + has_bullet_item_after_blank_lines_at_indent(p, marker_indent) + }); + Some(!has_item) + } + }, + ) + } + + fn recover( + &mut self, + p: &mut Self::Parser<'_>, + parsed_element: ParsedSyntax, + ) -> RecoveryResult { + parsed_element.or_recover_with_token_set( + p, + &ParseRecoveryTokenSet::new(MD_BOGUS_BULLET, BLOCK_RECOVERY_SET) + .enable_recovery_on_line_break(), + expected_bullet, + ) + } + + fn finish_list(&mut self, p: &mut Self::Parser<'_>, m: Marker) -> CompletedMarker { + let completed = m.complete(p, Self::LIST_KIND); + let range = completed.range(p); + + p.record_list_tightness(range, self.is_tight); + completed + } +} + +fn current_bullet_marker(p: &mut MarkdownParser) -> Option { + p.lookahead(|p| { + if !p.at_line_start() { + return None; + } + + skip_leading_whitespace_tokens(p); + + if p.at(MD_SETEXT_UNDERLINE_LITERAL) { + if is_single_dash_setext_marker(p.cur_text()) { + return Some(T![-]); + } + return None; + } + + if p.at(MD_TEXTUAL_LITERAL) { + return match p.cur_text() { + "-" => Some(T![-]), + "*" => Some(T![*]), + "+" => Some(T![+]), + _ => None, + }; + } + + if p.at(T![-]) { + return Some(T![-]); + } + if p.at(T![*]) { + return Some(T![*]); + } + if p.at(T![+]) { + return Some(T![+]); + } + + None + }) +} + +/// Error builder for bullet list recovery +fn expected_bullet(p: &MarkdownParser, range: TextRange) -> ParseDiagnostic { + p.err_builder("Expected a list item", range) + .with_hint("List items start with `-`, `*`, or `+` at the beginning of a line") +} + +/// Parse a bullet list item. +/// +/// Grammar: +/// MdBulletListItem = MdBulletList +/// MdBulletList = MdBullet* +/// MdBullet = bullet: ('-' | '*') content: MdBlockList +/// +/// Parses consecutive bullet items into a single list. +/// +/// Nesting is limited to `MarkdownParseOptions::max_nesting_depth` to prevent stack overflow. +pub(crate) fn parse_bullet_list_item(p: &mut MarkdownParser) -> ParsedSyntax { + if !at_bullet_list_item(p) { + return Absent; + } + + // Check depth limit before parsing + let max_nesting_depth = p.options().max_nesting_depth; + if p.state().list_nesting_depth >= max_nesting_depth { + // Emit diagnostic and treat as content + let range = p.cur_range(); + p.error(list_nesting_too_deep(p, range, max_nesting_depth)); + skip_list_marker_indent(p); + if p.at(MD_SETEXT_UNDERLINE_LITERAL) { + p.parse_as_skipped_trivia_tokens(|p| p.bump_remap(T![-])); + } else if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == "-" { + p.parse_as_skipped_trivia_tokens(|p| p.bump_remap(T![-])); + } else if text == "*" { + p.parse_as_skipped_trivia_tokens(|p| p.bump_remap(T![*])); + } else if text == "+" { + p.parse_as_skipped_trivia_tokens(|p| p.bump_remap(T![+])); + } + } else if p.at(T![-]) || p.at(T![*]) || p.at(T![+]) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(p.cur())); + } + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text.starts_with(' ') || text.starts_with('\t') { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + } + return Absent; + } + + let item_m = p.start(); + + // Increment list depth + p.state_mut().list_nesting_depth += 1; + + // Compute the marker indent (leading whitespace before the first marker) + let marker_indent = compute_marker_indent(p); + + // Use ParseNodeList to parse the list with proper recovery + let mut list_helper = BulletList::new(marker_indent); + list_helper.parse_list(p); + + // Decrement list depth + p.state_mut().list_nesting_depth -= 1; + + Present(item_m.complete(p, MD_BULLET_LIST_ITEM)) +} + +/// Parse a single bullet (marker + content). +/// +/// Returns `Present` if a bullet was successfully parsed, `Absent` otherwise. +/// Also returns blank-line information for the list item. +fn parse_bullet(p: &mut MarkdownParser) -> (ParsedSyntax, ListItemBlankInfo) { + // Must be at a bullet marker at line start + if !at_bullet_list_item(p) { + return (Absent, ListItemBlankInfo::default()); + } + + let m = p.start(); + + // Compute the marker indent, handling both normal and virtual line start cases. + // For virtual line start (nested list detection), we compute the actual column + // to ensure correct indented code block detection. + let marker_indent = compute_marker_indent(p); + skip_list_marker_indent(p); + + // Bullet marker is 1 character (-, *, or +) + let marker_width = 1; + + // Bump the bullet marker (-, *, or +) + let mut marker_token_text = None; + if p.at(MD_SETEXT_UNDERLINE_LITERAL) && is_single_dash_setext_marker(p.cur_text()) { + marker_token_text = Some(p.cur_text().to_string()); + p.bump_remap(T![-]); + } else if p.at(MD_TEXTUAL_LITERAL) && is_textual_bullet_marker(p.cur_text()) { + let text = p.cur_text().to_string(); + marker_token_text = Some(text.clone()); + if text == "-" { + p.bump_remap(T![-]); + } else if text == "*" { + p.bump_remap(T![*]); + } else { + p.bump_remap(T![+]); + } + } else if p.at(T![-]) { + p.bump(T![-]); + } else if p.at(T![*]) { + p.bump(T![*]); + } else { + p.bump(T![+]); + } + + // Count spaces after marker to determine required indentation. + // Per CommonMark §5.2, content aligns to first non-space after marker. + // + // For the setext-remapped case (marker_token_text is Some), the token includes + // trailing spaces before the newline. This means the first line is empty + // (marker + whitespace + newline), and the trailing spaces shouldn't count + // for indentation purposes. Per CommonMark, the required indent is marker_width + 1. + let (spaces_after_marker, first_line_empty) = if let Some(text) = marker_token_text.as_deref() { + // Setext token case: token is "- " or "- " etc. followed by newline + // The first line is empty, so use minimum indent (marker_width + 1) + let spaces = count_spaces_after_dash_in_token(text, marker_indent + marker_width); + (spaces, true) + } else { + let spaces = + count_spaces_after_marker(p.source_after_current(), marker_indent + marker_width); + // Check if first line is empty by looking at what follows + let first_empty = p.lookahead(|p| { + // Skip any whitespace + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + // If we hit newline or EOF, first line is empty + p.at(NEWLINE) || p.at(T![EOF]) + }); + (spaces, first_empty) + }; + + // Set required indent for continuation lines + // Required indent = marker width + spaces after marker (minimum 1) + // BUT: if first line is empty (marker + whitespace + newline), use minimum indent + let prev_required_indent = p.state().list_item_required_indent; + let prev_marker_indent = p.state().list_item_marker_indent; + p.state_mut().list_item_required_indent = if spaces_after_marker > INDENT_CODE_BLOCK_SPACES { + marker_indent + marker_width + 1 + } else if first_line_empty { + // Empty first line: use minimum indent (marker + 1 space) + marker_indent + marker_width + 1 + } else { + marker_indent + marker_width + spaces_after_marker.max(1) + }; + p.state_mut().list_item_marker_indent = marker_indent; + + // Parse block content (MD_BLOCK_LIST) + let blank_info = parse_list_item_block_content(p, spaces_after_marker); + + // Restore previous required indent + p.state_mut().list_item_required_indent = prev_required_indent; + p.state_mut().list_item_marker_indent = prev_marker_indent; + + let completed = m.complete(p, MD_BULLET); + let range = completed.range(p); + let indent = marker_indent + marker_width + spaces_after_marker.max(1); + p.record_list_item_indent( + range, + indent, + marker_indent, + marker_width, + spaces_after_marker, + ); + (Present(completed), blank_info) +} + +/// Check if we're at the start of an ordered list item (e.g., "1.", "2)"). +/// +/// An ordered list marker is a sequence of 1-9 digits followed by `.` or `)`, +/// at the start of a line. +pub(crate) fn at_order_list_item(p: &mut MarkdownParser) -> bool { + at_order_list_item_with_base_indent(p, list_marker_base_indent(p)) +} + +fn at_order_list_item_with_base_indent(p: &mut MarkdownParser, base_indent: usize) -> bool { + p.lookahead(|p| { + if !list_item_within_indent(p, base_indent) { + return false; + } + + skip_leading_whitespace_tokens(p); + + // Check for ordered list marker token at line start + if !p.at(MD_ORDERED_LIST_MARKER) { + return false; + } + + p.bump(MD_ORDERED_LIST_MARKER); + marker_followed_by_whitespace_or_eol(p) + }) +} + +/// Struct implementing `ParseNodeList` for ordered lists. +struct OrderedList { + /// A list is tight if there are no blank lines between items or inside items. + is_tight: bool, + /// Whether the last parsed item ended with a blank line. + last_item_ends_with_blank: bool, + /// The delimiter for this ordered list (`.` or `)`). + marker_delim: Option, +} + +impl OrderedList { + fn new() -> Self { + Self { + is_tight: true, + last_item_ends_with_blank: false, + marker_delim: None, + } + } +} + +impl ParseNodeList for OrderedList { + type Kind = MarkdownSyntaxKind; + type Parser<'source> = MarkdownParser<'source>; + + const LIST_KIND: Self::Kind = MD_BULLET_LIST; // Reuse bullet list node structure + + fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { + parse_list_element_common( + p, + &mut self.marker_delim, + current_ordered_delim, + parse_ordered_bullet, + has_ordered_item_after_blank_lines, + &mut self.is_tight, + &mut self.last_item_ends_with_blank, + ) + } + + fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { + is_at_list_end_common( + p, + self.marker_delim, + at_order_list_item, + current_ordered_delim, + has_ordered_item_after_blank_lines, + |p, marker_delim| { + let next_is_ordered = p.lookahead(|p| { + p.bump(NEWLINE); + skip_leading_whitespace_tokens(p); + if p.at(MD_ORDERED_LIST_MARKER) { + p.bump(MD_ORDERED_LIST_MARKER); + return marker_followed_by_whitespace_or_eol(p); + } + false + }); + if next_is_ordered { + if let (Some(current_delim), Some(next_delim)) = + (marker_delim, current_ordered_delim(p)) + && current_delim != next_delim + { + return Some(true); + } + return Some(false); + } + Some(!has_ordered_item_after_blank_lines(p)) + }, + ) + } + + fn recover( + &mut self, + p: &mut Self::Parser<'_>, + parsed_element: ParsedSyntax, + ) -> RecoveryResult { + parsed_element.or_recover_with_token_set( + p, + &ParseRecoveryTokenSet::new(MD_BOGUS_BULLET, BLOCK_RECOVERY_SET) + .enable_recovery_on_line_break(), + expected_ordered_item, + ) + } + + fn finish_list(&mut self, p: &mut Self::Parser<'_>, m: Marker) -> CompletedMarker { + let completed = m.complete(p, Self::LIST_KIND); + let range = completed.range(p); + p.record_list_tightness(range, self.is_tight); + completed + } +} + +fn current_ordered_delim(p: &mut MarkdownParser) -> Option { + p.lookahead(|p| { + if !p.at_line_start() { + return None; + } + + skip_leading_whitespace_tokens(p); + + if !p.at(MD_ORDERED_LIST_MARKER) { + return None; + } + + let text = p.cur_text(); + text.chars().last().filter(|c| *c == '.' || *c == ')') + }) +} + +/// Error builder for ordered list recovery +fn expected_ordered_item(p: &MarkdownParser, range: TextRange) -> ParseDiagnostic { + p.err_builder("Expected an ordered list item", range) + .with_hint("Ordered list items start with a number followed by `.` or `)` at the beginning of a line") +} + +/// Parse an ordered list item. +/// +/// Grammar: +/// MdOrderListItem = MdBulletList (reusing bullet list structure) +/// +/// Parses consecutive ordered items into a single list. +/// +/// Nesting is limited to `MarkdownParseOptions::max_nesting_depth` to prevent stack overflow. +pub(crate) fn parse_order_list_item(p: &mut MarkdownParser) -> ParsedSyntax { + if !at_order_list_item(p) { + return Absent; + } + + // Check depth limit before parsing + let max_nesting_depth = p.options().max_nesting_depth; + if p.state().list_nesting_depth >= max_nesting_depth { + // Emit diagnostic and treat as content + let range = p.cur_range(); + p.error(list_nesting_too_deep(p, range, max_nesting_depth)); + skip_list_marker_indent(p); + if p.at(MD_ORDERED_LIST_MARKER) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_ORDERED_LIST_MARKER)); + } + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text.starts_with(' ') || text.starts_with('\t') { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + } + return Absent; + } + + let item_m = p.start(); + + // Increment list depth + p.state_mut().list_nesting_depth += 1; + + // Use ParseNodeList to parse the list with proper recovery + let mut list_helper = OrderedList::new(); + list_helper.parse_list(p); + + // Decrement list depth + p.state_mut().list_nesting_depth -= 1; + + Present(item_m.complete(p, MD_ORDERED_LIST_ITEM)) +} + +/// Parse a single ordered item (marker + content). +fn parse_ordered_bullet(p: &mut MarkdownParser) -> (ParsedSyntax, ListItemBlankInfo) { + if !at_order_list_item(p) { + return (Absent, ListItemBlankInfo::default()); + } + + let m = p.start(); + + // Compute the marker indent, handling both normal and virtual line start cases. + // For virtual line start (nested list detection), we compute the actual column + // to ensure correct indented code block detection. + let marker_indent = compute_marker_indent(p); + skip_list_marker_indent(p); + + // Get marker width from actual token text (e.g., "1." = 2, "10." = 3) + let marker_width = p.cur_text().len(); + + // Bump the ordered list marker + p.bump(MD_ORDERED_LIST_MARKER); + + // Count spaces after marker to determine required indentation. + // Per CommonMark §5.2, content aligns to first non-space after marker. + let spaces_after_marker = + count_spaces_after_marker(p.source_after_current(), marker_indent + marker_width); + + // Check if first line is empty (marker followed by only whitespace + newline) + let first_line_empty = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + + // Set required indent for continuation lines + // Required indent = marker width + spaces after marker (minimum 1) + // BUT: if first line is empty (marker + whitespace + newline), use minimum indent + let prev_required_indent = p.state().list_item_required_indent; + let prev_marker_indent = p.state().list_item_marker_indent; + p.state_mut().list_item_required_indent = if spaces_after_marker > INDENT_CODE_BLOCK_SPACES { + marker_indent + marker_width + 1 + } else if first_line_empty { + // Empty first line: use minimum indent (marker + 1 space) + marker_indent + marker_width + 1 + } else { + marker_indent + marker_width + spaces_after_marker.max(1) + }; + p.state_mut().list_item_marker_indent = marker_indent; + + // Parse block content + let blank_info = parse_list_item_block_content(p, spaces_after_marker); + + // Restore previous required indent + p.state_mut().list_item_required_indent = prev_required_indent; + p.state_mut().list_item_marker_indent = prev_marker_indent; + + let completed = m.complete(p, MD_BULLET); + let range = completed.range(p); + let indent = marker_indent + marker_width + spaces_after_marker.max(1); + p.record_list_item_indent( + range, + indent, + marker_indent, + marker_width, + spaces_after_marker, + ); + (Present(completed), blank_info) +} + +/// Count the number of space/tab characters at the start of a string. +/// Used to determine actual spaces after list marker. +fn count_spaces_after_marker(s: &str, start_column: usize) -> usize { + let mut column = start_column; + + for c in s.chars() { + match c { + ' ' => column += 1, + '\t' => column += TAB_STOP_SPACES - (column % TAB_STOP_SPACES), + _ => break, + } + } + + column.saturating_sub(start_column) +} + +fn is_single_dash_setext_marker(text: &str) -> bool { + let trimmed = text.trim_matches(|c| c == ' ' || c == '\t'); + trimmed == "-" +} + +fn is_textual_bullet_marker(text: &str) -> bool { + text == "-" || text == "*" || text == "+" +} + +pub(crate) fn textual_starts_with_ordered_marker(text: &str) -> bool { + let trimmed = text.trim_start_matches([' ', '\t']); + let mut chars = trimmed.chars().peekable(); + let mut digit_count = 0; + + while let Some(c) = chars.peek().copied() { + if c.is_ascii_digit() { + digit_count += 1; + if digit_count > 9 { + return false; + } + chars.next(); + } else { + break; + } + } + + if digit_count == 0 { + return false; + } + + match chars.next() { + Some('.' | ')') => {} + _ => return false, + } + + matches!(chars.peek(), None | Some(' ' | '\t' | '\n' | '\r')) +} + +fn count_spaces_after_dash_in_token(text: &str, start_column: usize) -> usize { + let mut column = start_column; + let mut seen_dash = false; + + for c in text.chars() { + if !seen_dash { + if c == '-' { + seen_dash = true; + } + continue; + } + + match c { + ' ' => column += 1, + '\t' => column += TAB_STOP_SPACES - (column % TAB_STOP_SPACES), + _ => break, + } + } + + column.saturating_sub(start_column) +} + +fn line_indent_from_current(p: &MarkdownParser) -> usize { + let mut column = 0usize; + for c in p.source_after_current().chars() { + match c { + ' ' => column += 1, + '\t' => column += TAB_STOP_SPACES - (column % TAB_STOP_SPACES), + _ => break, + } + } + column +} + +fn quote_only_line_indent_at_current(p: &MarkdownParser, depth: usize) -> Option { + if depth == 0 { + return None; + } + + let mut start: usize = p.cur_range().start().into(); + let source = p.source().source_text(); + let bytes = source.as_bytes(); + while start > 0 && bytes[start - 1] != b'\n' && bytes[start - 1] != b'\r' { + start -= 1; + } + let line_end = source[start..] + .find('\n') + .map_or(source.len(), |offset| start + offset); + + let mut i = start; + for _ in 0..depth { + let mut column = 0usize; + while i < line_end && column < 3 { + match bytes[i] { + b' ' => { + column += 1; + i += 1; + } + b'\t' => { + let advance = TAB_STOP_SPACES - (column % TAB_STOP_SPACES); + column += advance; + i += 1; + } + _ => break, + } + } + + if i >= line_end || bytes[i] != b'>' { + return None; + } + i += 1; + + if i < line_end && (bytes[i] == b' ' || bytes[i] == b'\t') { + i += 1; + } + } + + let mut indent = 0usize; + while i < line_end { + match bytes[i] { + b' ' => { + indent += 1; + i += 1; + } + b'\t' => { + indent += TAB_STOP_SPACES - (indent % TAB_STOP_SPACES); + i += 1; + } + _ => return None, + } + } + + Some(indent) +} + +fn next_quote_content_indent(p: &MarkdownParser, depth: usize) -> Option { + if depth == 0 { + return None; + } + + let source = p.source().source_text(); + let bytes = source.as_bytes(); + let mut line_start: usize = p.cur_range().start().into(); + while line_start > 0 && bytes[line_start - 1] != b'\n' && bytes[line_start - 1] != b'\r' { + line_start -= 1; + } + + loop { + let newline_index = source[line_start..] + .find('\n') + .map(|offset| line_start + offset); + let mut line_end = newline_index.unwrap_or(source.len()); + if line_end > line_start && bytes[line_end - 1] == b'\r' { + line_end -= 1; + } + + let mut i = line_start; + for _ in 0..depth { + let mut column = 0usize; + while i < line_end && column < 3 { + match bytes[i] { + b' ' => { + column += 1; + i += 1; + } + b'\t' => { + let advance = TAB_STOP_SPACES - (column % TAB_STOP_SPACES); + column += advance; + i += 1; + } + _ => break, + } + } + + if i >= line_end || bytes[i] != b'>' { + return None; + } + i += 1; + + if i < line_end && (bytes[i] == b' ' || bytes[i] == b'\t') { + i += 1; + } + } + + let mut indent = 0usize; + while i < line_end { + match bytes[i] { + b' ' => { + indent += 1; + i += 1; + } + b'\t' => { + indent += TAB_STOP_SPACES - (indent % TAB_STOP_SPACES); + i += 1; + } + _ => return Some(indent), + } + } + + let newline_index = newline_index?; + line_start = newline_index + 1; + } +} + +/// Parse block content for a list item. +/// +/// Handles the sequence of blocks belonging to a list item. +/// The first block usually starts on the same line as the marker. +/// Subsequent lines must be indented to at least `required_indent` columns. +/// +/// Per CommonMark §5.2, continuation lines must align with the first non-space +/// character after the list marker. +/// +/// Returns blank-line information for the list item content. +fn parse_list_item_block_content( + p: &mut MarkdownParser, + spaces_after_marker: usize, +) -> ListItemBlankInfo { + let m = p.start(); + let mut has_blank_line = false; + let mut last_was_blank = false; + let mut last_block_was_paragraph = false; + let required_indent = p.state().list_item_required_indent; + let marker_indent = p.state().list_item_marker_indent; + + // Track whether we're on the first line (same line as marker) + let mut first_line = true; + + loop { + if p.at(T![EOF]) { + break; + } + + let quote_depth = p.state().block_quote_depth; + if !first_line + && quote_depth > 0 + && quote_only_line_indent_at_current(p, quote_depth).is_some() + && let Some(next_indent) = next_quote_content_indent(p, quote_depth) + && next_indent < required_indent + { + break; + } + let newline_has_quote_prefix = quote_depth > 0 + && p.at(NEWLINE) + && (p.at_line_start() || p.has_preceding_line_break()) + && has_quote_prefix(p, quote_depth); + + // Special case: blank line with only quote prefixes (e.g., ">>"). + // Treat it as a blank line inside the list item so it becomes loose. + if !first_line && quote_depth > 0 && p.at(NEWLINE) { + let is_quote_blank_line = p.lookahead(|p| { + p.bump(NEWLINE); + if !has_quote_prefix(p, quote_depth) { + return false; + } + consume_quote_prefix_without_virtual(p, quote_depth); + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + + if is_quote_blank_line { + let m = p.start(); + p.bump(NEWLINE); + m.complete(p, MD_NEWLINE); + if has_quote_prefix(p, quote_depth) { + consume_quote_prefix(p, quote_depth); + } + consume_blank_line(p); + has_blank_line = true; + last_was_blank = true; + first_line = false; + continue; + } + } + + if !first_line && p.at(NEWLINE) && !p.at_blank_line() && !newline_has_quote_prefix { + let action = classify_blank_line(p, required_indent, marker_indent); + // Check if the NEWLINE we're at is itself on a blank line + // (i.e., preceded by another newline). This distinguishes a real + // blank line from a content-terminating newline (e.g., after a + // fenced code block's closing fence). + let is_blank = list_newline_is_blank_line(p); + match action { + BlankLineAction::ContinueItem => { + consume_blank_line(p); + if is_blank { + has_blank_line = true; + } + last_was_blank = is_blank; + continue; + } + BlankLineAction::EndItemAfterBlank => { + consume_blank_line(p); + has_blank_line = true; + last_was_blank = true; + break; + } + BlankLineAction::EndItemAtBoundary => { + consume_blank_line(p); + if is_blank { + has_blank_line = true; + last_was_blank = true; + } + break; + } + BlankLineAction::EndItemBeforeBlank => { + break; + } + } + } + + let line_has_quote_prefix = quote_depth > 0 + && (p.at_line_start() || p.has_preceding_line_break()) + && (has_quote_prefix(p, quote_depth) + || quote_only_line_indent_at_current(p, quote_depth).is_some()); + + if line_has_quote_prefix { + let is_quote_only_line = p.lookahead(|p| { + consume_quote_prefix_without_virtual(p, quote_depth); + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + + if is_quote_only_line { + consume_quote_prefix(p, quote_depth); + consume_blank_line(p); + if !first_line { + has_blank_line = true; + } + last_was_blank = true; + first_line = false; + continue; + } + } + + let blank_line_after_prefix = if line_has_quote_prefix { + p.lookahead(|p| { + consume_quote_prefix_without_virtual(p, quote_depth); + at_blank_line_after_prefix(p) + }) + } else { + at_blank_line_after_prefix(p) + }; + + // On the first line (same line as marker), if we're at a blank line, + // this is a marker-only line followed by blank line. Handle this + // in the first_line && p.at(NEWLINE) block below, not here. + if first_line && blank_line_after_prefix && p.at(NEWLINE) { + // Fall through to the first_line && p.at(NEWLINE) handler below + } else if (p.at_line_start() || line_has_quote_prefix) && blank_line_after_prefix { + if line_has_quote_prefix + && quote_only_line_indent_at_current(p, quote_depth).is_some() + && let Some(next_indent) = next_quote_content_indent(p, quote_depth) + { + if next_indent >= required_indent { + if line_has_quote_prefix { + consume_quote_prefix(p, quote_depth); + } + consume_blank_line(p); + if !first_line { + has_blank_line = true; + } + last_was_blank = true; + first_line = false; + continue; + } + if next_indent < required_indent { + break; + } + } + let marker_line_break = first_line; + let action = if quote_depth > 0 { + classify_blank_line_in_quote(p, required_indent, marker_indent, quote_depth) + } else { + classify_blank_line(p, required_indent, marker_indent) + }; + match action { + BlankLineAction::ContinueItem => { + if line_has_quote_prefix { + consume_quote_prefix(p, quote_depth); + } + consume_blank_line(p); + if !marker_line_break { + has_blank_line = true; + } + last_was_blank = true; + first_line = false; + continue; + } + BlankLineAction::EndItemAfterBlank => { + if line_has_quote_prefix { + consume_quote_prefix(p, quote_depth); + } + consume_blank_line(p); + if !marker_line_break { + has_blank_line = true; + } + last_was_blank = true; + break; + } + BlankLineAction::EndItemAtBoundary => { + // In the blank_line_after_prefix path, we know there's an + // actual blank line, so treat as EndItemAfterBlank. + if line_has_quote_prefix { + consume_quote_prefix(p, quote_depth); + } + consume_blank_line(p); + if !marker_line_break { + has_blank_line = true; + } + last_was_blank = true; + break; + } + BlankLineAction::EndItemBeforeBlank => { + break; + } + } + } + + if line_has_quote_prefix { + consume_quote_prefix(p, quote_depth); + } + let line_started_with_quote_prefix = line_has_quote_prefix; + + let prev_was_blank = last_was_blank; + + if first_line && p.at(NEWLINE) { + let next_is_sibling = p.lookahead(|p| { + p.bump(NEWLINE); + if p.at_line_start() { + at_bullet_list_item_with_base_indent(p, marker_indent) + || at_order_list_item_with_base_indent(p, marker_indent) + } else { + false + } + }); + + // Marker-only line: consume the newline as trivia and continue. + p.parse_as_skipped_trivia_tokens(|p| p.bump(NEWLINE)); + first_line = false; + last_was_blank = false; + + if next_is_sibling { + continue; + } + + // Now check if we're at a blank line (the line immediately after marker is empty). + // Per CommonMark: if marker-only line is followed by a blank line, + // the item is truly empty and subsequent content is outside the list. + let now_at_blank_line = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + + if now_at_blank_line { + // Item is empty - break out of the loop + break; + } + + // Continue to next iteration with fresh state to properly handle + // the continuation content on the next line. + continue; + } + + if first_line { + enum NestedListMarker { + Bullet, + Ordered, + } + + let fenced_code_start = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + if p.at(TRIPLE_BACKTICK) || p.at(TRIPLE_TILDE) { + return true; + } + (p.at(BACKTICK) || p.at(TILDE)) && p.cur_text().len() >= 3 + }); + + if fenced_code_start { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + + let parsed = super::with_virtual_line_start(p, p.cur_range().start(), |p| { + parse_fenced_code_block(p) + }); + if parsed.is_present() { + last_block_was_paragraph = false; + last_was_blank = false; + first_line = false; + continue; + } + } + + let html_block_start = p.lookahead(|p| { + super::with_virtual_line_start(p, p.cur_range().start(), |p| { + super::html_block::at_html_block(p) + }) + }); + + if html_block_start { + let parsed = super::with_virtual_line_start(p, p.cur_range().start(), |p| { + super::html_block::parse_html_block(p) + }); + if parsed.is_present() { + last_block_was_paragraph = false; + last_was_blank = false; + first_line = false; + continue; + } + } + + // Check for ATX heading on the first line of list item content. + // e.g., `- # Foo` should produce a heading inside the list item. + let atx_heading_info = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + // # may be tokenized as HASH or MD_TEXTUAL_LITERAL + let is_hash = p.at(T![#]) + || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == '#')); + if !is_hash { + return None; + } + let text = p.cur_text(); + let hash_count = text.len(); + if !(1..=6).contains(&hash_count) { + return None; + } + p.bump(p.cur()); + // Must be followed by space/tab, EOL, or EOF + if p.at(NEWLINE) || p.at(T![EOF]) { + return Some(hash_count); + } + if p.at(MD_TEXTUAL_LITERAL) { + let t = p.cur_text(); + if t.starts_with(' ') || t.starts_with('\t') { + return Some(hash_count); + } + } + None + }); + + if atx_heading_info.is_some() { + // Skip leading whitespace as trivia + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + + // Manually build the heading node since we're on the first + // line and parse_header can't handle tokens here directly. + let header_m = p.start(); + + // Build MdHashList > MdHash > T![#] + let hash_list_m = p.start(); + let hash_m = p.start(); + if p.at(T![#]) { + p.bump(T![#]); + } else { + p.bump_remap(T![#]); + } + hash_m.complete(p, MD_HASH); + hash_list_m.complete(p, MD_HASH_LIST); + + // Parse heading content (inline until end of line) + super::header::parse_header_content(p); + + // Parse trailing hashes + super::header::parse_trailing_hashes(p); + + header_m.complete(p, MD_HEADER); + + last_block_was_paragraph = false; + last_was_blank = false; + first_line = false; + continue; + } + + // Check for blockquote on the first line of list item content. + // Per CommonMark §5.2, list item content can include block-level + // elements like blockquotes on the same line as the marker. + // e.g., `> 1. > Blockquote` has a blockquote inside the list item. + let blockquote_start = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + // Check for > as either T![>] or MD_TEXTUAL_LITERAL ">" + p.at(T![>]) || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == ">") + }); + + if blockquote_start { + // Skip leading whitespace as trivia + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + + let prev_virtual = p.state().virtual_line_start; + let prev_required = p.state().list_item_required_indent; + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + p.state_mut().list_item_required_indent = 0; + + // Remap textual ">" to T![>] so parse_quote can recognize it. + // parse_quote checks `p.at(T![>])` after skipping indent. + if p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == ">" { + p.bump_remap(T![>]); + // We bumped the >, but parse_quote expects to bump it itself. + // Instead, manually build the quote node inline. + let quote_m = p.start(); + p.state_mut().block_quote_depth += 1; + + // Skip optional space after > + if p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == " " { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + + parse_quote_block_list(p); + + p.state_mut().block_quote_depth -= 1; + quote_m.complete(p, MD_QUOTE); + + last_block_was_paragraph = false; + last_was_blank = false; + first_line = false; + p.state_mut().virtual_line_start = prev_virtual; + p.state_mut().list_item_required_indent = prev_required; + continue; + } + + // T![>] case: parse_quote can handle it directly + let parsed = super::quote::parse_quote(p); + if parsed.is_present() { + last_block_was_paragraph = false; + last_was_blank = false; + first_line = false; + p.state_mut().virtual_line_start = prev_virtual; + p.state_mut().list_item_required_indent = prev_required; + continue; + } + p.state_mut().virtual_line_start = prev_virtual; + p.state_mut().list_item_required_indent = prev_required; + } + + // Check for thematic break BEFORE nested list markers. + // Per CommonMark §4.1, `* * *` or `- - -` on a line by itself is a thematic + // break, not nested list markers. This handles example 061. + let is_thematic_break = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + // Check for lexer-produced thematic break token + if p.at(MD_THEMATIC_BREAK_LITERAL) { + return true; + } + // Check for token-based thematic break pattern + // The lexer may not produce MD_THEMATIC_BREAK_LITERAL after a list marker + // because after_newline is false. Check manually. + is_thematic_break_pattern(p) + }); + + if is_thematic_break { + // Parse the thematic break as a block within the list item. + let _ = super::thematic_break_block::parse_thematic_break_block(p); + last_block_was_paragraph = false; + last_was_blank = false; + first_line = false; + continue; + } + + let nested_marker = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + + if p.at(MD_ORDERED_LIST_MARKER) { + p.bump(MD_ORDERED_LIST_MARKER); + return marker_followed_by_whitespace_or_eol(p) + .then_some(NestedListMarker::Ordered); + } + + if p.at(MD_SETEXT_UNDERLINE_LITERAL) && is_single_dash_setext_marker(p.cur_text()) { + p.bump(MD_SETEXT_UNDERLINE_LITERAL); + return marker_followed_by_whitespace_or_eol(p) + .then_some(NestedListMarker::Bullet); + } + + if p.at(T![-]) || p.at(T![*]) || p.at(T![+]) { + p.bump(p.cur()); + return marker_followed_by_whitespace_or_eol(p) + .then_some(NestedListMarker::Bullet); + } + + if p.at(MD_TEXTUAL_LITERAL) && is_textual_bullet_marker(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + return marker_followed_by_whitespace_or_eol(p) + .then_some(NestedListMarker::Bullet); + } + + if p.at(MD_TEXTUAL_LITERAL) && textual_starts_with_ordered_marker(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + return Some(NestedListMarker::Ordered); + } + + None + }); + + if let Some(nested_marker) = nested_marker { + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + + let prev_virtual = p.state().virtual_line_start; + let prev_required = p.state().list_item_required_indent; + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + p.state_mut().list_item_required_indent = 0; + + let parsed = match nested_marker { + NestedListMarker::Bullet => parse_bullet_list_item(p), + NestedListMarker::Ordered => { + p.set_force_ordered_list_marker(true); + p.force_relex_regular(); + let parsed = parse_order_list_item(p); + p.set_force_ordered_list_marker(false); + parsed + } + }; + if parsed.is_absent() { + let parsed = parse_any_block_with_indent_code_policy(p, true); + last_block_was_paragraph = if let Present(ref marker) = parsed { + is_paragraph_like(marker.kind(p)) + } else { + false + }; + } else { + last_block_was_paragraph = false; + } + first_line = false; + + p.state_mut().virtual_line_start = prev_virtual; + p.state_mut().list_item_required_indent = prev_required; + continue; + } + } + + if first_line && spaces_after_marker > INDENT_CODE_BLOCK_SPACES { + parse_indent_code_block_in_list_first_line(p); + last_block_was_paragraph = false; + last_was_blank = false; + first_line = false; + continue; + } + // Blank line handling happens above, before consuming quote prefixes. + + // After the first line, check indentation for continuation + // Skip this check on the first line (content on same line as marker) + let mut restore_virtual_line_start = None; + if !first_line && (p.at_line_start() || line_started_with_quote_prefix) { + // Get indentation of current line + let indent = line_indent_from_current(p); + + if indent < marker_indent { + break; + } + + if indent >= required_indent { + let allow_indent_code_block = !last_block_was_paragraph || prev_was_blank; + let is_indent_code_block = + allow_indent_code_block && indent >= required_indent + INDENT_CODE_BLOCK_SPACES; + if !is_indent_code_block { + // Sufficient indentation - skip it and continue + p.skip_line_indent(required_indent); + let prev_virtual = p.state().virtual_line_start; + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + restore_virtual_line_start = Some(prev_virtual); + + if at_bullet_list_item(p) { + let _ = parse_bullet_list_item(p); + last_block_was_paragraph = false; + first_line = false; + p.state_mut().virtual_line_start = prev_virtual; + continue; + } + if at_order_list_item(p) { + let _ = parse_order_list_item(p); + last_block_was_paragraph = false; + first_line = false; + p.state_mut().virtual_line_start = prev_virtual; + continue; + } + } + } else { + // Insufficient indentation - check for block interrupts + + // A new list marker at this indentation starts a sibling item + if at_bullet_list_item_with_base_indent(p, marker_indent) + || at_order_list_item_with_base_indent(p, marker_indent) + { + break; + } + + // Check if this line starts a block-level construct that can + // interrupt paragraphs (headers, quotes, thematic breaks, etc.) + if at_block_interrupt(p) { + break; + } + + // Otherwise, this is "lazy continuation" per CommonMark §5.2: + // Content continues without meeting the indent requirement. + // Don't skip indent, just continue parsing at actual position. + if !last_block_was_paragraph { + break; + } + } + } + + let is_blank_line = p.at_blank_line(); + if is_blank_line { + has_blank_line = true; + last_was_blank = true; + } else { + last_was_blank = false; + } + + // After parsing any block, we'll be on a new line (or EOF) + first_line = false; + + // Parse the next block + // parse_any_block_with_indent_code_policy handles paragraphs, code blocks, etc. + // It consumes newlines as MdNewline if they are blank lines. + let allow_indent_code_block = !last_block_was_paragraph || prev_was_blank; + let parsed = parse_any_block_with_indent_code_policy(p, allow_indent_code_block); + last_block_was_paragraph = if let Present(ref marker) = parsed { + is_paragraph_like(marker.kind(p)) + } else { + false + }; + if let Some(prev_virtual) = restore_virtual_line_start { + p.state_mut().virtual_line_start = prev_virtual; + } + } + + m.complete(p, MD_BLOCK_LIST); + ListItemBlankInfo { + has_blank_line, + ends_with_blank_line: last_was_blank, + } +} + +fn parse_indent_code_block_in_list_first_line(p: &mut MarkdownParser) { + let m = p.start(); + let content = p.start(); + + loop { + if p.at(T![EOF]) { + break; + } + + if p.at(NEWLINE) { + if list_newline_is_blank_line(p) && !list_has_following_indented_code_line(p) { + break; + } + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + continue; + } + + if p.at_line_start() && !at_indent_code_block(p) { + if at_blank_line_start(p) { + if list_has_following_indented_code_line(p) { + consume_blank_line(p); + continue; + } + break; + } + break; + } + + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + } + + content.complete(p, MD_INLINE_ITEM_LIST); + m.complete(p, MD_INDENT_CODE_BLOCK); +} + +fn list_has_following_indented_code_line(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + while p.at_line_start() && at_blank_line_start(p) { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + if p.at(NEWLINE) { + p.bump(NEWLINE); + } else { + break; + } + } + + at_indent_code_block(p) + }) +} + +fn list_newline_is_blank_line(p: &MarkdownParser) -> bool { + let start: usize = p.cur_range().start().into(); + if start == 0 { + return true; + } + + let source = p.source().source_text(); + let prev = source.as_bytes()[start - 1]; + prev == b'\n' || prev == b'\r' +} + +enum BlankLineAction { + ContinueItem, + /// End item; actual blank lines were found before the next item. + EndItemAfterBlank, + /// End item; no actual blank lines, just a normal item boundary. + EndItemAtBoundary, + EndItemBeforeBlank, +} + +fn classify_blank_line( + p: &mut MarkdownParser, + required_indent: usize, + marker_indent: usize, +) -> BlankLineAction { + p.lookahead(|p| { + // Skip ALL consecutive blank lines (not just one). + // Per CommonMark §5.3, multiple blank lines between items still + // belong to the same list - they just make it "loose". + let mut blank_lines_found = 0usize; + loop { + let line_is_blank = p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + + if !line_is_blank { + break; + } + + blank_lines_found += 1; + + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + if p.at(NEWLINE) { + p.bump(NEWLINE); + continue; + } + + break; + } + + if p.at(T![EOF]) { + return BlankLineAction::EndItemBeforeBlank; + } + + // Otherwise, keep blank line as part of item only if indentation is sufficient. + let indent = p.line_start_leading_indent(); + if indent >= required_indent { + return BlankLineAction::ContinueItem; + } + + // If next non-blank line starts a new list item, this is a blank line between items. + if indent <= marker_indent + 3 + && (at_bullet_list_item_with_base_indent(p, marker_indent) + || at_order_list_item_with_base_indent(p, marker_indent)) + { + // The first "blank line" is just the item-ending newline. + // Only report actual blank lines if more than 1 was found. + if blank_lines_found > 1 { + return BlankLineAction::EndItemAfterBlank; + } + return BlankLineAction::EndItemAtBoundary; + } + + BlankLineAction::EndItemBeforeBlank + }) +} + +fn classify_blank_line_in_quote( + p: &mut MarkdownParser, + required_indent: usize, + marker_indent: usize, + quote_depth: usize, +) -> BlankLineAction { + p.lookahead(|p| { + loop { + let blank_indent = p.lookahead(|p| { + if !consume_quote_prefix_without_virtual(p, quote_depth) { + return None; + } + Some(line_indent_from_current(p)) + }); + + if let Some(indent) = blank_indent + && indent < required_indent + { + return BlankLineAction::EndItemBeforeBlank; + } + + let line_is_blank = p.lookahead(|p| { + if !consume_quote_prefix_without_virtual(p, quote_depth) { + return false; + } + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + + if !line_is_blank { + break; + } + + if !consume_quote_prefix_without_virtual(p, quote_depth) { + return BlankLineAction::EndItemBeforeBlank; + } + + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + + if p.at(NEWLINE) { + p.bump(NEWLINE); + continue; + } + + return BlankLineAction::EndItemBeforeBlank; + } + + if p.at(T![EOF]) { + return BlankLineAction::EndItemBeforeBlank; + } + + let prev_virtual = p.state().virtual_line_start; + let has_prefix = consume_quote_prefix(p, quote_depth); + if !has_prefix { + p.state_mut().virtual_line_start = prev_virtual; + return BlankLineAction::EndItemBeforeBlank; + } + let indent = line_indent_from_current(p); + p.state_mut().virtual_line_start = prev_virtual; + if indent >= required_indent { + return BlankLineAction::ContinueItem; + } + + if indent <= marker_indent + 3 { + let is_list_marker = p.lookahead(|p| { + skip_leading_whitespace_tokens(p); + + if p.at(MD_ORDERED_LIST_MARKER) { + p.bump(MD_ORDERED_LIST_MARKER); + return marker_followed_by_whitespace_or_eol(p); + } + + if p.at(MD_SETEXT_UNDERLINE_LITERAL) { + if !is_single_dash_setext_marker(p.cur_text()) { + return false; + } + p.bump(MD_SETEXT_UNDERLINE_LITERAL); + return marker_followed_by_whitespace_or_eol(p); + } + + if p.at(MD_TEXTUAL_LITERAL) && is_textual_bullet_marker(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + return marker_followed_by_whitespace_or_eol(p); + } + + if p.at(T![-]) || p.at(T![*]) || p.at(T![+]) { + p.bump(p.cur()); + return marker_followed_by_whitespace_or_eol(p); + } + + false + }); + + if is_list_marker { + return BlankLineAction::EndItemAfterBlank; + } + } + + BlankLineAction::EndItemBeforeBlank + }) +} + +fn at_blank_line_start(p: &mut MarkdownParser) -> bool { + if !p.at_line_start() { + return false; + } + + at_blank_line_after_prefix(p) +} + +fn at_blank_line_after_prefix(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + if p.at(NEWLINE) { + return p.at_blank_line(); + } + if p.at(T![EOF]) { + return true; + } + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + if p.at(NEWLINE) { + return p.source().at_line_start_with_whitespace(); + } + + p.at(T![EOF]) + }) +} + +fn consume_blank_line(p: &mut MarkdownParser) { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } else { + break; + } + } + + if p.at(NEWLINE) { + let m = p.start(); + p.bump(NEWLINE); + m.complete(p, MD_NEWLINE); + } +} + +/// Check if there's a bullet list item after skipping blank lines. +/// +/// Per CommonMark §5.3, blank lines between list items don't end the list, +/// they just make it "loose". This function peeks ahead across blank lines +/// to see if another bullet item follows. +fn has_bullet_item_after_blank_lines(p: &mut MarkdownParser) -> bool { + has_list_item_after_blank_lines(p, |p| { + if p.at(T![-]) || p.at(T![*]) || p.at(T![+]) { + p.bump(p.cur()); + marker_followed_by_whitespace_or_eol(p) + } else { + false + } + }) +} + +/// Like `has_bullet_item_after_blank_lines` but also checks that the +/// bullet marker is at the expected indent level for this list. +fn has_bullet_item_after_blank_lines_at_indent( + p: &mut MarkdownParser, + expected_indent: usize, +) -> bool { + has_list_item_after_blank_lines_at_indent(p, expected_indent, |p| { + if p.at(T![-]) || p.at(T![*]) || p.at(T![+]) { + p.bump(p.cur()); + marker_followed_by_whitespace_or_eol(p) + } else { + false + } + }) +} + +fn has_list_item_after_blank_lines_at_indent( + p: &mut MarkdownParser, + expected_indent: usize, + has_marker: F, +) -> bool +where + F: Fn(&mut MarkdownParser) -> bool, +{ + p.lookahead(|p| { + // Skip all blank lines + loop { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + if p.at(NEWLINE) { + p.bump(NEWLINE); + continue; + } + break; + } + + let mut indent = 0; + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " { + indent += 1; + p.bump(MD_TEXTUAL_LITERAL); + } else if text == "\t" { + indent += TAB_STOP_SPACES - (indent % TAB_STOP_SPACES); + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + // Check indent matches the list's marker indent range + if expected_indent == 0 { + if indent > 3 { + return false; + } + } else if indent < expected_indent || indent > expected_indent + 3 { + return false; + } + + has_marker(p) + }) +} + +/// Check if there's an ordered list item after skipping blank lines. +/// +/// Per CommonMark §5.3, blank lines between list items don't end the list, +/// they just make it "loose". This function peeks ahead across blank lines +/// to see if another ordered item follows. +fn has_ordered_item_after_blank_lines(p: &mut MarkdownParser) -> bool { + has_list_item_after_blank_lines(p, |p| p.at(MD_ORDERED_LIST_MARKER)) +} + +fn has_list_item_after_blank_lines(p: &mut MarkdownParser, has_marker: F) -> bool +where + F: Fn(&mut MarkdownParser) -> bool, +{ + p.lookahead(|p| { + // Skip all blank lines + loop { + // Skip whitespace on current line + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + // If at NEWLINE, consume it and continue checking + if p.at(NEWLINE) { + p.bump(NEWLINE); + continue; + } + + // Reached non-blank content or EOF + break; + } + + // Check for marker directly (avoid nested lookahead issues) + // Skip leading indent (up to 3 spaces for list items) + let mut indent = 0; + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " { + indent += 1; + p.bump(MD_TEXTUAL_LITERAL); + } else if text == "\t" { + indent += TAB_STOP_SPACES - (indent % TAB_STOP_SPACES); + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + // More than 3 spaces indent = indented code block, not a list item + if indent > 3 { + return false; + } + + has_marker(p) + }) +} diff --git a/crates/biome_markdown_parser/src/syntax/mod.rs b/crates/biome_markdown_parser/src/syntax/mod.rs new file mode 100644 index 000000000000..92a9af1878a0 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/mod.rs @@ -0,0 +1,1821 @@ +//! Block and inline syntax parsing for Markdown. +//! +//! # CommonMark Specification References +//! +//! This module implements CommonMark 0.31.2 block structure: +//! +//! ## Leaf Blocks (§4) +//! - **§4.1 Thematic breaks**: `---`, `***`, `___` +//! - **§4.2 ATX headings**: `# Heading`, `## Heading`, etc. +//! - **§4.3 Setext headings**: Underlined with `===` or `---` +//! - **§4.4 Indented code blocks**: 4+ spaces of indentation +//! - **§4.5 Fenced code blocks**: ``` or ~~~ delimited +//! - **§4.6 HTML blocks**: Raw HTML content +//! - **§4.7 Link reference definitions**: `[label]: url "title"` +//! - **§4.8 Paragraphs**: Default block content +//! +//! ## Container Blocks (§5) +//! - **§5.1 Block quotes**: `>` prefixed content +//! - **§5.2 List items**: `-`, `*`, `+` or `1.` prefixed +//! - **§5.3 Lists**: Sequences of list items +//! +//! ## Inline Content (§6) +//! See [`inline`] module for inline element parsing. + +pub mod fenced_code_block; +pub mod header; +pub mod html_block; +pub mod inline; +pub mod link_block; +pub mod list; +pub mod parse_error; +pub mod quote; +pub mod reference; +pub mod thematic_break_block; + +use biome_markdown_syntax::kind::MarkdownSyntaxKind; +use biome_markdown_syntax::{T, kind::MarkdownSyntaxKind::*}; +use biome_parser::parse_lists::ParseNodeList; +use biome_parser::parse_recovery::RecoveryResult; +use biome_parser::{ + Parser, + prelude::ParsedSyntax::{self, *}, +}; +use biome_rowan::TextSize; +use fenced_code_block::{ + at_fenced_code_block, info_string_has_backtick, parse_fenced_code_block, + parse_fenced_code_block_force, +}; +use header::{at_header, parse_header}; +use html_block::{at_html_block, at_html_block_interrupt, parse_html_block}; +use inline::EmphasisContext; +use link_block::{at_link_block, parse_link_block}; +use list::{ + at_bullet_list_item, at_order_list_item, marker_followed_by_whitespace_or_eol, + parse_bullet_list_item, parse_order_list_item, textual_starts_with_ordered_marker, +}; +use quote::{ + at_quote, consume_quote_prefix, consume_quote_prefix_without_virtual, has_quote_prefix, + parse_quote, +}; +use thematic_break_block::{at_thematic_break_block, parse_thematic_break_block}; + +use crate::MarkdownParser; + +/// Maximum paren nesting allowed in link destinations per CommonMark. +pub(crate) const MAX_LINK_DESTINATION_PAREN_DEPTH: i32 = 32; + +/// CommonMark requires 4 or more spaces for indented code blocks. +const INDENT_CODE_BLOCK_SPACES: usize = 4; +/// Tabs advance to the next 4-space tab stop in CommonMark parsing. +const TAB_STOP_SPACES: usize = 4; + +pub(crate) fn parse_document(p: &mut MarkdownParser) { + let m = p.start(); + let _ = parse_block_list(p); + // Bump the EOF token - required by the grammar + p.bump(T![EOF]); + m.complete(p, MD_DOCUMENT); +} + +/// Result of updating parenthesis depth when scanning link destinations. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ParenDepthResult { + /// Depth updated successfully, contains new depth value + Ok(i32), + /// Depth would exceed the maximum (too many nested opening parens). + /// Per cmark, this truncates the destination at this point. + DepthExceeded, + /// Unmatched closing paren (would go below 0). + /// This typically means the `)` belongs to the enclosing construct. + UnmatchedClose, +} + +pub(crate) fn try_update_paren_depth(text: &str, depth: i32, max: i32) -> ParenDepthResult { + let mut depth = depth; + let mut chars = text.chars().peekable(); + + while let Some(c) = chars.next() { + if c == '\\' && matches!(chars.peek(), Some('(' | ')')) { + chars.next(); + continue; + } + + if c == '(' { + if depth == max { + return ParenDepthResult::DepthExceeded; + } + depth += 1; + } else if c == ')' { + if depth == 0 { + return ParenDepthResult::UnmatchedClose; + } + depth -= 1; + } + } + + ParenDepthResult::Ok(depth) +} + +pub(crate) enum LinkDestinationKind { + Enclosed, + Raw, +} + +pub(crate) fn validate_link_destination_text( + text: &str, + kind: LinkDestinationKind, + pending_escape: &mut bool, +) -> bool { + for c in text.chars() { + if *pending_escape { + if c.is_ascii_punctuation() { + *pending_escape = false; + continue; + } + *pending_escape = false; + } + + if c == '\\' { + *pending_escape = true; + continue; + } + + if c.is_ascii_control() { + return false; + } + + if matches!(kind, LinkDestinationKind::Enclosed) && c == '<' { + return false; + } + } + + true +} + +pub(crate) fn ends_with_unescaped_close(text: &str, close_char: char) -> bool { + if !text.ends_with(close_char) { + return false; + } + + let mut backslashes = 0; + for c in text.chars().rev().skip(1) { + if c == '\\' { + backslashes += 1; + } else { + break; + } + } + + backslashes % 2 == 0 +} + +pub(crate) fn parse_block_list(p: &mut MarkdownParser) -> ParsedSyntax { + let mut list = DocumentBlockList; + Present(list.parse_list(p)) +} + +/// Struct implementing `ParseNodeList` for document-level block content. +struct DocumentBlockList; + +impl ParseNodeList for DocumentBlockList { + type Kind = MarkdownSyntaxKind; + type Parser<'source> = MarkdownParser<'source>; + + const LIST_KIND: Self::Kind = MD_BLOCK_LIST; + + fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { + parse_any_block(p) + } + + fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { + p.at(T![EOF]) + } + + fn recover( + &mut self, + _p: &mut Self::Parser<'_>, + parsed_element: ParsedSyntax, + ) -> RecoveryResult { + match parsed_element { + Present(marker) => RecoveryResult::Ok(marker), + Absent => RecoveryResult::Err(biome_parser::parse_recovery::RecoveryError::Eof), + } + } +} + +pub(crate) fn parse_any_block(p: &mut MarkdownParser) -> ParsedSyntax { + parse_any_block_with_indent_code_policy(p, true) +} + +/// Check if a syntax kind represents a paragraph-like block. +/// +/// This is used for lazy continuation logic in block quotes and lists. +/// Both paragraphs and setext headings are considered "paragraph-like" +/// because they share continuation behavior. +pub(crate) fn is_paragraph_like(kind: biome_markdown_syntax::MarkdownSyntaxKind) -> bool { + matches!(kind, MD_PARAGRAPH | MD_SETEXT_HEADER) +} + +pub(crate) fn parse_any_block_with_indent_code_policy( + p: &mut MarkdownParser, + allow_indent_code_block: bool, +) -> ParsedSyntax { + let start = p.cur_range().start(); + // Handle standalone NEWLINE tokens as MdNewline nodes. + // This prevents inter-block NEWLINEs from becoming "newline-only paragraphs". + if p.at(NEWLINE) { + let m = p.start(); + p.bump(NEWLINE); + return Present(m.complete(p, MD_NEWLINE)); + } + if at_blank_line_start(p) { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } else { + break; + } + } + if p.at(NEWLINE) { + let m = p.start(); + p.bump(NEWLINE); + return Present(m.complete(p, MD_NEWLINE)); + } + // Blank line with no trailing newline (e.g., end of file) + return Absent; + } + + let parsed = if allow_indent_code_block && at_indent_code_block(p) { + parse_indent_code_block(p) + } else if at_fenced_code_block(p) { + parse_fenced_code_block(p) + } else if line_starts_with_fence(p) { + parse_fenced_code_block_force(p) + } else if at_thematic_break_block(p) { + let break_block = try_parse(p, |p| { + let break_block = parse_thematic_break_block(p); + if break_block.is_absent() { + return Err(()); + } + Ok(break_block) + }); + if let Ok(parsed) = break_block { + parsed + } else { + parse_paragraph(p) + } + } else if at_header(p) { + // Check for too many hashes BEFORE try_parse (which would lose diagnostics on rewind) + let too_many = check_too_many_hashes(p); + let header_result = try_parse(p, |p| { + let header = parse_header(p); + if header.is_absent() { + return Err(()); + } + Ok(header) + }); + if let Ok(parsed) = header_result { + parsed + } else { + // Emit diagnostic for too many hashes (outside try_parse to persist) + if let Some((range, count)) = too_many { + p.error(parse_error::too_many_hashes(p, range, count)); + } + // Not a valid header, parse as paragraph + parse_paragraph(p) + } + } else if at_quote(p) { + parse_quote(p) + } else if at_bullet_list_item(p) { + parse_bullet_list_item(p) + } else if at_order_list_item(p) || at_order_list_item_textual(p) { + let forced = if !at_order_list_item(p) && at_order_list_item_textual(p) { + p.set_force_ordered_list_marker(true); + p.force_relex_regular(); + true + } else { + false + }; + let parsed = parse_order_list_item(p); + if forced { + p.set_force_ordered_list_marker(false); + } + if parsed.is_absent() { + parse_paragraph(p) + } else { + parsed + } + } else if at_html_block(p) { + parse_html_block(p) + } else if at_link_block(p) { + // Try to parse as link reference definition + // Use try_parse to fall back to paragraph if not a valid definition + let link_result = try_parse(p, |p| { + let link = parse_link_block(p); + if link.is_absent() { + return Err(()); + } + Ok(link) + }); + if let Ok(parsed) = link_result { + parsed + } else { + parse_paragraph(p) + } + } else if at_block_interrupt(p) { + // We see a block interrupt but didn't match a concrete block above. + // This can happen when list item indentation is still active. + if at_bullet_list_item_at_any_indent(p) { + let prev_required = p.state().list_item_required_indent; + let prev_virtual = p.state().virtual_line_start; + p.state_mut().list_item_required_indent = 0; + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + let parsed = parse_bullet_list_item(p); + p.state_mut().list_item_required_indent = prev_required; + p.state_mut().virtual_line_start = prev_virtual; + if parsed.is_present() { + parsed + } else { + parse_paragraph(p) + } + } else if at_order_list_item_at_any_indent(p) { + let prev_required = p.state().list_item_required_indent; + let prev_virtual = p.state().virtual_line_start; + p.state_mut().list_item_required_indent = 0; + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + let parsed = parse_order_list_item(p); + p.state_mut().list_item_required_indent = prev_required; + p.state_mut().virtual_line_start = prev_virtual; + if parsed.is_present() { + parsed + } else { + parse_paragraph(p) + } + } else { + parse_paragraph(p) + } + } else { + // Default fallback: parse as paragraph + parse_paragraph(p) + }; + + if start == p.cur_range().start() { + let range = p.cur_range(); + if std::env::var("CMARK_HANG_DEBUG").is_ok() { + eprintln!( + "parse_any_block made no progress at {:?} {:?} => {:?}", + p.cur(), + p.cur_text(), + parsed + ); + } + p.error(parse_error::parse_any_block_no_progress(p, range)); + if !p.at(T![EOF]) { + p.bump_any(); + } + return Absent; + } + + parsed +} + +fn with_virtual_line_start(p: &mut MarkdownParser, start: TextSize, op: F) -> R +where + F: FnOnce(&mut MarkdownParser) -> R, +{ + let prev_virtual = p.state().virtual_line_start; + p.state_mut().virtual_line_start = Some(start); + let result = op(p); + p.state_mut().virtual_line_start = prev_virtual; + result +} + +enum QuoteBreakKind { + None, + SetextUnderline, + Other, +} + +/// Check if we're at an indented code block (4+ spaces of indentation). +/// +/// Uses `line_start_leading_indent()` to correctly handle indentation when NEWLINE +/// tokens are explicit (not trivia). +pub(crate) fn at_indent_code_block(p: &mut MarkdownParser) -> bool { + if !p.at_line_start() { + return false; + } + + if at_blank_line_start(p) { + return false; + } + + let indent = p.line_start_leading_indent(); + let required_indent = p.state().list_item_required_indent; + + // Inside a list item, we need 4 spaces BEYOND the list item's required indent. + // e.g., if list item requires 2 spaces, code block needs 2 + 4 = 6 spaces. + // Outside a list item (required_indent == 0), we need just 4 spaces. + let effective_indent = indent.saturating_sub(required_indent); + effective_indent >= INDENT_CODE_BLOCK_SPACES +} + +/// Parse an indented code block. +/// +/// Grammar: MdIndentCodeBlock = content: MdInlineItemList +/// +/// An indented code block consists of one or more lines with 4+ spaces +/// of indentation. The indentation is tracked in trivia. +/// +/// # NEWLINE Handling +/// +/// NEWLINE is an explicit token. We consume tokens until NEWLINE, +/// then check if the next line has proper indentation. +pub(crate) fn parse_indent_code_block(p: &mut MarkdownParser) -> ParsedSyntax { + if !at_indent_code_block(p) { + return Absent; + } + + let m = p.start(); + let content = p.start(); + + // Parse content while we're at indented lines, including interior blank lines. + loop { + if p.at(T![EOF]) { + break; + } + + // Always include NEWLINE tokens in code content + if p.at(NEWLINE) { + if newline_is_blank_line(p) && !has_following_indented_code_line(p) { + break; + } + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + p.set_virtual_line_start(); + p.set_virtual_line_start(); + continue; + } + + if p.at_line_start() { + if at_blank_line_start(p) { + if has_following_indented_code_line(p) { + consume_blank_line(p); + continue; + } + break; + } else if !at_indent_code_block(p) { + break; + } + if p.state().list_item_required_indent == 0 { + consume_indent_prefix(p, INDENT_CODE_BLOCK_SPACES); + } + } + + // Consume token as code content + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + } + + content.complete(p, MD_INLINE_ITEM_LIST); + Present(m.complete(p, MD_INDENT_CODE_BLOCK)) +} + +fn has_following_indented_code_line(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + while p.at_line_start() && at_blank_line_start(p) { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + if p.at(NEWLINE) { + p.bump(NEWLINE); + } else { + break; + } + } + + at_indent_code_block(p) + }) +} + +fn newline_is_blank_line(p: &MarkdownParser) -> bool { + // Token stream doesn't expose the prior byte, so read source to detect CR/LF. + let start: usize = p.cur_range().start().into(); + if start == 0 { + return true; + } + + let source = p.source().source_text(); + let prev = source.as_bytes()[start - 1]; + prev == b'\n' || prev == b'\r' +} + +/// Check if the current line is blank (lookahead only). +fn consume_indent_prefix(p: &mut MarkdownParser, indent: usize) { + if indent == 0 { + return; + } + + let mut consumed = 0usize; + while consumed < indent && p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " { + consumed += 1; + } else if text == "\t" { + consumed += 4; + } else { + break; + } + + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } +} + +/// Consume exactly `indent` columns of leading whitespace at line start. +fn at_blank_line_start(p: &mut MarkdownParser) -> bool { + if !p.at_line_start() { + return false; + } + + p.lookahead(|p| { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.bump(MD_TEXTUAL_LITERAL); + } else { + break; + } + } + + p.at(NEWLINE) || p.at(T![EOF]) + }) +} + +fn consume_blank_line(p: &mut MarkdownParser) { + while p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } else { + break; + } + } + + if p.at(NEWLINE) { + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + } +} + +/// Parse a paragraph block, or a setext heading if followed by an underline. +/// +/// A paragraph is a sequence of non-blank lines that cannot be interpreted as +/// other kinds of blocks. The paragraph ends at a blank line or EOF. +/// +/// If the paragraph is followed by a setext heading underline (=== or ---), +/// it becomes a setext heading instead. +/// +/// Grammar: MdParagraph = list: MdInlineItemList hard_line: MdHardLine? +/// Grammar: MdSetextHeader = content: MdInlineItemList underline: 'md_setext_underline_literal' +pub(crate) fn parse_paragraph(p: &mut MarkdownParser) -> ParsedSyntax { + let m = p.start(); + + let inline_start: usize = p.cur_range().start().into(); + parse_inline_item_list(p); + let inline_end: usize = p.cur_range().start().into(); + + let has_inline_content = inline_has_non_whitespace(p, inline_start, inline_end); + let allow_setext = has_inline_content && allow_setext_heading(p); + + // Check if this paragraph is followed by a setext heading underline + // MD_SETEXT_UNDERLINE_LITERAL is for `=` underlines + // MD_THEMATIC_BREAK_LITERAL with only `-` is also a setext underline (H2) + let completed = if allow_setext && p.at(MD_SETEXT_UNDERLINE_LITERAL) { + let indent = real_line_indent_from_source(p); + if indent < 4 { + // This is a setext heading (H1 with `=`) - consume the underline + p.bump(MD_SETEXT_UNDERLINE_LITERAL); + m.complete(p, MD_SETEXT_HEADER) + } else { + // 4+ spaces of indent: not a setext underline (CommonMark §4.3) + m.complete(p, MD_PARAGRAPH) + } + } else if allow_setext && p.at(MD_THEMATIC_BREAK_LITERAL) && is_dash_only_thematic_break(p) { + let indent = real_line_indent_from_source(p); + if indent < 4 { + // This is a setext heading (H2 with `-`) - remap token and consume + p.bump_remap(MD_SETEXT_UNDERLINE_LITERAL); + m.complete(p, MD_SETEXT_HEADER) + } else { + // 4+ spaces of indent: not a setext underline (CommonMark §4.3) + m.complete(p, MD_PARAGRAPH) + } + } else { + m.complete(p, MD_PARAGRAPH) + }; + Present(completed) +} + +fn inline_has_non_whitespace(p: &MarkdownParser, start: usize, end: usize) -> bool { + if end <= start { + return false; + } + + let source = p.source().source_text(); + if end > source.len() { + return false; + } + + !source[start..end] + .trim_matches(|c: char| matches!(c, ' ' | '\t' | '\r' | '\n')) + .is_empty() +} + +/// Check if a thematic break text contains only dashes (used for setext H2 detection). +pub(crate) fn is_dash_only_thematic_break_text(text: &str) -> bool { + !text.is_empty() && text.trim().chars().all(|c| c == '-') +} + +/// Token-based check: is the current line a setext underline? +/// +/// Call after consuming a NEWLINE token. Skips 0–3 columns of leading whitespace +/// (tabs expand to the next tab stop per CommonMark §2.2), then checks for +/// `MD_SETEXT_UNDERLINE_LITERAL` or a dash-only `MD_THEMATIC_BREAK_LITERAL`. +/// +/// Returns `Some(bytes_consumed)` if the line is a setext underline, `None` otherwise. +/// The byte count includes only the whitespace tokens consumed during the indent skip, +/// NOT the underline token itself. Callers that track byte budgets must subtract this. +/// +/// This is the shared helper for setext detection in inline contexts. +/// Used by `has_matching_code_span_closer`, `parse_inline_html`, and `parse_inline_item_list`. +/// +/// Context safety: this function does NOT call `allow_setext_heading` because the token +/// stream itself encodes context. In blockquotes, `R_ANGLE` tokens appear after NEWLINE +/// before content, so the whitespace-only skip naturally rejects those lines. In list +/// items, the indent reflected in the token stream is the raw line indent, and the +/// `columns < 4` check correctly rejects lines with 4+ columns of leading whitespace. +pub(crate) fn at_setext_underline_after_newline(p: &mut MarkdownParser) -> Option { + let mut columns = 0; + let mut bytes_consumed = 0; + while columns < INDENT_CODE_BLOCK_SPACES + && p.at(MD_TEXTUAL_LITERAL) + && p.cur_text().chars().all(|c| c == ' ' || c == '\t') + { + for c in p.cur_text().chars() { + match c { + ' ' => columns += 1, + '\t' => columns += 4 - (columns % 4), + _ => {} + } + } + bytes_consumed += p.cur_text().len(); + p.bump(MD_TEXTUAL_LITERAL); + } + if columns >= INDENT_CODE_BLOCK_SPACES { + return None; + } + let is_setext = p.at(MD_SETEXT_UNDERLINE_LITERAL) + || (p.at(MD_THEMATIC_BREAK_LITERAL) && is_dash_only_thematic_break_text(p.cur_text())); + if is_setext { + Some(bytes_consumed) + } else { + None + } +} + +/// Token-based check: does an inline span of `byte_len` bytes cross a setext underline? +/// +/// Walks tokens via lookahead. At each NEWLINE, delegates to +/// [`at_setext_underline_after_newline`] — the same detection used by +/// `has_matching_code_span_closer` and `parse_inline_item_list`. +pub(crate) fn inline_span_crosses_setext(p: &mut MarkdownParser, byte_len: usize) -> bool { + p.lookahead(|p| { + let mut remaining = byte_len; + loop { + if remaining == 0 || p.at(T![EOF]) { + return false; + } + if p.at(NEWLINE) { + let nl_len = p.cur_text().len(); + if nl_len > remaining { + return false; + } + remaining -= nl_len; + p.bump(NEWLINE); + if let Some(ws_bytes) = at_setext_underline_after_newline(p) { + // Only flag if the whitespace consumed is still within our span + return ws_bytes <= remaining; + } + continue; + } + let tok_len = p.cur_text().len(); + if tok_len > remaining { + return false; + } + remaining -= tok_len; + p.bump_any(); + } + }) +} + +/// Check if the current thematic break token contains only dashes. +/// This is used to detect H2 setext underlines. +fn is_dash_only_thematic_break(p: &MarkdownParser) -> bool { + is_dash_only_thematic_break_text(p.cur_text()) +} + +fn allow_setext_heading(p: &MarkdownParser) -> bool { + let required_indent = p.state().list_item_required_indent; + if required_indent > 0 { + // Compute real indent from source text, since leading whitespace + // may have been consumed as trivia in list item context. + let indent = real_line_indent_from_source(p); + if indent < required_indent { + return false; + } + } + + if p.state().list_item_required_indent > 0 && p.at(MD_SETEXT_UNDERLINE_LITERAL) { + let text = p.cur_text().trim_matches(|c| c == ' ' || c == '\t'); + if text == "-" { + return false; + } + } + + let depth = p.state().block_quote_depth; + if depth == 0 { + return true; + } + + line_has_quote_prefix(p, depth) +} + +/// Compute the real leading indent of the current line from source text. +/// This is needed because leading whitespace may have been consumed as trivia +/// in list item context, making `line_start_leading_indent()` return 0. +/// Token-based lookahead cannot recover the original column once trivia is skipped. +fn real_line_indent_from_source(p: &MarkdownParser) -> usize { + let source = p.source().source_text(); + let pos: usize = p.cur_range().start().into(); + + // Find the start of the current line + let line_start = source[..pos].rfind('\n').map_or(0, |i| i + 1); + + // Count leading whitespace columns on this line + let mut column = 0; + for c in source[line_start..].chars() { + match c { + ' ' => column += 1, + '\t' => column += 4 - (column % 4), + _ => break, + } + } + column +} + +fn line_has_quote_prefix(p: &MarkdownParser, depth: usize) -> bool { + // Tokens may have consumed whitespace as trivia; scan source to recover columns. + if depth == 0 { + return false; + } + + let source = p.source().source_text(); + let start: usize = p.cur_range().start().into(); + let line_start = source[..start].rfind('\n').map_or(0, |idx| idx + 1); + + let mut idx = line_start; + let mut indent = 0usize; + while idx < start { + match source.as_bytes()[idx] { + b' ' => { + indent += 1; + idx += 1; + } + b'\t' => { + indent += 4; + idx += 1; + } + _ => break, + } + if indent > 3 { + return false; + } + } + + for _ in 0..depth { + if idx >= start || source.as_bytes()[idx] != b'>' { + return false; + } + idx += 1; + if idx < start { + let c = source.as_bytes()[idx]; + if c == b' ' || c == b'\t' { + idx += 1; + } + } + } + + true +} + +fn classify_quote_break_after_newline( + p: &mut MarkdownParser, + quote_depth: usize, + include_textual_markers: bool, +) -> QuoteBreakKind { + p.lookahead(|p| { + consume_quote_prefix_without_virtual(p, quote_depth); + with_virtual_line_start(p, p.cur_range().start(), |p| { + if p.at(MD_SETEXT_UNDERLINE_LITERAL) + || (p.at(MD_THEMATIC_BREAK_LITERAL) && is_dash_only_thematic_break(p)) + { + QuoteBreakKind::SetextUnderline + } else if at_block_interrupt(p) + || (include_textual_markers && textual_looks_like_list_marker(p)) + { + QuoteBreakKind::Other + } else { + QuoteBreakKind::None + } + }) + }) +} + +enum InlineNewlineAction { + Break, + Continue, +} + +fn handle_inline_newline(p: &mut MarkdownParser, has_content: bool) -> InlineNewlineAction { + if p.at_blank_line() { + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + return InlineNewlineAction::Break; + } + + let quote_depth = p.state().block_quote_depth; + if quote_depth > 0 { + let is_quote_blank_line = p.lookahead(|p| { + p.bump(NEWLINE); + if is_quote_only_blank_line_from_source(p, quote_depth) { + return true; + } + if !has_quote_prefix(p, quote_depth) { + return false; + } + consume_quote_prefix_without_virtual(p, quote_depth); + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + if is_quote_blank_line { + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + return InlineNewlineAction::Break; + } + } + + // Not a blank line - this is a soft line break within paragraph + // Consume the NEWLINE as textual content (remap to MD_TEXTUAL_LITERAL) + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + + // If we're inside a block quote, only consume the quote prefix + // when it doesn't start a new block (e.g., a nested quote). + if quote_depth > 0 && has_quote_prefix(p, quote_depth) { + let break_kind = classify_quote_break_after_newline(p, quote_depth, true); + match break_kind { + QuoteBreakKind::SetextUnderline => { + // Consume the quote prefix so the setext underline is visible + // to the paragraph parser. + consume_quote_prefix(p, quote_depth); + return InlineNewlineAction::Break; + } + QuoteBreakKind::Other => { + return InlineNewlineAction::Break; + } + QuoteBreakKind::None => { + consume_quote_prefix(p, quote_depth); + } + } + } + if quote_depth > 0 && p.at(R_ANGLE) && !has_quote_prefix(p, quote_depth) { + consume_partial_quote_prefix(p, quote_depth); + } + + // After crossing a line, check for setext underlines. + // For non-list paragraphs, we need to look past up to 3 spaces of indent + // to detect setext underlines (CommonMark §4.3). + // IMPORTANT: Only break if allow_setext_heading() is true - this ensures + // setext underlines outside a blockquote (without >) don't incorrectly + // terminate the paragraph (CommonMark example 093). + if has_content && p.state().list_item_required_indent == 0 && allow_setext_heading(p) { + let is_setext = p.lookahead(|p| at_setext_underline_after_newline(p).is_some()); + if is_setext { + // Skip the indent so parse_paragraph sees the underline + p.skip_line_indent(INDENT_CODE_BLOCK_SPACES); + return InlineNewlineAction::Break; + } + } + + // Check if we're at a setext heading underline (already past indent) + if has_content && p.at(MD_SETEXT_UNDERLINE_LITERAL) && allow_setext_heading(p) { + return InlineNewlineAction::Break; + } + if has_content && p.at(MD_THEMATIC_BREAK_LITERAL) && is_dash_only_thematic_break(p) { + return InlineNewlineAction::Break; + } + + // If we're inside a list item and the next line meets the required indent, + // check for block interrupts after skipping that indent. This allows + // nested list markers like "\t - baz" to break out of the paragraph. + let required_indent = p.state().list_item_required_indent; + if required_indent > 0 { + // Check for setext underline after indent stripping. + // The `---` or `===` may be indented by the list item's required indent, + // so we need to look past that indent. + let real_indent = real_line_indent_from_source(p); + if real_indent >= required_indent { + let is_setext = p.lookahead(|p| { + p.skip_line_indent(required_indent); + p.at(MD_SETEXT_UNDERLINE_LITERAL) + || (p.at(MD_THEMATIC_BREAK_LITERAL) && is_dash_only_thematic_break(p)) + }); + if is_setext && has_content { + // Skip the indent so parse_paragraph sees the underline + p.skip_line_indent(required_indent); + return InlineNewlineAction::Break; + } + } + + let indent = p.line_start_leading_indent(); + if indent >= required_indent { + let interrupts = p.lookahead(|p| { + p.skip_line_indent(required_indent); + let prev_required = p.state().list_item_required_indent; + with_virtual_line_start(p, p.cur_range().start(), |p| { + p.state_mut().list_item_required_indent = 0; + let breaks = at_block_interrupt(p) || textual_looks_like_list_marker(p); + p.state_mut().list_item_required_indent = prev_required; + breaks + }) + }); + if interrupts { + return InlineNewlineAction::Break; + } + } + } + + // Check for block-level constructs that can interrupt paragraphs + if line_starts_with_fence(p) { + return InlineNewlineAction::Break; + } + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text.starts_with("```") || text.starts_with("~~~") { + return InlineNewlineAction::Break; + } + } + if at_block_interrupt(p) { + return InlineNewlineAction::Break; + } + + // Also check for list markers that appear as textual content. + // Inside inline content, '-' is lexed as MD_TEXTUAL_LITERAL, not MINUS, + // so at_block_interrupt won't detect them. Per CommonMark §5.1, list + // items can interrupt paragraphs (bullet lists always, ordered lists + // only if they start with 1). + if textual_looks_like_list_marker(p) { + return InlineNewlineAction::Break; + } + + // Per CommonMark §5.2, when inside a list item, check indentation. + // If sufficient indentation, skip it. If insufficient, this is + // "lazy continuation" - the content continues without meeting the + // indent requirement (at_block_interrupt already checked above). + if required_indent > 0 { + let indent = p.line_start_leading_indent(); + if indent >= required_indent { + // Sufficient indentation - skip it + p.skip_line_indent(required_indent); + } + // else: Lazy continuation - don't break, don't skip indent. + // The at_block_interrupt check above handles real interruptions. + // Content continues at its actual position. + } + + // For plain paragraphs, strip up to 4 leading spaces on continuation lines. + if required_indent == 0 { + p.skip_line_indent(INDENT_CODE_BLOCK_SPACES); + } + + InlineNewlineAction::Continue +} + +/// Parse the inline item list within a block. +/// +/// Grammar: MdInlineItemList = AnyMdInline* +/// +/// Inline content continues until we hit EOF, a blank line (paragraph boundary), +/// a setext heading underline, or a block-level construct that can interrupt +/// paragraphs per CommonMark. +/// +/// # NEWLINE Handling +/// +/// NEWLINE is an explicit token (not trivia). When we hit NEWLINE: +/// - If it's a blank line (NEWLINE + optional whitespace + NEWLINE/EOF) → stop +/// - Otherwise it's a soft line break → consume and continue to next line +pub(crate) fn parse_inline_item_list(p: &mut MarkdownParser) { + let m = p.start(); + let prev_emphasis_context = set_inline_emphasis_context(p); + let quote_depth = p.state().block_quote_depth; + if quote_depth > 0 && p.at_line_start() && has_quote_prefix(p, quote_depth) { + consume_quote_prefix(p, quote_depth); + } + let inline_start: usize = p.cur_range().start().into(); + let mut has_content = false; + + loop { + // EOF ends inline content + if p.at(T![EOF]) { + break; + } + + // NEWLINE handling: check for blank line (paragraph boundary) + if p.at(NEWLINE) { + if matches!( + handle_inline_newline(p, has_content), + InlineNewlineAction::Break + ) { + break; + } + continue; + } + + // Check if we're at a setext heading underline (stop for paragraph to handle) + // Per CommonMark §4.3, setext underlines can be indented 0-3 spaces only. + if has_content + && p.at(MD_SETEXT_UNDERLINE_LITERAL) + && real_line_indent_from_source(p) < INDENT_CODE_BLOCK_SPACES + && allow_setext_heading(p) + { + break; + } + + // Check if we're at a thematic break that could be a setext underline + // (dash-only thematic breaks following paragraph content are setext H2) + if has_content + && p.at(MD_THEMATIC_BREAK_LITERAL) + && real_line_indent_from_source(p) < INDENT_CODE_BLOCK_SPACES + && is_dash_only_thematic_break(p) + { + break; + } + + // Per CommonMark, certain block-level constructs can interrupt paragraphs + // without requiring a blank line. Check for these at line start. + if p.has_preceding_line_break() && at_block_interrupt(p) { + break; + } + + // Also check for list markers in textual content (see comment above) + if p.has_preceding_line_break() && textual_looks_like_list_marker(p) { + break; + } + + // Parse inline content (stops at NEWLINE via at_inline_end) + let parsed = parse_any_inline(p); + if parsed.is_absent() { + break; + } + let after_hard_break = matches!(&parsed, Present(cm) if cm.kind(p) == MD_HARD_LINE); + + // Per CommonMark §6.7: after a hard line break, leading spaces on the + // next line are ignored. Skip whitespace-only textual tokens as trivia. + if after_hard_break + && p.at(MD_TEXTUAL_LITERAL) + && p.cur_text().chars().all(|c| c == ' ' || c == '\t') + { + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + } + + let inline_end: usize = p.cur_range().start().into(); + has_content = inline_has_non_whitespace(p, inline_start, inline_end); + } + + m.complete(p, MD_INLINE_ITEM_LIST); + p.set_emphasis_context(prev_emphasis_context); +} + +fn is_quote_only_blank_line_from_source(p: &MarkdownParser, depth: usize) -> bool { + if depth == 0 { + return false; + } + + let source = p.source().source_text(); + let start: usize = p.cur_range().start().into(); + if start >= source.len() { + return true; + } + + // Scan up to 3 spaces/tabs before the first '>' + let mut idx = start; + let mut indent = 0usize; + while idx < source.len() { + match source.as_bytes()[idx] { + b' ' => { + indent += 1; + idx += 1; + } + b'\t' => { + indent += 4 - (indent % 4); + idx += 1; + } + _ => break, + } + if indent > 3 { + return false; + } + } + + // Consume quote markers and optional single space after each + for _ in 0..depth { + if idx >= source.len() || source.as_bytes()[idx] != b'>' { + return false; + } + idx += 1; + if idx < source.len() { + let c = source.as_bytes()[idx]; + if c == b' ' || c == b'\t' { + idx += 1; + } + } + } + + // Skip trailing whitespace + while idx < source.len() { + match source.as_bytes()[idx] { + b' ' | b'\t' => idx += 1, + _ => break, + } + } + + // Blank if line ends here or at newline + if idx >= source.len() { + return true; + } + + matches!(source.as_bytes()[idx], b'\n' | b'\r') +} + +/// Build an emphasis context for the current inline list and install it on the parser. +/// Returns the previous context so it can be restored. +fn set_inline_emphasis_context(p: &mut MarkdownParser) -> Option { + let source_len = inline_list_source_len(p); + let source = p.source_after_current(); + let inline_source = if source_len <= source.len() { + &source[..source_len] + } else { + source + }; + let base_offset = u32::from(p.cur_range().start()) as usize; + // Create a reference checker closure that uses the parser's link reference definitions + let context = EmphasisContext::new(inline_source, base_offset, |label| { + p.has_link_reference_definition(label) + }); + p.set_emphasis_context(Some(context)) +} + +/// Compute the byte length of the inline list starting at the current token. +fn inline_list_source_len(p: &mut MarkdownParser) -> usize { + p.lookahead(|p| { + let mut len = 0usize; + + loop { + if p.at(T![EOF]) { + break; + } + + if p.at(NEWLINE) { + if p.at_blank_line() { + len += p.cur_text().len(); + p.bump(NEWLINE); + break; + } + + len += p.cur_text().len(); + p.bump(NEWLINE); + + let quote_depth = p.state().block_quote_depth; + if quote_depth > 0 && has_quote_prefix(p, quote_depth) { + let break_kind = classify_quote_break_after_newline(p, quote_depth, false); + if !matches!(break_kind, QuoteBreakKind::None) { + break; + } + consume_quote_prefix_without_virtual(p, quote_depth); + } + + if p.at(MD_SETEXT_UNDERLINE_LITERAL) && allow_setext_heading(p) { + break; + } + + if p.at(MD_THEMATIC_BREAK_LITERAL) && is_dash_only_thematic_break(p) { + break; + } + + if quote_depth > 0 && p.at(R_ANGLE) && !has_quote_prefix(p, quote_depth) { + consume_partial_quote_prefix_lookahead(p, quote_depth, &mut len); + } + + if line_starts_with_fence(p) { + break; + } + + if at_block_interrupt(p) { + break; + } + + let required_indent = p.state().list_item_required_indent; + if required_indent > 0 { + let indent = p.line_start_leading_indent(); + if indent < required_indent { + break; + } + + let mut consumed = 0usize; + while consumed < required_indent && p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text.is_empty() || !text.chars().all(|c| c == ' ' || c == '\t') { + break; + } + + let indent = text + .chars() + .map(|c| if c == '\t' { 4 } else { 1 }) + .sum::(); + + if consumed + indent > required_indent { + break; + } + + consumed += indent; + len += text.len(); + p.bump(MD_TEXTUAL_LITERAL); + } + } + + continue; + } + + if p.at(MD_SETEXT_UNDERLINE_LITERAL) && allow_setext_heading(p) { + break; + } + + if p.at(MD_THEMATIC_BREAK_LITERAL) && is_dash_only_thematic_break(p) { + break; + } + + if p.has_preceding_line_break() && at_block_interrupt(p) { + break; + } + + len += p.cur_text().len(); + p.bump(p.cur()); + } + + len + }) +} + +fn line_starts_with_fence(p: &mut MarkdownParser) -> bool { + if !p.at_line_start() { + return false; + } + + p.lookahead(|p| { + if p.line_start_leading_indent() > 3 { + return false; + } + p.skip_line_indent(3); + let rest = p.source_after_current(); + let Some((fence_char, _len)) = fenced_code_block::detect_fence(rest) else { + return false; + }; + if fence_char == '`' { + return !info_string_has_backtick(p); + } + true + }) +} + +fn consume_partial_quote_prefix(p: &mut MarkdownParser, depth: usize) -> bool { + let mut consumed = 0usize; + while consumed < depth && p.at(R_ANGLE) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(R_ANGLE)); + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + } + consumed += 1; + } + consumed > 0 +} + +fn consume_partial_quote_prefix_lookahead( + p: &mut MarkdownParser, + depth: usize, + len: &mut usize, +) -> bool { + let mut consumed = 0usize; + while consumed < depth && p.at(R_ANGLE) { + *len += p.cur_text().len(); + p.bump(R_ANGLE); + if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text == " " || text == "\t" { + *len += text.len(); + p.bump(MD_TEXTUAL_LITERAL); + } + } + consumed += 1; + } + consumed > 0 +} + +/// Check if we're at a block-level construct that can interrupt a paragraph. +/// +/// Per CommonMark, these constructs can interrupt paragraphs: +/// - ATX headings (# followed by space or EOL) +/// - Fenced code blocks (``` or ~~~) +/// - Block quotes (>) +/// - Thematic breaks (---, ***, ___) +/// - List items (-, *, +, or ordered markers) - with restrictions +/// - HTML blocks (certain types) +/// +/// Note: Setext headings and indented code blocks do NOT interrupt paragraphs. +/// Also, lines with 4+ spaces of indentation cannot start these constructs +/// (they would be indented code blocks instead). +/// +/// ## List Interruption Rules (CommonMark §5.2) +/// +/// - Bullet lists can interrupt paragraphs if item has content OR marker is followed by blank line +/// - Ordered lists can interrupt paragraphs ONLY if starting with `1` AND not empty +pub(crate) fn at_block_interrupt(p: &mut MarkdownParser) -> bool { + // Per CommonMark, lines indented 4+ spaces cannot start block constructs + // that interrupt paragraphs - they would be indented code blocks. + // Tabs count as 4 spaces per CommonMark §2.2. + if p.line_start_leading_indent() >= 4 { + // Inside list items, allow list markers at the current indent to + // interrupt paragraphs (nested lists). + if (p.state().list_nesting_depth > 0 || p.state().list_item_required_indent > 0) + && (at_bullet_list_item(p) || at_order_list_item(p)) + { + return true; + } + return false; + } + + // ATX heading: # at line start (must have space/tab after per CommonMark §4.2) + // Use checkpoint to look ahead and verify it's a valid heading + if at_header(p) { + return is_valid_atx_heading_start(p); + } + + // Fenced code block (``` or ~~~) - lexer already ensures line start + if at_fenced_code_block(p) { + return true; + } + + // Block quote (>) + if at_quote(p) { + return true; + } + + // Thematic break (---, ***, ___) - lexer already ensures line start + if at_thematic_break_block(p) { + return true; + } + + // Bullet list item (-, *, +) + // Per CommonMark §5.2: bullet lists can interrupt paragraphs only if the + // item has content (non-empty). Empty markers cannot interrupt paragraphs. + // When inside a list, we also need to check for list items at ANY indent + // (not just at the current context's indent) because a less-indented list + // marker would end the current list item and start a sibling/parent item. + if at_bullet_list_item(p) || at_bullet_list_item_at_any_indent(p) { + let in_list = p.state().list_nesting_depth > 0; + if in_list || can_bullet_interrupt_paragraph(p) { + return true; + } + } + + // Ordered list item (1., 2), etc.) + // Per CommonMark §5.2: ordered lists can interrupt TOP-LEVEL paragraphs only if: + // - Starting with 1 (not 2, 3, etc.), AND + // - The item has content (empty ordered items cannot interrupt) + // Inside a list context, any ordered marker can start a new sibling item. + if at_order_list_item(p) || at_order_list_item_at_any_indent(p) { + let in_list = p.state().list_nesting_depth > 0; + if in_list || (is_ordered_list_starts_with_one(p) && !is_empty_list_item(p)) { + return true; + } + } + + // HTML block (type 7 does not interrupt paragraphs) + if at_html_block_interrupt(p) { + return true; + } + + false +} + +/// Check if the current token looks like a list marker when lexed as textual content. +/// +/// Inside inline content, list markers like `- ` are lexed as MD_TEXTUAL_LITERAL. +/// This function checks if a textual token at line start looks like it would be +/// a list marker in block context. +fn textual_looks_like_list_marker(p: &mut MarkdownParser) -> bool { + if !p.at(MD_TEXTUAL_LITERAL) { + return false; + } + + let text = p.cur_text(); + + // Bullet marker: single -, *, or + followed by space/tab or EOF + if text == "-" || text == "*" || text == "+" { + // Check if followed by space, tab, or at end of line + return p.lookahead(|p| { + p.bump(MD_TEXTUAL_LITERAL); + if p.at(T![EOF]) || p.at(NEWLINE) { + return true; + } + if p.at(MD_TEXTUAL_LITERAL) { + let next = p.cur_text(); + return next.starts_with(' ') || next.starts_with('\t'); + } + false + }); + } + + // Ordered marker: per CommonMark §5.1, only ordered lists starting with 1 + // can interrupt paragraphs. Check if text is "1." or "1)" pattern. + if let Some(rest) = text.strip_prefix('1') { + // "1." or "1)" followed by space (the space might be in next token) + if rest == "." || rest == ")" { + return p.lookahead(|p| { + p.bump(MD_TEXTUAL_LITERAL); + if p.at(T![EOF]) || p.at(NEWLINE) { + return true; + } + if p.at(MD_TEXTUAL_LITERAL) { + let next = p.cur_text(); + return next.starts_with(' ') || next.starts_with('\t'); + } + false + }); + } + // "1. " or "1) " all in one token + if rest.starts_with(". ") + || rest.starts_with(".\t") + || rest.starts_with(") ") + || rest.starts_with(")\t") + { + return true; + } + } + + false +} + +/// Check if an ordered list marker starts with the number 1. +/// +/// Per CommonMark §5.2: "In order to solve of an ambiguity in the spec, +/// only ordered lists starting with 1 can interrupt paragraphs." +/// +/// This prevents accidental list creation from wrapped lines like: +/// "The number of windows is 14. The number of doors is 6." +fn is_ordered_list_starts_with_one(p: &mut MarkdownParser) -> bool { + if !p.at(MD_ORDERED_LIST_MARKER) { + return false; + } + + // The marker text includes digits + delimiter (e.g., "1.", "2)", "10.") + // We want exactly "1." or "1)" - not "10.", "11.", etc. + let text = p.cur_text(); + text == "1." || text == "1)" +} + +/// Check for a bullet list item at any valid top-level indent (0-3 spaces). +/// +/// This is used for detecting paragraph interruption when we're inside a nested +/// context (like a list item) but need to detect list markers at any level, +/// not just at the current context's indent level. +fn at_bullet_list_item_at_any_indent(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + if !p.at_line_start() { + return false; + } + + // Top-level list items can have 0-3 spaces of leading indent + let indent = p.line_start_leading_indent(); + if indent > 3 { + return false; + } + + // Skip leading whitespace tokens + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + } + + // Check for -, *, or + marker + if p.at(MD_SETEXT_UNDERLINE_LITERAL) { + let trimmed = p.cur_text().trim_matches(|c| c == ' ' || c == '\t'); + if trimmed != "-" { + return false; + } + } else if !p.at(T![-]) && !p.at(T![*]) && !p.at(T![+]) { + return false; + } + + if p.at(MD_SETEXT_UNDERLINE_LITERAL) { + p.bump_remap(T![-]); + } else { + p.bump(p.cur()); + } + marker_followed_by_whitespace_or_eol(p) + }) +} + +/// Check for an ordered list item at any valid top-level indent (0-3 spaces). +/// +/// This is used for detecting paragraph interruption when we're inside a nested +/// context (like a list item) but need to detect list markers at any level. +fn at_order_list_item_at_any_indent(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + if !p.at_line_start() { + return false; + } + + // Top-level list items can have 0-3 spaces of leading indent + let indent = p.line_start_leading_indent(); + if indent > 3 { + return false; + } + + // Skip leading whitespace tokens + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + } + + // Check for ordered list marker (lexer produces MD_ORDERED_LIST_MARKER) + p.at(MD_ORDERED_LIST_MARKER) + }) +} + +fn at_order_list_item_textual(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + if !p.at_line_start() { + return false; + } + + let indent = p.line_start_leading_indent(); + let base_indent = if p.state().virtual_line_start == Some(p.cur_range().start()) + && p.state().list_item_required_indent > 0 + { + 0 + } else { + p.state().list_item_required_indent + }; + + if base_indent == 0 { + if indent > 3 { + return false; + } + } else if indent < base_indent || indent > base_indent + 3 { + return false; + } + + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + } + + p.at(MD_TEXTUAL_LITERAL) && textual_starts_with_ordered_marker(p.cur_text()) + }) +} + +/// Check if a bullet list item can interrupt a top-level paragraph. +/// +/// Per CommonMark §5.2: "A bullet list can interrupt a paragraph only if +/// it starts with a non-empty item (that is, a list item that contains +/// some non-blank character)." +/// +/// This means empty markers (marker followed by only whitespace/newline) +/// cannot interrupt paragraphs, regardless of what follows. +fn can_bullet_interrupt_paragraph(p: &mut MarkdownParser) -> bool { + let checkpoint = p.checkpoint(); + + // Bump the bullet marker (-, *, or +) + if p.at(T![-]) { + p.bump(T![-]); + } else if p.at(T![*]) { + p.bump(T![*]); + } else if p.at(T![+]) { + p.bump(T![+]); + } else { + p.rewind(checkpoint); + return false; + } + + if !marker_followed_by_whitespace_or_eol(p) { + p.rewind(checkpoint); + return false; + } + + // Check what follows the marker + // Per CommonMark §5.2: "A bullet list can interrupt a paragraph only if + // it starts with a non-empty item (that is, a list item that contains + // some non-blank character)." + let result = if p.at(T![EOF]) { + // Empty item at EOF - cannot interrupt + false + } else if p.at(NEWLINE) { + // Empty item (marker + newline) - cannot interrupt paragraphs + false + } else if p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + // Skip all whitespace tokens after marker + while p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + // If only whitespace followed by newline/EOF, item is empty and cannot interrupt + !(p.at(NEWLINE) || p.at(T![EOF])) + } else { + // Has content after marker - can interrupt + true + }; + + p.rewind(checkpoint); + result +} + +/// Check if the current list item is empty (no content after marker). +/// +/// Per CommonMark §5.2: "an empty list item cannot interrupt a paragraph." +/// +/// Uses lookahead to check if only whitespace/newline follows the marker. +fn is_empty_list_item(p: &mut MarkdownParser) -> bool { + let checkpoint = p.checkpoint(); + + // Bump the list marker + if p.at(MD_ORDERED_LIST_MARKER) { + p.bump(MD_ORDERED_LIST_MARKER); + } else if p.at(T![-]) { + p.bump(T![-]); + } else if p.at(T![*]) { + p.bump(T![*]); + } else if p.at(T![+]) { + p.bump(T![+]); + } else { + p.rewind(checkpoint); + return false; + } + + if !marker_followed_by_whitespace_or_eol(p) { + p.rewind(checkpoint); + return false; + } + + // Check what follows the marker + if p.at(MD_TEXTUAL_LITERAL) && is_whitespace_only(p.cur_text()) { + p.bump(MD_TEXTUAL_LITERAL); + } + + // Empty if: EOF or NEWLINE after optional whitespace + let is_empty = p.at(T![EOF]) || p.at(NEWLINE); + + p.rewind(checkpoint); + is_empty +} + +fn is_whitespace_only(text: &str) -> bool { + !text.is_empty() && text.chars().all(|c| c == ' ' || c == '\t') +} + +/// Check if the current position has too many hashes for an ATX heading (>6). +/// +/// Returns `Some((range, count))` if there are >6 hashes, `None` otherwise. +/// This is used to emit a diagnostic BEFORE `try_parse` which would lose it on rewind. +fn check_too_many_hashes(p: &mut MarkdownParser) -> Option<(biome_rowan::TextRange, usize)> { + p.lookahead(|p| { + p.skip_line_indent(3); + + if !p.at(T![#]) { + return None; + } + + // The lexer emits all consecutive `#` as a single HASH token. + // Get the count from token text length. + let range = p.cur_range(); + let count = p.cur_text().len(); + + if count > 6 { + Some((range, count)) + } else { + None + } + }) +} + +/// Check if we're at a valid ATX heading start (1-6 `#` followed by space or EOL). +/// Uses lookahead to verify without consuming tokens. +fn is_valid_atx_heading_start(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + p.skip_line_indent(3); + + // The lexer emits all consecutive `#` as a single HASH token. + // Count hash characters from the token's text length. + if !p.at(T![#]) { + return false; + } + + let hash_count = p.cur_text().len(); + + // Too many hashes - not a valid heading (must be 1-6) + if hash_count > 6 { + return false; + } + + p.bump(T![#]); + + // Check if followed by space, tab, or EOL/EOF per CommonMark §4.2 + // In Markdown, whitespace is significant and included in token text. + let text = p.cur_text(); + p.at(T![EOF]) + || p.has_preceding_line_break() + || text.starts_with(' ') + || text.starts_with('\t') + }) +} + +/// Parse any inline element. +/// +/// Dispatches to the appropriate inline parser based on the current token. +pub(crate) fn parse_any_inline(p: &mut MarkdownParser) -> ParsedSyntax { + inline::parse_any_inline(p) +} + +/// Parse a textual inline element. +/// +/// Grammar: MdTextual = value: 'md_textual_literal' +/// +/// For now, we treat any non-EOF token as textual content to ensure +/// the paragraph parser makes progress. In later prompts, we'll add +/// proper handling for inline elements like emphasis, links, etc. +pub(crate) fn parse_textual(p: &mut MarkdownParser) -> ParsedSyntax { + if p.at(T![EOF]) { + return Absent; + } + let m = p.start(); + // Remap any token to MD_TEXTUAL_LITERAL so the syntax factory accepts it. + // This is necessary because tokens like L_PAREN, R_PAREN, etc. are lexed + // as their specific token kinds, but MdTextual expects MD_TEXTUAL_LITERAL. + p.bump_remap(MD_TEXTUAL_LITERAL); + Present(m.complete(p, MD_TEXTUAL)) +} + +/// Attempt to parse some input with the given parsing function. If parsing +/// succeeds, `Ok` is returned with the result of the parse and the state is +/// preserved. If parsing fails, this function rewinds the parser back to +/// where it was before attempting the parse and the `Err` value is returned. +#[must_use = "The result of try_parse contains information about whether the parse succeeded and should not be ignored"] +pub(crate) fn try_parse( + p: &mut MarkdownParser, + func: impl FnOnce(&mut MarkdownParser) -> Result, +) -> Result { + let checkpoint = p.checkpoint(); + + let res = func(p); + + if res.is_err() { + p.rewind(checkpoint); + } + + res +} diff --git a/crates/biome_markdown_parser/src/syntax/parse_error.rs b/crates/biome_markdown_parser/src/syntax/parse_error.rs new file mode 100644 index 000000000000..97ce5288ac56 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/parse_error.rs @@ -0,0 +1,154 @@ +//! Markdown-specific parse error diagnostics. + +use crate::MarkdownParser; +use biome_parser::Parser; +use biome_parser::diagnostic::ParseDiagnostic; +use biome_rowan::TextRange; + +/// Default maximum nesting depth for block quotes and lists. +pub(crate) const DEFAULT_MAX_NESTING_DEPTH: usize = 100; + +/// Unclosed emphasis (bold/italic). +/// +/// ```markdown +/// *text +/// ^ expected closing * +/// ``` +pub(crate) fn unclosed_emphasis( + p: &MarkdownParser, + opening_range: TextRange, + marker: &str, +) -> ParseDiagnostic { + p.err_builder( + format!("Unclosed emphasis, expected closing `{marker}`."), + opening_range, + ) + .with_detail(opening_range, "emphasis started here") + .with_hint(format!( + "Add closing `{marker}` or remove the opening delimiter." + )) +} + +/// Unclosed inline link. +/// +/// ```markdown +/// [text +/// ^ expected closing ] and (url) +/// ``` +pub(crate) fn unclosed_link( + p: &MarkdownParser, + opening_range: TextRange, + missing_part: &str, +) -> ParseDiagnostic { + p.err_builder(format!("Unclosed link, {missing_part}."), opening_range) + .with_detail(opening_range, "link started here") + .with_hint("Format: [link text](url)") +} + +/// Unclosed inline image. +/// +/// ```markdown +/// ![alt +/// ^ expected closing ] and (src) +/// ``` +pub(crate) fn unclosed_image( + p: &MarkdownParser, + opening_range: TextRange, + missing_part: &str, +) -> ParseDiagnostic { + p.err_builder(format!("Unclosed image, {missing_part}."), opening_range) + .with_detail(opening_range, "image started here") + .with_hint("Format: ![alt text](image-url)") +} + +/// ATX heading with too many hashes (>6). +/// +/// ```markdown +/// ####### heading +/// ^^^^^^^ too many hashes (max 6) +/// ``` +pub(crate) fn too_many_hashes( + p: &MarkdownParser, + range: TextRange, + count: usize, +) -> ParseDiagnostic { + p.err_builder( + format!("ATX heading has {count} hashes, but maximum is 6."), + range, + ) + .with_detail(range, "heading started here") + .with_hint("Use 1-6 `#` characters for headings. This will be parsed as a paragraph.") +} + +/// Unterminated fenced code block. +/// +/// ```markdown +/// ```rust +/// fn main() {} +/// +/// ^ expected closing ``` +/// ``` +pub(crate) fn unterminated_fenced_code( + p: &MarkdownParser, + opening_range: TextRange, + fence_type: &str, +) -> ParseDiagnostic { + let fence_name = if fence_type == "```" { + "triple backticks (```)" + } else { + "triple tildes (~~~)" + }; + p.err_builder( + format!("Unterminated fenced code block, expected closing {fence_name}."), + opening_range, + ) + .with_detail(opening_range, "code block started here") + .with_hint(format!( + "Add closing {fence_name} at the start of a new line." + )) +} + +/// Block quote nesting too deep. +/// +/// ```markdown +/// >>>>>>>>...>>>> (100+ levels) +/// ^^^^^^^^^^^^^^^^ nesting too deep +/// ``` +pub(crate) fn quote_nesting_too_deep( + p: &MarkdownParser, + range: TextRange, + max_nesting_depth: usize, +) -> ParseDiagnostic { + p.err_builder( + format!("Block quote nesting exceeds maximum depth of {max_nesting_depth}."), + range, + ) + .with_detail(range, "nesting limit reached here") + .with_hint("Reduce nesting depth. Additional levels will be treated as content.") +} + +/// List nesting too deep. +/// +/// ```markdown +/// - - - - ... - (100+ levels) +/// ^^^^^^^^^^^^^^ nesting too deep +/// ``` +pub(crate) fn list_nesting_too_deep( + p: &MarkdownParser, + range: TextRange, + max_nesting_depth: usize, +) -> ParseDiagnostic { + p.err_builder( + format!("List nesting exceeds maximum depth of {max_nesting_depth}."), + range, + ) + .with_detail(range, "nesting limit reached here") + .with_hint("Reduce nesting depth. Additional levels will be treated as content.") +} + +/// Parser made no progress while parsing a block. +pub(crate) fn parse_any_block_no_progress(p: &MarkdownParser, range: TextRange) -> ParseDiagnostic { + p.err_builder("Parser made no progress while parsing a block.", range) + .with_detail(range, "stuck token skipped") + .with_hint("This is likely a parser bug; the token was skipped to recover.") +} diff --git a/crates/biome_markdown_parser/src/syntax/quote.rs b/crates/biome_markdown_parser/src/syntax/quote.rs new file mode 100644 index 000000000000..aacec0d01df8 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/quote.rs @@ -0,0 +1,418 @@ +//! Block quote parsing for Markdown (CommonMark §5.1). +//! +//! A block quote begins with `>` at the start of a line and can contain +//! nested block elements. Multiple consecutive `>` lines form a single quote. +//! Nested quotes are created with `>>`, `>>>`, etc. +//! +//! # CommonMark §5.1 Block Quotes +//! +//! A block quote marker consists of 0-3 spaces of indentation, `>`, and an +//! optional space. The contents of the block quote are the result of parsing +//! the remainder of the line (after the `>` and optional space) as blocks. +//! +//! ## Depth Limits +//! +//! To prevent stack overflow from pathological input (e.g., hundreds of `>`), +//! nesting depth is limited by `MarkdownParseOptions::max_nesting_depth` +//! (default: 100). Deeper nesting emits a diagnostic and treats additional +//! `>` as content. +//! +//! ## Lazy Continuation (§5.1) +//! +//! A block quote can contain "lazy continuation lines" — paragraph content +//! that continues without requiring `>` on each line. For example: +//! +//! ```markdown +//! > This is a quote +//! that continues here without > +//! ``` +//! +//! Both lines belong to the same block quote. Lazy continuation stops at: +//! - A blank line +//! - A line that starts another block-level construct (header, code, list, etc.) + +use biome_markdown_syntax::T; +use biome_markdown_syntax::kind::MarkdownSyntaxKind::{self, *}; +use biome_parser::Parser; +use biome_parser::parse_lists::ParseNodeList; +use biome_parser::parse_recovery::RecoveryResult; +use biome_parser::prelude::ParsedSyntax::{self, *}; + +use crate::MarkdownParser; +use crate::syntax::parse_any_block_with_indent_code_policy; +use crate::syntax::parse_error::quote_nesting_too_deep; +use crate::syntax::{INDENT_CODE_BLOCK_SPACES, TAB_STOP_SPACES, is_paragraph_like}; + +/// Check if we're at the start of a block quote (`>`). +pub(crate) fn at_quote(p: &mut MarkdownParser) -> bool { + p.lookahead(|p| { + let at_virtual_line_start = p.state().virtual_line_start == Some(p.cur_range().start()); + if !p.at_line_start() && !p.at_start_of_input() && !at_virtual_line_start { + return false; + } + let mut indent = p.line_start_leading_indent(); + if at_virtual_line_start && indent > 0 { + // Treat virtual line start as column 0. + indent = 0; + } + if indent > 3 { + return false; + } + p.skip_line_indent(3); + p.at(T![>]) + }) +} + +/// Parse a block quote. +/// +/// Grammar: MdQuote = marker: '>' content: AnyMdBlock +/// +/// A block quote starts with `>` at line start and contains block content. +/// Multi-line quotes: consecutive `>` lines continue the same quote's content. +/// Nested quotes: `>>` creates a nested quote inside the outer quote. +/// +/// Nesting is limited to `MarkdownParseOptions::max_nesting_depth` to prevent stack overflow. +pub(crate) fn parse_quote(p: &mut MarkdownParser) -> ParsedSyntax { + if !at_quote(p) { + return Absent; + } + + let max_nesting_depth = p.options().max_nesting_depth; + if p.state().block_quote_depth >= max_nesting_depth { + let range = p.cur_range(); + p.error(quote_nesting_too_deep(p, range, max_nesting_depth)); + p.state_mut().quote_depth_exceeded = true; + p.skip_line_indent(3); + if p.at(T![>]) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(T![>])); + } else if p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == ">" { + p.parse_as_skipped_trivia_tokens(|p| p.bump_remap(T![>])); + } + let has_indented_code = at_quote_indented_code_start(p); + skip_optional_marker_space(p, has_indented_code); + return Absent; + } + + let m = p.start(); + + p.skip_line_indent(3); + + // Increment quote depth + p.state_mut().block_quote_depth += 1; + + // Bump the `>` marker token + p.bump(T![>]); + + let has_indented_code = at_quote_indented_code_start(p); + let marker_space = skip_optional_marker_space(p, has_indented_code); + p.set_virtual_line_start(); + + parse_quote_block_list(p); + + // Decrement quote depth + p.state_mut().block_quote_depth -= 1; + + let completed = m.complete(p, MD_QUOTE); + let range = completed.range(p); + let indent = 1 + if marker_space { 1 } else { 0 }; + p.record_quote_indent(range, indent); + Present(completed) +} + +/// Struct implementing `ParseNodeList` for quote block content. +struct QuoteBlockList { + depth: usize, + first_line: bool, + last_block_was_paragraph: bool, + line_started_with_prefix: bool, +} + +impl QuoteBlockList { + fn new(depth: usize) -> Self { + Self { + depth, + first_line: true, + last_block_was_paragraph: false, + line_started_with_prefix: true, // First line implicitly has prefix + } + } +} + +impl ParseNodeList for QuoteBlockList { + type Kind = MarkdownSyntaxKind; + type Parser<'source> = MarkdownParser<'source>; + + const LIST_KIND: Self::Kind = MD_BLOCK_LIST; + + fn parse_element(&mut self, p: &mut Self::Parser<'_>) -> ParsedSyntax { + // Handle quote depth exceeded + if p.state().quote_depth_exceeded { + p.state_mut().quote_depth_exceeded = false; + return Absent; + } + + // Check/consume quote prefix for non-first lines + self.line_started_with_prefix = self.first_line; + if !self.first_line && !p.at(NEWLINE) && (p.at_line_start() || p.has_preceding_line_break()) + { + if has_quote_prefix(p, self.depth) { + consume_quote_prefix(p, self.depth); + self.line_started_with_prefix = true; + } else { + return Absent; + } + } + self.first_line = false; + + // Handle NEWLINE tokens + if p.at(NEWLINE) { + if !self.line_started_with_prefix && line_has_quote_prefix_at_current(p, self.depth) { + self.line_started_with_prefix = true; + } + if (p.at_blank_line() || has_empty_line_before(p) || self.last_block_was_paragraph) + && !self.line_started_with_prefix + { + return Absent; + } + if !self.line_started_with_prefix { + let has_next_prefix = p.lookahead(|p| { + p.bump(NEWLINE); + has_quote_prefix(p, self.depth) + }); + if !has_next_prefix { + return Absent; + } + } + let text_m = p.start(); + p.bump(NEWLINE); + return Present(text_m.complete(p, MD_NEWLINE)); + } + + // Handle indented code blocks inside quotes + if at_quote_indented_code_start(p) { + let parsed = parse_quote_indented_code_block(p, self.depth); + self.last_block_was_paragraph = false; + return parsed; + } + + // Parse regular block + // Treat content after '>' as column 0 for block parsing (fence detection). + let prev_virtual = p.state().virtual_line_start; + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + let parsed = parse_any_block_with_indent_code_policy(p, true); + p.state_mut().virtual_line_start = prev_virtual; + if let Present(ref marker) = parsed { + self.last_block_was_paragraph = is_paragraph_like(marker.kind(p)); + } else { + self.last_block_was_paragraph = false; + } + + parsed + } + + fn is_at_list_end(&self, p: &mut Self::Parser<'_>) -> bool { + p.at(T![EOF]) || p.state().quote_depth_exceeded + } + + fn recover( + &mut self, + _p: &mut Self::Parser<'_>, + parsed_element: ParsedSyntax, + ) -> RecoveryResult { + // No recovery needed - Absent means we're done + match parsed_element { + Present(marker) => RecoveryResult::Ok(marker), + Absent => RecoveryResult::Err(biome_parser::parse_recovery::RecoveryError::Eof), + } + } +} + +pub(crate) fn parse_quote_block_list(p: &mut MarkdownParser) { + let depth = p.state().block_quote_depth; + let mut list = QuoteBlockList::new(depth); + list.parse_list(p); +} + +pub(crate) fn line_has_quote_prefix_at_current(p: &MarkdownParser, depth: usize) -> bool { + if depth == 0 { + return false; + } + + let source = p.source().source_text(); + let start: usize = p.cur_range().start().into(); + let line_start = source[..start].rfind('\n').map_or(0, |idx| idx + 1); + + let mut idx = line_start; + let mut indent = 0usize; + while idx < start { + match source.as_bytes()[idx] { + b' ' => { + indent += 1; + idx += 1; + } + b'\t' => { + indent += TAB_STOP_SPACES; + idx += 1; + } + _ => break, + } + if indent > 3 { + return false; + } + } + + for _ in 0..depth { + if idx >= start || source.as_bytes()[idx] != b'>' { + return false; + } + idx += 1; + if idx < start { + let c = source.as_bytes()[idx]; + if c == b' ' || c == b'\t' { + idx += 1; + } + } + } + + true +} + +fn has_empty_line_before(p: &MarkdownParser) -> bool { + let start: usize = p.cur_range().start().into(); + if start == 0 { + return false; + } + let source = p.source().source_text().as_bytes(); + matches!(source.get(start - 1), Some(b'\n' | b'\r')) +} + +fn at_quote_indented_code_start(p: &MarkdownParser) -> bool { + let mut column = 0usize; + + for c in p.source_after_current().chars() { + match c { + ' ' => column += 1, + '\t' => column += TAB_STOP_SPACES - (column % TAB_STOP_SPACES), + _ => break, + } + } + + column >= INDENT_CODE_BLOCK_SPACES +} + +fn parse_quote_indented_code_block(p: &mut MarkdownParser, depth: usize) -> ParsedSyntax { + let m = p.start(); + let content = p.start(); + + loop { + if p.at(T![EOF]) { + break; + } + + if p.at(NEWLINE) { + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + + if p.at(T![EOF]) { + break; + } + + if !has_quote_prefix(p, depth) { + break; + } + consume_quote_prefix(p, depth); + + if p.at(NEWLINE) { + continue; + } + if !at_quote_indented_code_start(p) { + break; + } + continue; + } + + let text_m = p.start(); + p.bump_remap(MD_TEXTUAL_LITERAL); + text_m.complete(p, MD_TEXTUAL); + } + + content.complete(p, MD_INLINE_ITEM_LIST); + Present(m.complete(p, MD_INDENT_CODE_BLOCK)) +} + +fn skip_optional_marker_space(p: &mut MarkdownParser, preserve_tab: bool) -> bool { + if !p.at(MD_TEXTUAL_LITERAL) { + return false; + } + + let text = p.cur_text(); + if text == " " { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + return true; + } + if text == "\t" { + if !preserve_tab { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + return true; + } + false +} + +pub(crate) fn has_quote_prefix(p: &mut MarkdownParser, depth: usize) -> bool { + if depth == 0 { + return false; + } + + p.lookahead(|p| consume_quote_prefix_impl(p, depth, false)) +} + +pub(crate) fn consume_quote_prefix(p: &mut MarkdownParser, depth: usize) -> bool { + if depth == 0 || !has_quote_prefix(p, depth) { + return false; + } + + consume_quote_prefix_impl(p, depth, true) +} + +/// Check if a quote prefix starts at the current position. +pub(crate) fn consume_quote_prefix_without_virtual(p: &mut MarkdownParser, depth: usize) -> bool { + if depth == 0 || !has_quote_prefix(p, depth) { + return false; + } + + consume_quote_prefix_impl(p, depth, false) +} + +fn consume_quote_prefix_impl( + p: &mut MarkdownParser, + depth: usize, + set_virtual_line_start: bool, +) -> bool { + if !p.at_line_start() && !p.at_start_of_input() && !p.has_preceding_line_break() { + return false; + } + + for _ in 0..depth { + let prev_virtual = p.state().virtual_line_start; + p.state_mut().virtual_line_start = Some(p.cur_range().start()); + p.skip_line_indent(3); + p.state_mut().virtual_line_start = prev_virtual; + if p.at(T![>]) { + p.parse_as_skipped_trivia_tokens(|p| p.bump(T![>])); + } else if p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == ">" { + p.parse_as_skipped_trivia_tokens(|p| p.bump_remap(T![>])); + } else { + return false; + } + let has_indented_code = at_quote_indented_code_start(p); + skip_optional_marker_space(p, has_indented_code); + } + + if set_virtual_line_start { + p.set_virtual_line_start(); + } + + true +} diff --git a/crates/biome_markdown_parser/src/syntax/reference.rs b/crates/biome_markdown_parser/src/syntax/reference.rs new file mode 100644 index 000000000000..90de6cc6acf2 --- /dev/null +++ b/crates/biome_markdown_parser/src/syntax/reference.rs @@ -0,0 +1,85 @@ +use std::borrow::Cow; + +use biome_string_case::StrOnlyExtension; + +/// Normalize a reference label per CommonMark spec. +/// +/// Per CommonMark, label normalization involves: +/// 1. Collapsing consecutive whitespace into a single space +/// 2. Case-folding (case-insensitive matching) +/// +/// IMPORTANT: Backslash escapes are NOT stripped during normalization. +/// This means `[foo\!]` does NOT match `[foo!]` - the backslash is preserved. +/// This matches cmark's reference implementation behavior. +pub(crate) fn normalize_reference_label(text: &str) -> Cow<'_, str> { + if is_whitespace_normalized(text) { + // Apply Unicode case folding for case-insensitive matching. + return text.to_casefold_cow(); + } + + let mut out = String::new(); + let mut saw_whitespace = false; + + for c in text.chars() { + if c.is_whitespace() { + saw_whitespace = true; + } else { + if saw_whitespace && !out.is_empty() { + out.push(' '); + } + saw_whitespace = false; + out.push(c); + } + } + + let folded = out.as_str().to_casefold_cow(); + match folded { + Cow::Borrowed(_) => Cow::Owned(out), + Cow::Owned(folded) => Cow::Owned(folded), + } +} + +fn is_whitespace_normalized(text: &str) -> bool { + let mut saw_non_whitespace = false; + let mut last_was_space = false; + + for c in text.chars() { + if c.is_whitespace() { + if c != ' ' { + return false; + } + if !saw_non_whitespace || last_was_space { + return false; + } + last_was_space = true; + } else { + saw_non_whitespace = true; + last_was_space = false; + } + } + + !last_was_space +} + +#[cfg(test)] +mod tests { + use crate::syntax::reference::normalize_reference_label; + + #[test] + fn normalizes_whitespace_and_case() { + assert_eq!(normalize_reference_label(" Foo\tBar "), "foo bar"); + assert_eq!(normalize_reference_label("Foo Bar Baz"), "foo bar baz"); + } + + #[test] + fn preserves_backslash_escapes() { + assert_eq!(normalize_reference_label(r"foo\!"), r"foo\!"); + assert_eq!(normalize_reference_label(r"Foo\! Bar"), r"foo\! bar"); + } + + #[test] + fn avoids_allocation_for_normalized_labels() { + let normalized = normalize_reference_label("foo bar"); + assert!(matches!(normalized, std::borrow::Cow::Borrowed(_))); + } +} diff --git a/crates/biome_markdown_parser/src/syntax/thematic_break_block.rs b/crates/biome_markdown_parser/src/syntax/thematic_break_block.rs index b6789b9fde4f..59a6c63088b2 100644 --- a/crates/biome_markdown_parser/src/syntax/thematic_break_block.rs +++ b/crates/biome_markdown_parser/src/syntax/thematic_break_block.rs @@ -1,12 +1,148 @@ +//! Thematic break parsing for Markdown (CommonMark §4.1). +//! +//! A thematic break (horizontal rule) is a line consisting of three or more +//! matching `-`, `_`, or `*` characters, optionally with spaces between them. +//! +//! # Examples +//! +//! ```markdown +//! --- +//! *** +//! ___ +//! - - - +//! * * * +//! ``` + use crate::parser::MarkdownParser; use biome_markdown_syntax::MarkdownSyntaxKind::*; +use biome_markdown_syntax::T; use biome_parser::{ Parser, prelude::ParsedSyntax::{self, *}, }; +/// CommonMark requires 3 or more matching characters for thematic breaks. +const THEMATIC_BREAK_MIN_CHARS: usize = 3; + pub(crate) fn at_thematic_break_block(p: &mut MarkdownParser) -> bool { - p.at(MD_THEMATIC_BREAK_LITERAL) + p.lookahead(|p| { + if p.at_line_start() || p.at_start_of_input() { + if p.line_start_leading_indent() > 3 { + return false; + } + p.skip_line_indent(3); + return p.at(MD_THEMATIC_BREAK_LITERAL); + } + + // Special case: we may not be at line start if a list marker was consumed + // (e.g., `- * * *` where `-` was consumed as a list marker). + // Check if the remaining content is a thematic break pattern. + is_thematic_break_pattern(p) + }) +} + +/// Check if the remaining content forms a thematic break pattern. +/// +/// Per CommonMark §4.1, a thematic break is 3 or more matching characters +/// (`*`, `-`, or `_`) on a line by itself, optionally with spaces between them. +fn is_thematic_break_pattern(p: &mut MarkdownParser) -> bool { + // Skip leading whitespace + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + } + + // Check for lexer-produced thematic break token + if p.at(MD_THEMATIC_BREAK_LITERAL) { + return true; + } + + // If the entire line segment is a single textual literal, validate it directly. + if p.at(MD_TEXTUAL_LITERAL) + && p.cur_text() + .chars() + .all(|c| c == ' ' || c == '\t' || c == '*' || c == '-' || c == '_') + { + let mut break_char = None; + let mut break_count = 0usize; + + for c in p.cur_text().chars() { + if c == ' ' || c == '\t' { + continue; + } + if let Some(existing) = break_char { + if existing != c { + return false; + } + } else { + break_char = Some(c); + } + break_count += 1; + } + + let has_eol = p.lookahead(|p| { + p.bump(MD_TEXTUAL_LITERAL); + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + + return break_count >= THEMATIC_BREAK_MIN_CHARS && has_eol; + } + + // Get the break character from the first non-whitespace token + let break_char = if p.at(T![*]) { + '*' + } else if p.at(T![-]) { + '-' + } else if p.at(UNDERSCORE) { + '_' + } else if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + if text.len() == 1 { + match text.chars().next() { + Some('*') => '*', + Some('-') => '-', + Some('_') => '_', + _ => return false, + } + } else { + return false; + } + } else { + return false; + }; + + // Count matching characters + let mut count = 0usize; + + loop { + // Check for the break character + let is_break = match break_char { + '*' => p.at(T![*]) || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == "*"), + '-' => p.at(T![-]) || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == "-"), + '_' => p.at(UNDERSCORE) || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == "_"), + _ => false, + }; + + if is_break { + count += 1; + p.bump_any(); + continue; + } + + // Skip whitespace between break characters + if p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + continue; + } + + // End of line or other content + break; + } + + // Valid thematic break if 3+ characters followed by end of line + count >= THEMATIC_BREAK_MIN_CHARS && (p.at(NEWLINE) || p.at(T![EOF])) } pub(crate) fn parse_thematic_break_block(p: &mut MarkdownParser) -> ParsedSyntax { @@ -15,7 +151,100 @@ pub(crate) fn parse_thematic_break_block(p: &mut MarkdownParser) -> ParsedSyntax } let m = p.start(); - p.expect(MD_THEMATIC_BREAK_LITERAL); + p.skip_line_indent(3); + + // If the lexer produced MD_THEMATIC_BREAK_LITERAL, use it directly. + // Otherwise, parse the thematic break pattern from individual tokens and + // ensure we emit a literal token (required by the grammar). + if p.at(MD_THEMATIC_BREAK_LITERAL) { + p.expect(MD_THEMATIC_BREAK_LITERAL); + } else { + parse_thematic_break_tokens(p); + } Present(m.complete(p, MD_THEMATIC_BREAK_BLOCK)) } + +/// Parse a thematic break from individual tokens when the lexer didn't produce +/// MD_THEMATIC_BREAK_LITERAL (e.g., after a list marker was consumed). +fn parse_thematic_break_tokens(p: &mut MarkdownParser) { + // Skip leading whitespace + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + } + + // If the entire thematic break is in a single textual token, remap it. + if p.at(MD_TEXTUAL_LITERAL) + && p.cur_text() + .chars() + .all(|c| c == ' ' || c == '\t' || c == '*' || c == '-' || c == '_') + { + let has_eol = p.lookahead(|p| { + p.bump(MD_TEXTUAL_LITERAL); + while p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.bump(MD_TEXTUAL_LITERAL); + } + p.at(NEWLINE) || p.at(T![EOF]) + }); + if !has_eol { + return; + } + p.bump_remap(MD_THEMATIC_BREAK_LITERAL); + return; + } + + // Determine the break character for multi-token cases. + let break_char = if p.at(T![*]) { + Some('*') + } else if p.at(T![-]) { + Some('-') + } else if p.at(UNDERSCORE) { + Some('_') + } else if p.at(MD_TEXTUAL_LITERAL) { + let text = p.cur_text(); + match text.chars().next() { + Some('*') => Some('*'), + Some('-') => Some('-'), + Some('_') => Some('_'), + _ => None, + } + } else { + None + }; + + // Emit the required literal token by remapping the first break marker token. + if break_char.is_some() + && (p.at(T![*]) || p.at(T![-]) || p.at(UNDERSCORE) || p.at(MD_TEXTUAL_LITERAL)) + { + p.bump_remap(MD_THEMATIC_BREAK_LITERAL); + } + + // Parse all break characters and whitespace until end of line + loop { + if p.at(NEWLINE) || p.at(T![EOF]) { + break; + } + + // Check for the break character + let is_break = match break_char { + Some('*') => p.at(T![*]) || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == "*"), + Some('-') => p.at(T![-]) || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == "-"), + Some('_') => p.at(UNDERSCORE) || (p.at(MD_TEXTUAL_LITERAL) && p.cur_text() == "_"), + _ => false, + }; + + if is_break { + p.parse_as_skipped_trivia_tokens(|p| p.bump_any()); + continue; + } + + // Skip whitespace between break characters + if p.at(MD_TEXTUAL_LITERAL) && p.cur_text().chars().all(|c| c == ' ' || c == '\t') { + p.parse_as_skipped_trivia_tokens(|p| p.bump(MD_TEXTUAL_LITERAL)); + continue; + } + + // Other content - shouldn't happen if at_thematic_break_block returned true + break; + } +} diff --git a/crates/biome_markdown_parser/src/to_html.rs b/crates/biome_markdown_parser/src/to_html.rs new file mode 100644 index 000000000000..bce7fbcbf171 --- /dev/null +++ b/crates/biome_markdown_parser/src/to_html.rs @@ -0,0 +1,1956 @@ +//! HTML renderer for the Biome Markdown CST. +//! +//! This module provides a CST-to-HTML renderer for validating CommonMark spec +//! compliance. It handles the architectural mismatch between Biome's lossless +//! CST (which preserves tabs) and CommonMark's HTML output requirements. +//! +//! ## Purpose +//! +//! This is a **test harness module** designed for validating the markdown parser +//! against the CommonMark specification. It is not intended for production HTML +//! rendering. For production use cases, consider: +//! +//! - Using a dedicated markdown-to-HTML library +//! - Implementing a streaming/zero-copy renderer +//! +//! ## Key Design Decisions +//! +//! 1. **Tab Expansion**: The CST preserves raw `\t` characters for losslessness. +//! This renderer expands tabs only for structural indentation purposes (per +//! CommonMark §2.2), preserving literal tabs in code block content. +//! +//! 2. **Entity Decoding**: Uses the [`htmlize`] crate for WHATWG-compliant +//! HTML5 entity decoding, supporting all 2000+ named entities. +//! +//! 3. **Code Block Content**: The CST may include structural newlines; we skip +//! the leading newline after a fence marker. +//! +//! 4. **Line Endings**: Handles both LF (`\n`) and CRLF (`\r\n`) line endings +//! via the [`split_lines`] helper. +//! +//! ## Performance Notes +//! +//! This implementation prioritizes correctness over performance. Each rendering +//! pass may allocate multiple intermediate strings. For production rendering, +//! consider a single-buffer approach using `fmt::Write` or direct string building. +//! +//! ## Traversal +//! +//! The renderer uses `biome_rowan`'s `.preorder()` visitor to walk the CST and +//! emit HTML for enter/leave events instead of manual recursive descent. Nodes +//! that need post-processing (like paragraphs, headers, list items, and +//! reference links) are buffered until `Leave` so their final HTML wrapper can +//! be decided with full context. + +use biome_markdown_syntax::{ + AnyMdBlock, AnyMdCodeBlock, AnyMdInline, AnyMdLeafBlock, MarkdownLanguage, MdAutolink, + MdBlockList, MdBullet, MdBulletListItem, MdDocument, MdEntityReference, MdFencedCodeBlock, + MdHardLine, MdHeader, MdHtmlBlock, MdIndentCodeBlock, MdInlineCode, MdInlineEmphasis, + MdInlineHtml, MdInlineImage, MdInlineItalic, MdInlineItemList, MdInlineLink, MdLinkBlock, + MdLinkDestination, MdLinkLabel, MdLinkReferenceDefinition, MdLinkTitle, MdOrderedListItem, + MdParagraph, MdQuote, MdReferenceImage, MdReferenceLink, MdReferenceLinkLabel, MdSetextHeader, + MdSoftBreak, MdTextual, MdThematicBreakBlock, +}; +use biome_rowan::{AstNode, AstNodeList, Direction, SyntaxNode, TextRange, WalkEvent}; +use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode}; +use std::collections::HashMap; + +use crate::parser::{ListItemIndent, ListTightness, QuoteIndent}; +use crate::syntax::reference::normalize_reference_label; + +// ============================================================================ +// Line Handling Utilities +// ============================================================================ + +/// Split text into lines, handling both LF (\n) and CRLF (\r\n) line endings. +/// +/// Unlike `str::lines()`, this preserves the information needed for proper +/// line-by-line processing in code blocks. Returns an iterator over line content +/// (without the line ending). +fn split_lines(text: &str) -> impl Iterator { + text.split('\n') + .map(|line| line.strip_suffix('\r').unwrap_or(line)) +} + +fn map_lines(text: &str, mut f: F) -> String +where + F: FnMut(&str, &mut String), +{ + let mut result = String::new(); + for (i, line) in split_lines(text).enumerate() { + if i > 0 { + result.push('\n'); + } + f(line, &mut result); + } + result +} + +// ============================================================================ +// Tab Expansion +// ============================================================================ + +/// Expand tabs to spaces based on 4-space tab stops. +/// +/// CommonMark spec §2.2: "Tabs are expanded to spaces with a tab stop of 4 characters." +/// The column position determines how many spaces a tab expands to. +#[cfg(test)] +fn expand_tabs(text: &str) -> String { + let mut result = String::with_capacity(text.len()); + let mut column = 0; + + for c in text.chars() { + match c { + '\t' => { + // Expand to next 4-space tab stop + let spaces = 4 - (column % 4); + for _ in 0..spaces { + result.push(' '); + } + column += spaces; + } + '\n' => { + result.push('\n'); + column = 0; // Reset column at newline + } + _ => { + result.push(c); + column += 1; + } + } + } + + result +} + +/// Strip structural indentation from code block content while preserving literal tabs. +/// +/// CommonMark §2.2: Tabs are expanded for structural purposes (determining indentation) +/// but preserved literally in content. This function: +/// 1. Calculates column position treating tabs as 4-space stops +/// 2. Strips the first `strip_cols` columns of indentation +/// 3. Preserves literal tabs in the remaining content +fn strip_indent_preserve_tabs(text: &str, strip_cols: usize) -> String { + strip_indent_preserve_tabs_with_offset(text, strip_cols, 0) +} + +fn strip_indent_preserve_tabs_with_offset( + text: &str, + strip_cols: usize, + first_line_column: usize, +) -> String { + let mut first_line = true; + map_lines(text, |line, result| { + let mut col = if first_line { first_line_column } else { 0 }; + first_line = false; + let mut char_idx = 0; + + // Find where to start copying (after stripping strip_cols columns) + for (idx, c) in line.char_indices() { + if col >= strip_cols { + char_idx = idx; + break; + } + match c { + '\t' => { + let next_col = col + (4 - (col % 4)); + if next_col > strip_cols { + // Tab crosses the strip boundary - add remaining spaces + let spaces = next_col - strip_cols; + for _ in 0..spaces { + result.push(' '); + } + char_idx = idx + 1; + break; + } + col = next_col; + } + ' ' => col += 1, + _ => { + // Non-whitespace before strip_cols - keep from here + char_idx = idx; + break; + } + } + char_idx = idx + c.len_utf8(); + } + + // Append the rest of the line (preserving literal tabs) + if char_idx < line.len() { + result.push_str(&line[char_idx..]); + } + }) +} + +fn strip_quote_prefixes(text: &str, quote_indent: usize) -> String { + map_lines(text, |line, result| { + let mut remaining = line; + loop { + let mut idx = 0usize; + let mut col = 0usize; + + // Scan up to 3 spaces/tabs for the optional quote indent. + while idx < remaining.len() { + let c = remaining.as_bytes()[idx]; + match c { + b' ' => { + col += 1; + idx += 1; + } + b'\t' => { + col += 4 - (col % 4); + idx += 1; + } + _ => break, + } + + if col > 3 { + idx = 0; + break; + } + } + + if idx > 0 || col == 0 { + if idx < remaining.len() { + if remaining[idx..].starts_with(">") { + idx += 4; + if idx < remaining.len() + && matches!(remaining.as_bytes()[idx], b' ' | b'\t') + { + idx += 1; + } + remaining = &remaining[idx..]; + continue; + } + if remaining.as_bytes()[idx] == b'>' { + idx += 1; + if idx < remaining.len() + && matches!(remaining.as_bytes()[idx], b' ' | b'\t') + { + idx += 1; + } + remaining = &remaining[idx..]; + continue; + } + } + + if quote_indent > 1 && idx > 0 && remaining.len() > 1 { + remaining = &remaining[1..]; + continue; + } + } + + break; + } + + result.push_str(remaining); + }) +} + +// ============================================================================ +// Context and Main Entry Point +// ============================================================================ + +/// Context for HTML rendering, containing link reference definitions +/// and list tightness information. +pub struct HtmlRenderContext { + /// Link reference definitions: label -> (url, title) + link_definitions: HashMap)>, + /// List tightness by text range + list_tightness: HashMap, + /// List item indentation details by text range + list_item_indents: HashMap, + /// Quote marker indents by text range + quote_indents: HashMap, +} + +impl HtmlRenderContext { + /// Create a new rendering context from parsed document data. + pub fn new( + document: &MdDocument, + list_tightness: &[ListTightness], + list_item_indents: &[ListItemIndent], + quote_indents: &[QuoteIndent], + ) -> Self { + let link_definitions = collect_link_definitions(document); + let list_tightness_map = list_tightness + .iter() + .map(|lt| (lt.range, lt.is_tight)) + .collect(); + let list_item_indent_map = list_item_indents + .iter() + .map(|item| (item.range, item.clone())) + .collect(); + let quote_indent_map = quote_indents + .iter() + .map(|item| (item.range, item.clone())) + .collect(); + + Self { + link_definitions, + list_tightness: list_tightness_map, + list_item_indents: list_item_indent_map, + quote_indents: quote_indent_map, + } + } + + /// Look up a link reference definition by normalized label. + pub fn get_link_definition(&self, label: &str) -> Option<&(String, Option)> { + let normalized = normalize_reference_label(label); + self.link_definitions.get(normalized.as_ref()) + } + + /// Check if a list at the given range is tight. + pub fn is_list_tight(&self, range: TextRange) -> bool { + self.list_tightness.get(&range).copied().unwrap_or(false) + } + + pub fn list_item_indent(&self, range: TextRange) -> Option<&ListItemIndent> { + self.list_item_indents.get(&range) + } + + pub fn quote_indent(&self, range: TextRange) -> usize { + self.quote_indents.get(&range).map_or(0, |item| item.indent) + } +} + +/// Render a markdown document to HTML. +pub fn document_to_html( + document: &MdDocument, + list_tightness: &[ListTightness], + list_item_indents: &[ListItemIndent], + quote_indents: &[QuoteIndent], +) -> String { + let ctx = HtmlRenderContext::new(document, list_tightness, list_item_indents, quote_indents); + HtmlRenderer::new(&ctx).render(document.syntax()) +} + +// ============================================================================ +// Link Reference Collection +// ============================================================================ + +/// Collect link reference definitions from the document. +fn collect_link_definitions(document: &MdDocument) -> HashMap)> { + let mut definitions = HashMap::new(); + + for node in document.syntax().descendants() { + if let Some(def) = MdLinkReferenceDefinition::cast(node) + && let (Ok(label), Ok(dest)) = (def.label(), def.destination()) + { + let label_text = collect_inline_text(&label.content()); + let normalized = normalize_reference_label(&label_text); + if normalized.is_empty() { + continue; + } + + // Only keep first definition (per CommonMark spec) + if definitions.contains_key(normalized.as_ref()) { + continue; + } + + let url = collect_inline_text(&dest.content()); + let url = process_link_destination(&url); + + let title = def.title().map(|t| { + let text = collect_inline_text(&t.content()); + process_link_title(&text) + }); + + definitions.insert(normalized.into_owned(), (url, title)); + } + } + + definitions +} + +// ============================================================================ +// Visitor Rendering +// ============================================================================ + +struct HtmlRenderer<'a> { + ctx: &'a HtmlRenderContext, + buffers: Vec, + list_stack: Vec, + list_item_stack: Vec, + quote_indent_stack: Vec, + quote_indent: usize, + depth: usize, + opaque_depth: Option, + skip_children_depth: Option, + suppressed_inline_nodes: Vec>>, +} + +struct Buffer { + kind: BufferKind, + content: String, +} + +enum BufferKind { + Root, + Paragraph(ParagraphState), + Header(HeaderState), + SetextHeader(HeaderState), + ReferenceLink(ReferenceLinkState), + ListItem, +} + +struct ParagraphState { + in_tight_list: bool, + quote_indent: usize, + suppress_wrapping: bool, +} + +struct HeaderState { + level: usize, +} + +struct ReferenceLinkState { + label_display: Option, + url: Option, + title: Option, +} + +struct ListState { + is_tight: bool, +} + +struct ListItemState { + is_tight: bool, + leading_newline: bool, + trim_trailing_newline: bool, + is_empty: bool, + block_indents: HashMap, +} + +#[derive(Clone, Copy, Default)] +struct BlockIndent { + indent: usize, + first_line_column: usize, +} + +impl<'a> HtmlRenderer<'a> { + fn new(ctx: &'a HtmlRenderContext) -> Self { + Self { + ctx, + buffers: vec![Buffer { + kind: BufferKind::Root, + content: String::new(), + }], + list_stack: Vec::new(), + list_item_stack: Vec::new(), + quote_indent_stack: Vec::new(), + quote_indent: 0, + depth: 0, + opaque_depth: None, + skip_children_depth: None, + suppressed_inline_nodes: Vec::new(), + } + } + + fn render(mut self, root: &SyntaxNode) -> String { + for event in root.preorder() { + match event { + WalkEvent::Enter(node) => { + if self.opaque_depth.is_some() { + self.depth += 1; + continue; + } + if let Some(skip) = self.skip_children_depth + && self.depth > skip + { + self.depth += 1; + continue; + } + + self.enter(node); + self.depth += 1; + } + WalkEvent::Leave(node) => { + self.depth = self.depth.saturating_sub(1); + if let Some(opaque_depth) = self.opaque_depth { + if self.depth == opaque_depth { + self.opaque_depth = None; + } + continue; + } + + if let Some(skip) = self.skip_children_depth { + if self.depth > skip { + continue; + } + if self.depth == skip { + self.leave(node); + self.skip_children_depth = None; + continue; + } + } + + self.leave(node); + } + } + } + + self.buffers + .pop() + .map(|buffer| buffer.content) + .unwrap_or_default() + } + + fn enter(&mut self, node: SyntaxNode) { + if MdInlineItemList::cast(node.clone()).is_some() + && self + .suppressed_inline_nodes + .iter() + .flatten() + .any(|suppressed| *suppressed == node) + { + self.opaque_depth = Some(self.depth); + return; + } + + if MdParagraph::cast(node.clone()).is_some() { + let suppress_wrapping = node.parent().and_then(MdHeader::cast).is_some() + || node.parent().and_then(MdSetextHeader::cast).is_some(); + let is_direct_list_item = node + .parent() + .and_then(MdBlockList::cast) + .and_then(|list| list.syntax().parent()) + .and_then(MdBullet::cast) + .is_some(); + let in_tight_list = is_direct_list_item + && self + .list_item_stack + .last() + .is_some_and(|state| state.is_tight); + let state = ParagraphState { + in_tight_list, + quote_indent: self.quote_indent, + suppress_wrapping, + }; + self.push_buffer(BufferKind::Paragraph(state)); + return; + } + + if let Some(header) = MdHeader::cast(node.clone()) { + let level = header_level(&header); + self.push_buffer(BufferKind::Header(HeaderState { level })); + return; + } + + if let Some(header) = MdSetextHeader::cast(node.clone()) { + let level = setext_header_level(&header); + self.push_buffer(BufferKind::SetextHeader(HeaderState { level })); + return; + } + + if let Some(quote) = MdQuote::cast(node.clone()) { + self.push_str("
\n"); + let marker_indent = self.ctx.quote_indent(quote.syntax().text_trimmed_range()); + self.quote_indent += marker_indent; + self.quote_indent_stack.push(marker_indent); + return; + } + + if let Some(list) = MdBulletListItem::cast(node.clone()) { + let is_tight = self.ctx.is_list_tight(list.syntax().text_trimmed_range()); + self.list_stack.push(ListState { is_tight }); + self.push_str("
\n"); + if let Some(indent) = self.quote_indent_stack.pop() { + self.quote_indent = self.quote_indent.saturating_sub(indent); + } + return; + } + + if MdBulletListItem::cast(node.clone()).is_some() { + self.push_str("\n"); + self.list_stack.pop(); + return; + } + + if MdOrderedListItem::cast(node.clone()).is_some() { + self.push_str("\n"); + self.list_stack.pop(); + return; + } + + if MdBullet::cast(node.clone()).is_some() { + let buffer = self.pop_buffer(); + let state = self.list_item_stack.pop(); + if let (BufferKind::ListItem, Some(state)) = (buffer.kind, state) { + if state.is_empty { + self.push_str("
  • \n"); + return; + } + + self.push_str("
  • "); + if state.leading_newline { + self.push_str("\n"); + } + + let mut content = buffer.content; + if state.trim_trailing_newline && content.ends_with('\n') { + content.pop(); + } + self.push_str(&content); + self.push_str("
  • \n"); + } + return; + } + + if MdInlineEmphasis::cast(node.clone()).is_some() { + self.push_str("
    "); + return; + } + + if MdInlineItalic::cast(node.clone()).is_some() { + self.push_str(""); + return; + } + + if MdInlineLink::cast(node.clone()).is_some() { + self.suppressed_inline_nodes.pop(); + self.push_str(""); + return; + } + + if MdReferenceLink::cast(node).is_some() { + let buffer = self.pop_buffer(); + if let BufferKind::ReferenceLink(state) = buffer.kind { + if let Some(url) = state.url { + self.push_str(""); + self.push_str(&buffer.content); + self.push_str(""); + } else { + self.push_str("["); + self.push_str(&buffer.content); + self.push_str("]"); + if let Some(label) = state.label_display { + self.push_str("["); + self.push_str(&escape_html(&label)); + self.push_str("]"); + } + } + } + } + } + + fn push_buffer(&mut self, kind: BufferKind) { + self.buffers.push(Buffer { + kind, + content: String::new(), + }); + } + + fn pop_buffer(&mut self) -> Buffer { + self.buffers.pop().unwrap_or(Buffer { + kind: BufferKind::Root, + content: String::new(), + }) + } + + fn out_mut(&mut self) -> &mut String { + &mut self.buffers.last_mut().expect("missing buffer").content + } + + fn push_str(&mut self, value: &str) { + self.out_mut().push_str(value); + } + + fn block_indent(&self, range: TextRange) -> BlockIndent { + self.list_item_stack + .last() + .and_then(|state| state.block_indents.get(&range).copied()) + .unwrap_or_default() + } +} + +fn is_last_inline_item(node: &SyntaxNode) -> bool { + let Some(parent) = node.parent() else { + return false; + }; + let Some(list) = biome_markdown_syntax::MdInlineItemList::cast(parent) else { + return false; + }; + list.iter().last().is_some_and(|item| item.syntax() == node) +} + +/// Strip leading whitespace from paragraph continuation lines. +/// +/// Per CommonMark §4.8, paragraph continuation lines can have any amount of +/// initial whitespace, and that whitespace is stripped in the output. +/// The first line keeps its content unchanged; subsequent lines have all +/// leading spaces and tabs stripped. +fn strip_paragraph_indent(content: &str) -> String { + let mut first_line = true; + map_lines(content, |line, out| { + if first_line { + // First line: keep as-is + first_line = false; + out.push_str(line); + } else { + // Continuation lines: strip ALL leading whitespace + out.push_str(line.trim_start()); + } + }) +} + +/// Render an ATX header (# style). +fn header_level(header: &MdHeader) -> usize { + // Count total hash characters in the before list. + // The lexer emits all consecutive `#` chars as a single HASH token, + // so we sum the text lengths of all hash tokens. + // Use text_trimmed() to exclude any leading trivia (skipped indentation spaces). + header + .before() + .iter() + .filter_map(|h| h.hash_token().ok()) + .map(|tok| tok.text_trimmed().len()) + .sum::() + .clamp(1, 6) +} + +/// Render a setext header (underline style). +fn setext_header_level(header: &MdSetextHeader) -> usize { + if let Ok(underline) = header.underline_token() { + let text = underline.text(); + if text.trim_start().starts_with('=') { + 1 + } else { + 2 + } + } else { + 1 + } +} + +// ============================================================================ +// Code Block Rendering +// ============================================================================ + +/// Render a fenced code block. +/// +/// Handles the architectural issue where the CST content may include the +/// newline immediately after the opening fence. We skip this leading newline. +/// Also strips the fence's indentation from each content line per CommonMark. +fn render_fenced_code_block( + code: &MdFencedCodeBlock, + out: &mut String, + list_indent: usize, + quote_indent: usize, +) { + out.push_str("
     indent += 1,
    +                    '\t' => indent += 4 - (indent % 4),
    +                    '\n' | '\r' => indent = 0, // Reset at newlines
    +                    _ => {}
    +                }
    +            }
    +        }
    +        indent
    +    });
    +    let container_indent = list_indent + quote_indent;
    +    let fence_indent = fence_leading_indent.saturating_sub(container_indent).min(3);
    +    let content_indent = container_indent + fence_indent;
    +
    +    // Get info string (language) - process escapes
    +    let info_string: String = code
    +        .code_list()
    +        .iter()
    +        .filter_map(|item| {
    +            item.syntax()
    +                .descendants_with_tokens(Direction::Next)
    +                .filter_map(|el| el.into_token())
    +                .map(|tok| tok.text().to_string())
    +                .next()
    +        })
    +        .collect::();
    +    let info_string = info_string.trim();
    +
    +    // Extract just the language part (before first space) and process escapes
    +    let language = info_string.split_whitespace().next().unwrap_or("");
    +    let language = process_escapes(language);
    +    let language = htmlize::unescape(&language);
    +
    +    if !language.is_empty() {
    +        out.push_str(" class=\"language-");
    +        out.push_str(&escape_html_attribute(language.as_ref()));
    +        out.push('"');
    +    }
    +
    +    out.push('>');
    +
    +    // Get raw content and handle leading newline
    +    let mut content = collect_raw_inline_text(&code.content());
    +
    +    // Skip leading newline if present (it's part of the fence structure, not content)
    +    if content.starts_with('\n') {
    +        content = content[1..].to_string();
    +    }
    +
    +    // Strip container + fence indentation from content lines
    +    if content_indent > 0 {
    +        content = strip_indent_preserve_tabs(&content, content_indent);
    +    }
    +    if quote_indent > 0 {
    +        content = strip_quote_prefixes(&content, quote_indent);
    +    }
    +
    +    // Escape HTML but preserve the content structure
    +    out.push_str(&escape_html(&content));
    +
    +    out.push_str("
    \n"); +} + +/// Render an indented code block. +/// +/// Indented code blocks require stripping 4 spaces of indentation after +/// tab expansion. +fn render_indented_code_block( + code: &MdIndentCodeBlock, + out: &mut String, + list_indent: usize, + quote_indent: usize, +) { + out.push_str("
    ");
    +
    +    let mut content = collect_raw_inline_text(&code.content());
    +    // Drop a leading newline from list marker-only lines.
    +    if content.starts_with('\n') {
    +        content = content[1..].to_string();
    +    }
    +    // Strip 4 columns of structural indent but preserve literal tabs in content
    +    let content = strip_indent_preserve_tabs(&content, 4 + list_indent + quote_indent);
    +    out.push_str(&escape_html(&content));
    +
    +    out.push_str("
    \n"); +} + +fn render_indented_code_block_in_list( + code: &MdIndentCodeBlock, + out: &mut String, + list_indent: usize, + quote_indent: usize, + first_line_column: usize, +) { + out.push_str("
    ");
    +
    +    let mut content = collect_raw_inline_text(&code.content());
    +    if content.starts_with('\n') {
    +        content = content[1..].to_string();
    +    }
    +
    +    let content = strip_indent_preserve_tabs_with_offset(
    +        &content,
    +        4 + list_indent + quote_indent,
    +        first_line_column,
    +    );
    +    out.push_str(&escape_html(&content));
    +
    +    out.push_str("
    \n"); +} + +/// Render an HTML block. +fn render_html_block( + html: &MdHtmlBlock, + out: &mut String, + list_indent: usize, + quote_indent: usize, +) { + let mut content = collect_raw_inline_text(&html.content()); + if list_indent > 0 { + content = strip_indent_preserve_tabs(&content, list_indent); + } + if quote_indent > 0 { + content = strip_quote_prefixes(&content, quote_indent); + } + out.push_str(&content); + if !content.ends_with('\n') { + out.push('\n'); + } +} + +/// Render textual content. +fn render_textual(text: &MdTextual, out: &mut String) { + if let Ok(token) = text.value_token() { + // Use text_trimmed() to exclude skipped trivia (e.g., indentation stripped during parsing) + let raw = token.text_trimmed(); + // Process backslash escapes and escape HTML + let processed = process_escapes(raw); + out.push_str(&escape_html(&processed)); + } +} + +/// Render inline code. +fn render_inline_code(code: &MdInlineCode, out: &mut String) { + out.push_str(""); + + let content = collect_raw_inline_text(&code.content()); + // Code spans: normalize line endings to spaces + let content = content.replace('\n', " "); + // Code spans: strip one leading/trailing space if content has both + // and the content isn't all spaces + let content = if content.starts_with(' ') + && content.ends_with(' ') + && content.len() > 2 + && content.chars().any(|c| c != ' ') + { + content[1..content.len() - 1].to_string() + } else { + content + }; + + out.push_str(&escape_html(&content)); + out.push_str(""); +} + +fn resolve_reference_label( + label_node: Option, + fallback: String, + label_text: F, +) -> (String, Option) +where + F: FnOnce(&L) -> String, +{ + if let Some(node) = label_node { + let text = label_text(&node); + if text.trim().is_empty() { + (fallback, None) + } else { + (text.clone(), Some(text)) + } + } else { + (fallback, None) + } +} + +/// Render an autolink. +fn render_autolink(autolink: &MdAutolink, out: &mut String) { + let content = collect_raw_inline_text(&autolink.value()); + + // Check if it's an email autolink + let is_email = content.contains('@') && !content.contains(':'); + + // Autolinks must NOT process backslash escapes or entity decoding. + // Only percent-encode for URL safety. + let href = if is_email { + format!("mailto:{}", content) + } else { + percent_encode_uri(&content) + }; + + out.push_str(""); + out.push_str(&escape_html(&content)); + out.push_str(""); +} + +/// Render inline HTML. +fn render_inline_html(html: &MdInlineHtml, out: &mut String) { + let content = collect_raw_inline_text(&html.value()); + out.push_str(&content); +} + +/// Render an entity reference. +fn render_entity_reference(entity: &MdEntityReference, out: &mut String) { + if let Ok(token) = entity.value_token() { + let text = token.text(); + // Decode known entities or pass through + if let Some(decoded) = decode_entity(text) { + out.push_str(&escape_html(&decoded)); + } else { + // Unknown entity - pass through as-is (escaped) + out.push_str(&escape_html(text)); + } + } +} + +/// Render a link title attribute. +fn render_link_title(title: &MdLinkTitle, out: &mut String) { + let text = collect_inline_text(&title.content()); + let text = process_link_title(&text); + + out.push_str(" title=\""); + out.push_str(&escape_html_attribute(&text)); + out.push('"'); +} + +// ============================================================================ +// Text Collection Helpers +// ============================================================================ + +/// Collect all text from an inline list (for processing). +fn collect_inline_text(list: &biome_markdown_syntax::MdInlineItemList) -> String { + let mut text = String::new(); + for token in list + .syntax() + .descendants_with_tokens(Direction::Next) + .filter_map(|element| element.into_token()) + { + text.push_str(token.text()); + } + text +} + +/// Collect raw inline text without processing escapes. +fn collect_raw_inline_text(list: &biome_markdown_syntax::MdInlineItemList) -> String { + let mut text = String::new(); + for item in list.iter() { + collect_raw_inline_item(&item, &mut text); + } + text +} + +/// Collect raw text from a single inline item. +fn collect_raw_inline_item(item: &AnyMdInline, out: &mut String) { + match item { + AnyMdInline::MdTextual(text) => { + if let Ok(token) = text.value_token() { + out.push_str(token.text()); + } + } + AnyMdInline::MdSoftBreak(_) => { + out.push('\n'); + } + AnyMdInline::MdHardLine(_) => { + out.push('\n'); + } + _ => { + // For other inline elements, collect their tokens + for token in item + .syntax() + .descendants_with_tokens(Direction::Next) + .filter_map(|element| element.into_token()) + { + out.push_str(token.text()); + } + } + } +} + +// ============================================================================ +// Text Processing +// ============================================================================ + +/// Process backslash escapes in text. +fn process_escapes(text: &str) -> String { + let mut result = String::new(); + let mut chars = text.chars().peekable(); + + while let Some(c) = chars.next() { + if c == '\\' + && let Some(&next) = chars.peek() + && is_escapable(next) + { + result.push(next); + chars.next(); + continue; + } + result.push(c); + } + + result +} + +/// Check if a character can be escaped in markdown. +fn is_escapable(c: char) -> bool { + matches!( + c, + '!' | '"' + | '#' + | '$' + | '%' + | '&' + | '\'' + | '(' + | ')' + | '*' + | '+' + | ',' + | '-' + | '.' + | '/' + | ':' + | ';' + | '<' + | '=' + | '>' + | '?' + | '@' + | '[' + | '\\' + | ']' + | '^' + | '_' + | '`' + | '{' + | '|' + | '}' + | '~' + ) +} + +/// Process a link destination (remove angle brackets, decode escapes). +fn process_link_destination(dest: &str) -> String { + let dest = dest.trim(); + + // Remove angle brackets if present + let dest = if dest.starts_with('<') && dest.ends_with('>') { + &dest[1..dest.len() - 1] + } else { + dest + }; + + // Process escapes + let dest = process_escapes(dest); + let decoded = htmlize::unescape(&dest).into_owned(); + percent_encode_uri(&decoded) +} + +/// Process a link title (remove quotes, decode escapes). +fn process_link_title(title: &str) -> String { + let title = title.trim(); + + // Remove surrounding quotes + let title = if (title.starts_with('"') && title.ends_with('"')) + || (title.starts_with('\'') && title.ends_with('\'')) + || (title.starts_with('(') && title.ends_with(')')) + { + &title[1..title.len() - 1] + } else { + title + }; + + // Process escapes + let title = process_escapes(title); + htmlize::unescape(&title).into_owned() +} + +fn percent_encode_uri(value: &str) -> String { + let mut result = String::new(); + let mut last = 0; + + for (i, c) in value.char_indices() { + if c == '%' { + let bytes = value.as_bytes(); + if i + 2 < bytes.len() + && bytes[i + 1].is_ascii_hexdigit() + && bytes[i + 2].is_ascii_hexdigit() + { + if last < i { + result.push_str( + &utf8_percent_encode(&value[last..i], URI_ENCODE_SET).to_string(), + ); + } + result.push_str(&value[i..i + 3]); + last = i + 3; + } + } + } + + if last < value.len() { + result.push_str(&utf8_percent_encode(&value[last..], URI_ENCODE_SET).to_string()); + } + + result +} + +const URI_ENCODE_SET: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'%') + .add(b'<') + .add(b'>') + .add(b'\\') + .add(b'[') + .add(b']') + .add(b'^') + .add(b'`') + .add(b'{') + .add(b'|') + .add(b'}'); + +// ============================================================================ +// HTML Escaping +// ============================================================================ + +/// Escape HTML special characters. +fn escape_html(text: &str) -> String { + let mut result = String::with_capacity(text.len()); + for c in text.chars() { + match c { + '&' => result.push_str("&"), + '<' => result.push_str("<"), + '>' => result.push_str(">"), + '"' => result.push_str("""), + _ => result.push(c), + } + } + result +} + +/// Escape HTML attribute values. +fn escape_html_attribute(text: &str) -> String { + escape_html(text) +} + +/// Extract plain text for image alt attribute. +/// Per CommonMark, the alt text is the content with inline formatting stripped +/// but text from nested links/images preserved (recursively extracting their text). +fn extract_alt_text( + list: &biome_markdown_syntax::MdInlineItemList, + ctx: &HtmlRenderContext, +) -> String { + let mut result = String::new(); + for item in list.iter() { + extract_alt_text_inline(&item, ctx, &mut result); + } + result +} + +fn extract_alt_text_inline(inline: &AnyMdInline, ctx: &HtmlRenderContext, out: &mut String) { + // NOTE: This function collects raw text WITHOUT HTML escaping. + // The final escaping happens in escape_html_attribute when writing the alt attribute. + match inline { + AnyMdInline::MdTextual(text) => { + // Process backslash escapes but don't escape HTML + if let Ok(token) = text.value_token() { + let raw = token.text_trimmed(); + let processed = process_escapes(raw); + out.push_str(&processed); + } + } + AnyMdInline::MdInlineEmphasis(em) => { + out.push_str(&extract_alt_text(&em.content(), ctx)); + } + AnyMdInline::MdInlineItalic(italic) => { + out.push_str(&extract_alt_text(&italic.content(), ctx)); + } + AnyMdInline::MdInlineCode(code) => { + // Plain text only — no tags for alt attribute + let content = collect_raw_inline_text(&code.content()); + let content = content.replace('\n', " "); + let content = if content.starts_with(' ') + && content.ends_with(' ') + && content.len() > 2 + && content.chars().any(|c| c != ' ') + { + content[1..content.len() - 1].to_string() + } else { + content + }; + out.push_str(&content); + } + AnyMdInline::MdInlineLink(link) => { + // Extract text content from link text + out.push_str(&extract_alt_text(&link.text(), ctx)); + } + AnyMdInline::MdInlineImage(img) => { + // Recursively extract alt text from nested image + out.push_str(&extract_alt_text(&img.alt(), ctx)); + } + AnyMdInline::MdReferenceLink(link) => { + out.push_str(&extract_alt_text(&link.text(), ctx)); + } + AnyMdInline::MdReferenceImage(img) => { + out.push_str(&extract_alt_text(&img.alt(), ctx)); + } + AnyMdInline::MdAutolink(autolink) => { + let content = collect_raw_inline_text(&autolink.value()); + out.push_str(&content); + } + AnyMdInline::MdHardLine(_) | AnyMdInline::MdSoftBreak(_) => { + out.push(' '); + } + AnyMdInline::MdEntityReference(entity) => { + // Decode entity but don't escape HTML + if let Ok(token) = entity.value_token() { + let text = token.text(); + if let Some(decoded) = decode_entity(text) { + out.push_str(&decoded); + } else { + // Unknown entity - pass through as-is + out.push_str(text); + } + } + } + AnyMdInline::MdInlineHtml(_) | AnyMdInline::MdHtmlBlock(_) => { + // HTML tags are stripped in alt text + } + } +} + +// ============================================================================ +// Entity Decoding +// ============================================================================ + +/// Decode HTML entities using the WHATWG standard. +/// +/// Handles numeric character references ({ and {) and all standard +/// HTML5 named entities via the `htmlize` crate. +fn decode_entity(entity: &str) -> Option { + use std::borrow::Cow; + + // Use htmlize for WHATWG-compliant entity decoding + let decoded = htmlize::unescape(entity); + + match decoded { + // If the entity was decoded (string changed), return the decoded value + Cow::Owned(s) if s != entity => Some(s), + // If unchanged and it looks like an entity, it's invalid + Cow::Borrowed(_) if entity.starts_with('&') && entity.ends_with(';') => None, + Cow::Owned(s) if entity.starts_with('&') && entity.ends_with(';') && s == entity => None, + // Otherwise return as-is + Cow::Owned(s) => Some(s), + Cow::Borrowed(s) => Some(s.to_string()), + } +} +fn is_paragraph_block(block: &AnyMdBlock) -> bool { + matches!( + block, + AnyMdBlock::AnyMdLeafBlock(AnyMdLeafBlock::MdParagraph(_)) + ) +} + +/// Check if a block is a newline (produces no output). +fn is_newline_block(block: &AnyMdBlock) -> bool { + matches!( + block, + AnyMdBlock::AnyMdLeafBlock(AnyMdLeafBlock::MdNewline(_)) + ) +} + +/// Check if blocks are effectively empty (empty or only newlines). +fn is_empty_content(blocks: &[AnyMdBlock]) -> bool { + blocks.is_empty() || blocks.iter().all(is_newline_block) +} + +const INDENT_CODE_BLOCK_SPACES: usize = 4; + +fn list_item_required_indent(entry: &ListItemIndent) -> usize { + if entry.spaces_after_marker > INDENT_CODE_BLOCK_SPACES { + entry.marker_indent + entry.marker_width + 1 + } else { + entry.marker_indent + entry.marker_width + entry.spaces_after_marker.max(1) + } +} + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::parse_markdown; + + #[test] + fn test_tab_expansion() { + assert_eq!(expand_tabs("\tfoo"), " foo"); + assert_eq!(expand_tabs("a\tb"), "a b"); + assert_eq!(expand_tabs("ab\tc"), "ab c"); + assert_eq!(expand_tabs("abc\td"), "abc d"); + assert_eq!(expand_tabs("abcd\te"), "abcd e"); + assert_eq!(expand_tabs("\t\tfoo"), " foo"); + } + + #[test] + fn test_tab_expansion_multiline() { + assert_eq!(expand_tabs("a\tb\nc\td"), "a b\nc d"); + } + + #[test] + fn test_simple_paragraph() { + let parsed = parse_markdown("Hello, world!\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!(html, "

    Hello, world!

    \n"); + } + + #[test] + fn test_atx_header() { + let parsed = parse_markdown("# Hello\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!(html, "

    Hello

    \n"); + } + + #[test] + fn test_emphasis() { + let parsed = parse_markdown("*italic* and **bold**\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!(html, "

    italic and bold

    \n"); + } + + #[test] + fn test_emphasis_complex_cases() { + // Test: Nested + let parsed = parse_markdown("**bold *and italic* text**\n"); + assert_eq!( + parsed.syntax().kind(), + biome_markdown_syntax::MarkdownSyntaxKind::MD_DOCUMENT, + "Nested failed: {}", + parsed.syntax() + ); + + // Test: Rule of 3 + let parsed = parse_markdown("***bold italic***\n"); + assert_eq!( + parsed.syntax().kind(), + biome_markdown_syntax::MarkdownSyntaxKind::MD_DOCUMENT, + "Rule of 3 failed: {}", + parsed.syntax() + ); + + // Test: Multiple runs + let parsed = parse_markdown("*a **b** c*\n"); + assert_eq!( + parsed.syntax().kind(), + biome_markdown_syntax::MarkdownSyntaxKind::MD_DOCUMENT, + "Multiple runs failed: {}", + parsed.syntax() + ); + + // Test: Overlapping + let parsed = parse_markdown("*foo**bar**baz*\n"); + assert_eq!( + parsed.syntax().kind(), + biome_markdown_syntax::MarkdownSyntaxKind::MD_DOCUMENT, + "Overlapping failed: {}", + parsed.syntax() + ); + + // Test: Unbalanced emphasis (CommonMark example 442) + // **foo* should produce *foo + let parsed = parse_markdown("**foo*\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!( + html, + "

    *foo

    \n", + "Unbalanced: {}", + parsed.syntax() + ); + } + + #[test] + fn test_example_431() { + // Test: Example 431 - nested emphasis with triple star closer + // **foo *bar*** should produce foo bar + let parsed = parse_markdown("**foo *bar***\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!( + html, + "

    foo bar

    \n", + "Example 431: {}", + parsed.syntax() + ); + } + + #[test] + fn test_escape_html() { + assert_eq!(escape_html("a & b < c > d"), "a & b < c > d"); + } + + #[test] + fn test_decode_entity() { + assert_eq!(decode_entity("&"), Some("&".to_string())); + assert_eq!(decode_entity("A"), Some("A".to_string())); + assert_eq!(decode_entity("A"), Some("A".to_string())); + assert_eq!(decode_entity(" "), Some("\u{00A0}".to_string())); + // U+0000 should become replacement character + assert_eq!(decode_entity("�"), Some("\u{FFFD}".to_string())); + } + + #[test] + fn test_percent_encode_uri() { + let input = format!("https://a{}b.c/%20/%", '\u{1F44D}'); + let encoded = percent_encode_uri(&input); + assert_eq!(encoded, "https://a%F0%9F%91%8Db.c/%20/%25"); + } + + #[test] + fn test_process_link_destination_decodes_entities() { + let encoded = process_link_destination("https://example.com/<"); + assert_eq!(encoded, "https://example.com/%3C"); + } + + #[test] + fn test_paren_depth_limit_in_destination() { + let dest = format!("x{}y{}", "(".repeat(32), ")".repeat(32)); + let input = format!("[a]({dest})\n"); + let parsed = parse_markdown(&input); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + let expected = format!("

    a

    \n"); + assert_eq!(html, expected); + } + + #[test] + fn test_paren_depth_limit_exceeded_in_destination() { + let dest = format!("x{}y{}", "(".repeat(33), ")".repeat(33)); + let input = format!("[a]({dest})\n"); + let parsed = parse_markdown(&input); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + let expected_dest = format!("x{}", "(".repeat(32)); + let trailing = ")".repeat(34); + let expected = format!("

    a(y{trailing}

    \n"); + assert_eq!(html, expected); + } + + #[test] + fn test_title_with_escaped_closing_quote() { + let parsed = parse_markdown("[a](/url \"title with \\\" quote\")\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!( + html, + "

    a

    \n" + ); + } + + #[test] + fn test_hard_line_break_at_end_of_block_is_literal() { + let parsed = parse_markdown("foo\\\\\n"); + let html = document_to_html( + &parsed.tree(), + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + assert_eq!(html, "

    foo\\

    \n"); + } +} diff --git a/crates/biome_markdown_parser/src/token_source.rs b/crates/biome_markdown_parser/src/token_source.rs index b33bd80f5956..71bc216ceae4 100644 --- a/crates/biome_markdown_parser/src/token_source.rs +++ b/crates/biome_markdown_parser/src/token_source.rs @@ -7,6 +7,26 @@ use biome_parser::token_source::{TokenSourceWithBufferedLexer, Trivia}; use biome_parser::{diagnostic::ParseDiagnostic, token_source::TokenSourceCheckpoint}; use biome_rowan::{TextRange, TriviaPieceKind}; +/// Find the start position of the current line in source text. +/// +/// Given a slice of text, finds the byte offset where the current line begins +/// (after the last newline, handling CRLF). +fn find_line_start(before: &str) -> usize { + let last_newline_pos = before.rfind(['\n', '\r']); + match last_newline_pos { + Some(pos) => { + let bytes = before.as_bytes(); + // Handle CRLF: if we found \r and next char is \n, skip both + if bytes.get(pos) == Some(&b'\r') && bytes.get(pos + 1) == Some(&b'\n') { + pos + 2 + } else { + pos + 1 + } + } + None => 0, + } +} + pub(crate) struct MarkdownTokenSource<'source> { lexer: BufferedLexer>, @@ -60,34 +80,96 @@ impl<'source> MarkdownTokenSource<'source> { } } - /// Returns the number of whitespace characters before the current token until the first new line. - /// tab will be counted as 4 spaces https://spec.commonmark.org/0.31.2/#tabs - /// whitespace will be counted as 1 space - pub fn before_whitespace_count(&self) -> usize { - let last_trivia: Vec<&Trivia> = self - .trivia_list - .iter() - .rev() - .take_while(|item| { - // get before whitespace and tab collect - matches!( - item.kind(), - TriviaPieceKind::Whitespace | TriviaPieceKind::Skipped - ) - }) - .collect(); - last_trivia.iter().fold(0, |count, b| match b.kind() { - TriviaPieceKind::Skipped => count + 4, - TriviaPieceKind::Whitespace => count + u32::from(b.len()) as usize, - _ => count, - }) - } - - #[expect(dead_code)] + // === Token-based helpers for Stage 1 refactor === + // These helpers work with explicit NEWLINE tokens rather than trivia inspection. + + /// Returns true if the current token is at the start of input (position 0). + /// + /// This is a position-based check that doesn't rely on trivia_len, + /// making it work correctly when NEWLINE is an explicit token. + pub fn at_start_of_input(&self) -> bool { + self.current_range().start() == 0.into() + } + + /// Returns the source text starting from the current token position. + /// This is useful for lookahead when detecting HTML blocks. + pub fn source_after_current(&self) -> &str { + let range = self.lexer.current_range(); + let start: usize = range.start().into(); + let source = self.lexer.source(); + &source[start..] + } + + /// Returns the full source text. + pub fn source_text(&self) -> &str { + self.lexer.source() + } + + /// Count leading indentation on the current line, including whitespace inside the current token. + /// + /// This scans from the start of the current line to the first non-whitespace character. + /// Tab characters are counted as 4 spaces per CommonMark spec. + pub fn line_start_leading_indent(&self) -> usize { + let range = self.lexer.current_range(); + let start: usize = range.start().into(); + + let source = self.lexer.source(); + let line_start = find_line_start(&source[..start]); + + let line = &source[line_start..]; + let mut count = 0usize; + for c in line.chars() { + match c { + ' ' => count += 1, + '\t' => count += 4, + _ => break, + } + } + count + } + + /// Returns true if the current token starts on a line with only whitespace before it. + /// + /// This is a more robust line-start check when NEWLINE is an explicit token. + pub fn at_line_start_with_whitespace(&self) -> bool { + let range = self.lexer.current_range(); + let start: usize = range.start().into(); + + let source = self.lexer.source(); + let before_token = &source[..start]; + let line_start = find_line_start(before_token); + + source[line_start..start] + .chars() + .all(|c| c == ' ' || c == '\t') + } + + /// Re-lexes the current token in a different context. + /// Used for context-sensitive parsing like link definitions where whitespace + /// needs to produce separate tokens to distinguish destination from title. pub fn re_lex(&mut self, mode: MarkdownReLexContext) -> MarkdownSyntaxKind { self.lexer.re_lex(mode) } + /// Force re-lex the current token in a new lex context. + /// + /// Use this after lookahead operations (like `lookahead_reference_link`) when + /// switching to LinkDefinition context. This ensures that tokens cached during + /// lookahead in Regular context are discarded and re-lexed correctly. + pub fn force_relex_in_context(&mut self, context: MarkdownLexContext) -> MarkdownSyntaxKind { + self.lexer.force_relex_in_context(context) + } + + pub fn set_force_ordered_list_marker(&mut self, value: bool) { + self.lexer.lexer_mut().set_force_ordered_list_marker(value); + } + + /// Bump the current token using the LinkDefinition context. + /// In this context, whitespace produces separate tokens. + pub fn bump_link_definition(&mut self) { + self.bump_with_context(MarkdownLexContext::LinkDefinition); + } + /// Creates a checkpoint to which it can later return using [Self::rewind]. pub fn checkpoint(&self) -> MarkdownTokenSourceCheckpoint { MarkdownTokenSourceCheckpoint { diff --git a/crates/biome_markdown_parser/tests/list_tightness.rs b/crates/biome_markdown_parser/tests/list_tightness.rs new file mode 100644 index 000000000000..3d1ee201e41c --- /dev/null +++ b/crates/biome_markdown_parser/tests/list_tightness.rs @@ -0,0 +1,32 @@ +use biome_markdown_parser::parse_markdown; + +#[test] +fn tracks_list_tightness() { + let source = "- a\n- b\n\npara\n\n- a\n\n b\n"; + let parse = parse_markdown(source); + let tightness = parse.list_tightness(); + + assert_eq!(tightness.len(), 2); + + let mut found_tight = false; + let mut found_loose = false; + + for entry in tightness { + let start: usize = entry.range.start().into(); + let end: usize = entry.range.end().into(); + let text = &source[start..end]; + + if text.contains("- a\n- b") { + assert!(entry.is_tight); + found_tight = true; + } + + if text.contains("- a\n\n b") { + assert!(!entry.is_tight); + found_loose = true; + } + } + + assert!(found_tight); + assert!(found_loose); +} diff --git a/crates/biome_markdown_parser/tests/md_test_suite/error/quote_nesting_too_deep.md b/crates/biome_markdown_parser/tests/md_test_suite/error/quote_nesting_too_deep.md new file mode 100644 index 000000000000..a27418151d0b --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/error/quote_nesting_too_deep.md @@ -0,0 +1 @@ +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Too deep diff --git a/crates/biome_markdown_parser/tests/md_test_suite/error/quote_nesting_too_deep.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/error/quote_nesting_too_deep.md.snap new file mode 100644 index 000000000000..66d9352badb3 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/error/quote_nesting_too_deep.md.snap @@ -0,0 +1,453 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Too deep + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@0..1 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@1..2 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@2..3 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@3..4 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@4..5 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@5..6 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@6..7 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@7..8 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@8..9 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@9..10 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@10..11 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@11..12 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@12..13 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@13..14 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@14..15 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@15..16 ">" [] [], + content: MdBlockList [ + MdQuote, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@100..110 "Too deep" [Skipped(">"), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@110..111 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@111..111 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..111 + 0: (empty) + 1: MD_BLOCK_LIST@0..111 + 0: MD_QUOTE@0..100 + 0: R_ANGLE@0..1 ">" [] [] + 1: MD_BLOCK_LIST@1..100 + 0: MD_QUOTE@1..100 + 0: R_ANGLE@1..2 ">" [] [] + 1: MD_BLOCK_LIST@2..100 + 0: MD_QUOTE@2..100 + 0: R_ANGLE@2..3 ">" [] [] + 1: MD_BLOCK_LIST@3..100 + 0: MD_QUOTE@3..100 + 0: R_ANGLE@3..4 ">" [] [] + 1: MD_BLOCK_LIST@4..100 + 0: MD_QUOTE@4..100 + 0: R_ANGLE@4..5 ">" [] [] + 1: MD_BLOCK_LIST@5..100 + 0: MD_QUOTE@5..100 + 0: R_ANGLE@5..6 ">" [] [] + 1: MD_BLOCK_LIST@6..100 + 0: MD_QUOTE@6..100 + 0: R_ANGLE@6..7 ">" [] [] + 1: MD_BLOCK_LIST@7..100 + 0: MD_QUOTE@7..100 + 0: R_ANGLE@7..8 ">" [] [] + 1: MD_BLOCK_LIST@8..100 + 0: MD_QUOTE@8..100 + 0: R_ANGLE@8..9 ">" [] [] + 1: MD_BLOCK_LIST@9..100 + 0: MD_QUOTE@9..100 + 0: R_ANGLE@9..10 ">" [] [] + 1: MD_BLOCK_LIST@10..100 + 0: MD_QUOTE@10..100 + 0: R_ANGLE@10..11 ">" [] [] + 1: MD_BLOCK_LIST@11..100 + 0: MD_QUOTE@11..100 + 0: R_ANGLE@11..12 ">" [] [] + 1: MD_BLOCK_LIST@12..100 + 0: MD_QUOTE@12..100 + 0: R_ANGLE@12..13 ">" [] [] + 1: MD_BLOCK_LIST@13..100 + 0: MD_QUOTE@13..100 + 0: R_ANGLE@13..14 ">" [] [] + 1: MD_BLOCK_LIST@14..100 + 0: MD_QUOTE@14..100 + 0: R_ANGLE@14..15 ">" [] [] + 1: MD_BLOCK_LIST@15..100 + 0: MD_QUOTE@15..100 + 0: R_ANGLE@15..16 ">" [] [] + 1: MD_BLOCK_LIST@16..100 + 0: MD_QUOTE@16..100 + 0: R_ANGLE@16..17 ">" [] [] + 1: MD_BLOCK_LIST@17..100 + 0: MD_QUOTE@17..100 + 0: R_ANGLE@17..18 ">" [] [] + 1: MD_BLOCK_LIST@18..100 + 0: MD_QUOTE@18..100 + 0: R_ANGLE@18..19 ">" [] [] + 1: MD_BLOCK_LIST@19..100 + 0: MD_QUOTE@19..100 + 0: R_ANGLE@19..20 ">" [] [] + 1: MD_BLOCK_LIST@20..100 + 0: MD_QUOTE@20..100 + 0: R_ANGLE@20..21 ">" [] [] + 1: MD_BLOCK_LIST@21..100 + 0: MD_QUOTE@21..100 + 0: R_ANGLE@21..22 ">" [] [] + 1: MD_BLOCK_LIST@22..100 + 0: MD_QUOTE@22..100 + 0: R_ANGLE@22..23 ">" [] [] + 1: MD_BLOCK_LIST@23..100 + 0: MD_QUOTE@23..100 + 0: R_ANGLE@23..24 ">" [] [] + 1: MD_BLOCK_LIST@24..100 + 0: MD_QUOTE@24..100 + 0: R_ANGLE@24..25 ">" [] [] + 1: MD_BLOCK_LIST@25..100 + 0: MD_QUOTE@25..100 + 0: R_ANGLE@25..26 ">" [] [] + 1: MD_BLOCK_LIST@26..100 + 0: MD_QUOTE@26..100 + 0: R_ANGLE@26..27 ">" [] [] + 1: MD_BLOCK_LIST@27..100 + 0: MD_QUOTE@27..100 + 0: R_ANGLE@27..28 ">" [] [] + 1: MD_BLOCK_LIST@28..100 + 0: MD_QUOTE@28..100 + 0: R_ANGLE@28..29 ">" [] [] + 1: MD_BLOCK_LIST@29..100 + 0: MD_QUOTE@29..100 + 0: R_ANGLE@29..30 ">" [] [] + 1: MD_BLOCK_LIST@30..100 + 0: MD_QUOTE@30..100 + 0: R_ANGLE@30..31 ">" [] [] + 1: MD_BLOCK_LIST@31..100 + 0: MD_QUOTE@31..100 + 0: R_ANGLE@31..32 ">" [] [] + 1: MD_BLOCK_LIST@32..100 + 0: MD_QUOTE@32..100 + 0: R_ANGLE@32..33 ">" [] [] + 1: MD_BLOCK_LIST@33..100 + 0: MD_QUOTE@33..100 + 0: R_ANGLE@33..34 ">" [] [] + 1: MD_BLOCK_LIST@34..100 + 0: MD_QUOTE@34..100 + 0: R_ANGLE@34..35 ">" [] [] + 1: MD_BLOCK_LIST@35..100 + 0: MD_QUOTE@35..100 + 0: R_ANGLE@35..36 ">" [] [] + 1: MD_BLOCK_LIST@36..100 + 0: MD_QUOTE@36..100 + 0: R_ANGLE@36..37 ">" [] [] + 1: MD_BLOCK_LIST@37..100 + 0: MD_QUOTE@37..100 + 0: R_ANGLE@37..38 ">" [] [] + 1: MD_BLOCK_LIST@38..100 + 0: MD_QUOTE@38..100 + 0: R_ANGLE@38..39 ">" [] [] + 1: MD_BLOCK_LIST@39..100 + 0: MD_QUOTE@39..100 + 0: R_ANGLE@39..40 ">" [] [] + 1: MD_BLOCK_LIST@40..100 + 0: MD_QUOTE@40..100 + 0: R_ANGLE@40..41 ">" [] [] + 1: MD_BLOCK_LIST@41..100 + 0: MD_QUOTE@41..100 + 0: R_ANGLE@41..42 ">" [] [] + 1: MD_BLOCK_LIST@42..100 + 0: MD_QUOTE@42..100 + 0: R_ANGLE@42..43 ">" [] [] + 1: MD_BLOCK_LIST@43..100 + 0: MD_QUOTE@43..100 + 0: R_ANGLE@43..44 ">" [] [] + 1: MD_BLOCK_LIST@44..100 + 0: MD_QUOTE@44..100 + 0: R_ANGLE@44..45 ">" [] [] + 1: MD_BLOCK_LIST@45..100 + 0: MD_QUOTE@45..100 + 0: R_ANGLE@45..46 ">" [] [] + 1: MD_BLOCK_LIST@46..100 + 0: MD_QUOTE@46..100 + 0: R_ANGLE@46..47 ">" [] [] + 1: MD_BLOCK_LIST@47..100 + 0: MD_QUOTE@47..100 + 0: R_ANGLE@47..48 ">" [] [] + 1: MD_BLOCK_LIST@48..100 + 0: MD_QUOTE@48..100 + 0: R_ANGLE@48..49 ">" [] [] + 1: MD_BLOCK_LIST@49..100 + 0: MD_QUOTE@49..100 + 0: R_ANGLE@49..50 ">" [] [] + 1: MD_BLOCK_LIST@50..100 + 0: MD_QUOTE@50..100 + 0: R_ANGLE@50..51 ">" [] [] + 1: MD_BLOCK_LIST@51..100 + 0: MD_QUOTE@51..100 + 0: R_ANGLE@51..52 ">" [] [] + 1: MD_BLOCK_LIST@52..100 + 0: MD_QUOTE@52..100 + 0: R_ANGLE@52..53 ">" [] [] + 1: MD_BLOCK_LIST@53..100 + 0: MD_QUOTE@53..100 + 0: R_ANGLE@53..54 ">" [] [] + 1: MD_BLOCK_LIST@54..100 + 0: MD_QUOTE@54..100 + 0: R_ANGLE@54..55 ">" [] [] + 1: MD_BLOCK_LIST@55..100 + 0: MD_QUOTE@55..100 + 0: R_ANGLE@55..56 ">" [] [] + 1: MD_BLOCK_LIST@56..100 + 0: MD_QUOTE@56..100 + 0: R_ANGLE@56..57 ">" [] [] + 1: MD_BLOCK_LIST@57..100 + 0: MD_QUOTE@57..100 + 0: R_ANGLE@57..58 ">" [] [] + 1: MD_BLOCK_LIST@58..100 + 0: MD_QUOTE@58..100 + 0: R_ANGLE@58..59 ">" [] [] + 1: MD_BLOCK_LIST@59..100 + 0: MD_QUOTE@59..100 + 0: R_ANGLE@59..60 ">" [] [] + 1: MD_BLOCK_LIST@60..100 + 0: MD_QUOTE@60..100 + 0: R_ANGLE@60..61 ">" [] [] + 1: MD_BLOCK_LIST@61..100 + 0: MD_QUOTE@61..100 + 0: R_ANGLE@61..62 ">" [] [] + 1: MD_BLOCK_LIST@62..100 + 0: MD_QUOTE@62..100 + 0: R_ANGLE@62..63 ">" [] [] + 1: MD_BLOCK_LIST@63..100 + 0: MD_QUOTE@63..100 + 0: R_ANGLE@63..64 ">" [] [] + 1: MD_BLOCK_LIST@64..100 + 0: MD_QUOTE@64..100 + 0: R_ANGLE@64..65 ">" [] [] + 1: MD_BLOCK_LIST@65..100 + 0: MD_QUOTE@65..100 + 0: R_ANGLE@65..66 ">" [] [] + 1: MD_BLOCK_LIST@66..100 + 0: MD_QUOTE@66..100 + 0: R_ANGLE@66..67 ">" [] [] + 1: MD_BLOCK_LIST@67..100 + 0: MD_QUOTE@67..100 + 0: R_ANGLE@67..68 ">" [] [] + 1: MD_BLOCK_LIST@68..100 + 0: MD_QUOTE@68..100 + 0: R_ANGLE@68..69 ">" [] [] + 1: MD_BLOCK_LIST@69..100 + 0: MD_QUOTE@69..100 + 0: R_ANGLE@69..70 ">" [] [] + 1: MD_BLOCK_LIST@70..100 + 0: MD_QUOTE@70..100 + 0: R_ANGLE@70..71 ">" [] [] + 1: MD_BLOCK_LIST@71..100 + 0: MD_QUOTE@71..100 + 0: R_ANGLE@71..72 ">" [] [] + 1: MD_BLOCK_LIST@72..100 + 0: MD_QUOTE@72..100 + 0: R_ANGLE@72..73 ">" [] [] + 1: MD_BLOCK_LIST@73..100 + 0: MD_QUOTE@73..100 + 0: R_ANGLE@73..74 ">" [] [] + 1: MD_BLOCK_LIST@74..100 + 0: MD_QUOTE@74..100 + 0: R_ANGLE@74..75 ">" [] [] + 1: MD_BLOCK_LIST@75..100 + 0: MD_QUOTE@75..100 + 0: R_ANGLE@75..76 ">" [] [] + 1: MD_BLOCK_LIST@76..100 + 0: MD_QUOTE@76..100 + 0: R_ANGLE@76..77 ">" [] [] + 1: MD_BLOCK_LIST@77..100 + 0: MD_QUOTE@77..100 + 0: R_ANGLE@77..78 ">" [] [] + 1: MD_BLOCK_LIST@78..100 + 0: MD_QUOTE@78..100 + 0: R_ANGLE@78..79 ">" [] [] + 1: MD_BLOCK_LIST@79..100 + 0: MD_QUOTE@79..100 + 0: R_ANGLE@79..80 ">" [] [] + 1: MD_BLOCK_LIST@80..100 + 0: MD_QUOTE@80..100 + 0: R_ANGLE@80..81 ">" [] [] + 1: MD_BLOCK_LIST@81..100 + 0: MD_QUOTE@81..100 + 0: R_ANGLE@81..82 ">" [] [] + 1: MD_BLOCK_LIST@82..100 + 0: MD_QUOTE@82..100 + 0: R_ANGLE@82..83 ">" [] [] + 1: MD_BLOCK_LIST@83..100 + 0: MD_QUOTE@83..100 + 0: R_ANGLE@83..84 ">" [] [] + 1: MD_BLOCK_LIST@84..100 + 0: MD_QUOTE@84..100 + 0: R_ANGLE@84..85 ">" [] [] + 1: MD_BLOCK_LIST@85..100 + 0: MD_QUOTE@85..100 + 0: R_ANGLE@85..86 ">" [] [] + 1: MD_BLOCK_LIST@86..100 + 0: MD_QUOTE@86..100 + 0: R_ANGLE@86..87 ">" [] [] + 1: MD_BLOCK_LIST@87..100 + 0: MD_QUOTE@87..100 + 0: R_ANGLE@87..88 ">" [] [] + 1: MD_BLOCK_LIST@88..100 + 0: MD_QUOTE@88..100 + 0: R_ANGLE@88..89 ">" [] [] + 1: MD_BLOCK_LIST@89..100 + 0: MD_QUOTE@89..100 + 0: R_ANGLE@89..90 ">" [] [] + 1: MD_BLOCK_LIST@90..100 + 0: MD_QUOTE@90..100 + 0: R_ANGLE@90..91 ">" [] [] + 1: MD_BLOCK_LIST@91..100 + 0: MD_QUOTE@91..100 + 0: R_ANGLE@91..92 ">" [] [] + 1: MD_BLOCK_LIST@92..100 + 0: MD_QUOTE@92..100 + 0: R_ANGLE@92..93 ">" [] [] + 1: MD_BLOCK_LIST@93..100 + 0: MD_QUOTE@93..100 + 0: R_ANGLE@93..94 ">" [] [] + 1: MD_BLOCK_LIST@94..100 + 0: MD_QUOTE@94..100 + 0: R_ANGLE@94..95 ">" [] [] + 1: MD_BLOCK_LIST@95..100 + 0: MD_QUOTE@95..100 + 0: R_ANGLE@95..96 ">" [] [] + 1: MD_BLOCK_LIST@96..100 + 0: MD_QUOTE@96..100 + 0: R_ANGLE@96..97 ">" [] [] + 1: MD_BLOCK_LIST@97..100 + 0: MD_QUOTE@97..100 + 0: R_ANGLE@97..98 ">" [] [] + 1: MD_BLOCK_LIST@98..100 + 0: MD_QUOTE@98..100 + 0: R_ANGLE@98..99 ">" [] [] + 1: MD_BLOCK_LIST@99..100 + 0: MD_QUOTE@99..100 + 0: R_ANGLE@99..100 ">" [] [] + 1: MD_BLOCK_LIST@100..100 + 1: MD_PARAGRAPH@100..111 + 0: MD_INLINE_ITEM_LIST@100..111 + 0: MD_TEXTUAL@100..110 + 0: MD_TEXTUAL_LITERAL@100..110 "Too deep" [Skipped(">"), Skipped(" ")] [] + 1: MD_TEXTUAL@110..111 + 0: MD_TEXTUAL_LITERAL@110..111 "\n" [] [] + 1: (empty) + 2: EOF@111..111 "" [] [] + +``` + +## Diagnostics + +``` +quote_nesting_too_deep.md:1:101 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Block quote nesting exceeds maximum depth of 100. + + > 1 │ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Too deep + │ ^ + 2 │ + + i nesting limit reached here + + > 1 │ >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Too deep + │ ^ + 2 │ + + i Reduce nesting depth. Additional levels will be treated as content. + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/error/too_many_hashes.md b/crates/biome_markdown_parser/tests/md_test_suite/error/too_many_hashes.md new file mode 100644 index 000000000000..358e3305eec3 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/error/too_many_hashes.md @@ -0,0 +1 @@ +####### This has too many hashes diff --git a/crates/biome_markdown_parser/tests/md_test_suite/error/too_many_hashes.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/error/too_many_hashes.md.snap new file mode 100644 index 000000000000..bdfe48e485e4 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/error/too_many_hashes.md.snap @@ -0,0 +1,77 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +####### This has too many hashes + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..7 "#######" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@7..32 " This has too many hashes" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..33 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@33..33 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..33 + 0: (empty) + 1: MD_BLOCK_LIST@0..33 + 0: MD_PARAGRAPH@0..33 + 0: MD_INLINE_ITEM_LIST@0..33 + 0: MD_TEXTUAL@0..7 + 0: MD_TEXTUAL_LITERAL@0..7 "#######" [] [] + 1: MD_TEXTUAL@7..32 + 0: MD_TEXTUAL_LITERAL@7..32 " This has too many hashes" [] [] + 2: MD_TEXTUAL@32..33 + 0: MD_TEXTUAL_LITERAL@32..33 "\n" [] [] + 1: (empty) + 2: EOF@33..33 "" [] [] + +``` + +## Diagnostics + +``` +too_many_hashes.md:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × ATX heading has 7 hashes, but maximum is 6. + + > 1 │ ####### This has too many hashes + │ ^^^^^^^ + 2 │ + + i heading started here + + > 1 │ ####### This has too many hashes + │ ^^^^^^^ + 2 │ + + i Use 1-6 `#` characters for headings. This will be parsed as a paragraph. + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/error/unterminated_code_fence.md b/crates/biome_markdown_parser/tests/md_test_suite/error/unterminated_code_fence.md new file mode 100644 index 000000000000..b8a7be054e98 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/error/unterminated_code_fence.md @@ -0,0 +1,4 @@ +```rust +fn main() { + println!("Hello"); +} diff --git a/crates/biome_markdown_parser/tests/md_test_suite/error/unterminated_code_fence.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/error/unterminated_code_fence.md.snap new file mode 100644 index 000000000000..89a7c3bf6d4c --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/error/unterminated_code_fence.md.snap @@ -0,0 +1,172 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +```rust +fn main() { + println!("Hello"); +} + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@0..3 "```" [] [], + code_list: MdCodeNameList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@3..7 "rust" [] [], + }, + ], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@7..8 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@8..15 "fn main" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..16 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..17 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@17..19 " {" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@19..20 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@20..21 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..22 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@22..23 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..24 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..31 "println" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..32 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..33 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@33..40 "\"Hello\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..41 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@41..42 ";" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..43 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@43..44 "}" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@44..45 "\n" [] [], + }, + ], + r_fence: missing (required), + }, + ], + eof_token: EOF@45..45 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..45 + 0: (empty) + 1: MD_BLOCK_LIST@0..45 + 0: MD_FENCED_CODE_BLOCK@0..45 + 0: TRIPLE_BACKTICK@0..3 "```" [] [] + 1: MD_CODE_NAME_LIST@3..7 + 0: MD_TEXTUAL@3..7 + 0: MD_TEXTUAL_LITERAL@3..7 "rust" [] [] + 2: MD_INLINE_ITEM_LIST@7..45 + 0: MD_TEXTUAL@7..8 + 0: MD_TEXTUAL_LITERAL@7..8 "\n" [] [] + 1: MD_TEXTUAL@8..15 + 0: MD_TEXTUAL_LITERAL@8..15 "fn main" [] [] + 2: MD_TEXTUAL@15..16 + 0: MD_TEXTUAL_LITERAL@15..16 "(" [] [] + 3: MD_TEXTUAL@16..17 + 0: MD_TEXTUAL_LITERAL@16..17 ")" [] [] + 4: MD_TEXTUAL@17..19 + 0: MD_TEXTUAL_LITERAL@17..19 " {" [] [] + 5: MD_TEXTUAL@19..20 + 0: MD_TEXTUAL_LITERAL@19..20 "\n" [] [] + 6: MD_TEXTUAL@20..21 + 0: MD_TEXTUAL_LITERAL@20..21 " " [] [] + 7: MD_TEXTUAL@21..22 + 0: MD_TEXTUAL_LITERAL@21..22 " " [] [] + 8: MD_TEXTUAL@22..23 + 0: MD_TEXTUAL_LITERAL@22..23 " " [] [] + 9: MD_TEXTUAL@23..24 + 0: MD_TEXTUAL_LITERAL@23..24 " " [] [] + 10: MD_TEXTUAL@24..31 + 0: MD_TEXTUAL_LITERAL@24..31 "println" [] [] + 11: MD_TEXTUAL@31..32 + 0: MD_TEXTUAL_LITERAL@31..32 "!" [] [] + 12: MD_TEXTUAL@32..33 + 0: MD_TEXTUAL_LITERAL@32..33 "(" [] [] + 13: MD_TEXTUAL@33..40 + 0: MD_TEXTUAL_LITERAL@33..40 "\"Hello\"" [] [] + 14: MD_TEXTUAL@40..41 + 0: MD_TEXTUAL_LITERAL@40..41 ")" [] [] + 15: MD_TEXTUAL@41..42 + 0: MD_TEXTUAL_LITERAL@41..42 ";" [] [] + 16: MD_TEXTUAL@42..43 + 0: MD_TEXTUAL_LITERAL@42..43 "\n" [] [] + 17: MD_TEXTUAL@43..44 + 0: MD_TEXTUAL_LITERAL@43..44 "}" [] [] + 18: MD_TEXTUAL@44..45 + 0: MD_TEXTUAL_LITERAL@44..45 "\n" [] [] + 3: (empty) + 2: EOF@45..45 "" [] [] + +``` + +## Diagnostics + +``` +unterminated_code_fence.md:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unterminated fenced code block, expected closing triple backticks (```). + + > 1 │ ```rust + │ ^^^ + 2 │ fn main() { + 3 │ println!("Hello"); + + i code block started here + + > 1 │ ```rust + │ ^^^ + 2 │ fn main() { + 3 │ println!("Hello"); + + i Add closing triple backticks (```) at the start of a new line. + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/atx_heading_trailing_hash.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/atx_heading_trailing_hash.md new file mode 100644 index 000000000000..b9cbe781d34a --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/atx_heading_trailing_hash.md @@ -0,0 +1,4 @@ +# foo# +# foo # +# foo # +#foo diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/atx_heading_trailing_hash.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/atx_heading_trailing_hash.md.snap new file mode 100644 index 000000000000..864437546029 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/atx_heading_trailing_hash.md.snap @@ -0,0 +1,169 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +# foo# +# foo # +# foo # +#foo + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@0..1 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..5 " foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@5..6 "#" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@6..7 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@7..8 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@8..12 " foo" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [ + MdHash { + hash_token: HASH@12..14 "#" [Skipped(" ")] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@14..15 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@15..16 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..22 " foo" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [ + MdHash { + hash_token: HASH@22..26 "#" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@26..27 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@27..28 "#" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..31 "foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..32 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@32..32 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..32 + 0: (empty) + 1: MD_BLOCK_LIST@0..32 + 0: MD_HEADER@0..6 + 0: MD_HASH_LIST@0..1 + 0: MD_HASH@0..1 + 0: HASH@0..1 "#" [] [] + 1: MD_PARAGRAPH@1..6 + 0: MD_INLINE_ITEM_LIST@1..6 + 0: MD_TEXTUAL@1..5 + 0: MD_TEXTUAL_LITERAL@1..5 " foo" [] [] + 1: MD_TEXTUAL@5..6 + 0: MD_TEXTUAL_LITERAL@5..6 "#" [] [] + 1: (empty) + 2: MD_HASH_LIST@6..6 + 1: MD_NEWLINE@6..7 + 0: NEWLINE@6..7 "\n" [] [] + 2: MD_HEADER@7..14 + 0: MD_HASH_LIST@7..8 + 0: MD_HASH@7..8 + 0: HASH@7..8 "#" [] [] + 1: MD_PARAGRAPH@8..12 + 0: MD_INLINE_ITEM_LIST@8..12 + 0: MD_TEXTUAL@8..12 + 0: MD_TEXTUAL_LITERAL@8..12 " foo" [] [] + 1: (empty) + 2: MD_HASH_LIST@12..14 + 0: MD_HASH@12..14 + 0: HASH@12..14 "#" [Skipped(" ")] [] + 3: MD_NEWLINE@14..15 + 0: NEWLINE@14..15 "\n" [] [] + 4: MD_HEADER@15..26 + 0: MD_HASH_LIST@15..16 + 0: MD_HASH@15..16 + 0: HASH@15..16 "#" [] [] + 1: MD_PARAGRAPH@16..22 + 0: MD_INLINE_ITEM_LIST@16..22 + 0: MD_TEXTUAL@16..22 + 0: MD_TEXTUAL_LITERAL@16..22 " foo" [] [] + 1: (empty) + 2: MD_HASH_LIST@22..26 + 0: MD_HASH@22..26 + 0: HASH@22..26 "#" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 5: MD_NEWLINE@26..27 + 0: NEWLINE@26..27 "\n" [] [] + 6: MD_PARAGRAPH@27..32 + 0: MD_INLINE_ITEM_LIST@27..32 + 0: MD_TEXTUAL@27..28 + 0: MD_TEXTUAL_LITERAL@27..28 "#" [] [] + 1: MD_TEXTUAL@28..31 + 0: MD_TEXTUAL_LITERAL@28..31 "foo" [] [] + 2: MD_TEXTUAL@31..32 + 0: MD_TEXTUAL_LITERAL@31..32 "\n" [] [] + 1: (empty) + 2: EOF@32..32 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/autolinks.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/autolinks.md new file mode 100644 index 000000000000..e786ac124363 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/autolinks.md @@ -0,0 +1,7 @@ +URL autolink: + +Email autolink: + +Not an autolink (no scheme): + +Mixed text: Visit for more. diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/autolinks.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/autolinks.md.snap new file mode 100644 index 000000000000..51ec91b293cf --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/autolinks.md.snap @@ -0,0 +1,226 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +URL autolink: + +Email autolink: + +Not an autolink (no scheme): + +Mixed text: Visit for more. + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..14 "URL autolink: " [] [], + }, + MdAutolink { + l_angle_token: L_ANGLE@14..15 "<" [] [], + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..34 "https://example.com" [] [], + }, + ], + r_angle_token: R_ANGLE@34..35 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@35..36 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@36..37 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..53 "Email autolink: " [] [], + }, + MdAutolink { + l_angle_token: L_ANGLE@53..54 "<" [] [], + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@54..70 "user@example.com" [] [], + }, + ], + r_angle_token: R_ANGLE@70..71 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@71..72 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@72..73 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@73..89 "Not an autolink " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@89..90 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@90..99 "no scheme" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@99..100 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@100..101 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@101..102 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@102..103 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@103..114 "example.com" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@114..115 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@115..116 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@116..117 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@117..135 "Mixed text: Visit " [] [], + }, + MdAutolink { + l_angle_token: L_ANGLE@135..136 "<" [] [], + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@136..147 "http://rust" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@147..148 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@148..156 "lang.org" [] [], + }, + ], + r_angle_token: R_ANGLE@156..157 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@157..167 " for more." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@167..168 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@168..168 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..168 + 0: (empty) + 1: MD_BLOCK_LIST@0..168 + 0: MD_PARAGRAPH@0..36 + 0: MD_INLINE_ITEM_LIST@0..36 + 0: MD_TEXTUAL@0..14 + 0: MD_TEXTUAL_LITERAL@0..14 "URL autolink: " [] [] + 1: MD_AUTOLINK@14..35 + 0: L_ANGLE@14..15 "<" [] [] + 1: MD_INLINE_ITEM_LIST@15..34 + 0: MD_TEXTUAL@15..34 + 0: MD_TEXTUAL_LITERAL@15..34 "https://example.com" [] [] + 2: R_ANGLE@34..35 ">" [] [] + 2: MD_TEXTUAL@35..36 + 0: MD_TEXTUAL_LITERAL@35..36 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@36..37 + 0: NEWLINE@36..37 "\n" [] [] + 2: MD_PARAGRAPH@37..72 + 0: MD_INLINE_ITEM_LIST@37..72 + 0: MD_TEXTUAL@37..53 + 0: MD_TEXTUAL_LITERAL@37..53 "Email autolink: " [] [] + 1: MD_AUTOLINK@53..71 + 0: L_ANGLE@53..54 "<" [] [] + 1: MD_INLINE_ITEM_LIST@54..70 + 0: MD_TEXTUAL@54..70 + 0: MD_TEXTUAL_LITERAL@54..70 "user@example.com" [] [] + 2: R_ANGLE@70..71 ">" [] [] + 2: MD_TEXTUAL@71..72 + 0: MD_TEXTUAL_LITERAL@71..72 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@72..73 + 0: NEWLINE@72..73 "\n" [] [] + 4: MD_PARAGRAPH@73..116 + 0: MD_INLINE_ITEM_LIST@73..116 + 0: MD_TEXTUAL@73..89 + 0: MD_TEXTUAL_LITERAL@73..89 "Not an autolink " [] [] + 1: MD_TEXTUAL@89..90 + 0: MD_TEXTUAL_LITERAL@89..90 "(" [] [] + 2: MD_TEXTUAL@90..99 + 0: MD_TEXTUAL_LITERAL@90..99 "no scheme" [] [] + 3: MD_TEXTUAL@99..100 + 0: MD_TEXTUAL_LITERAL@99..100 ")" [] [] + 4: MD_TEXTUAL@100..101 + 0: MD_TEXTUAL_LITERAL@100..101 ":" [] [] + 5: MD_TEXTUAL@101..102 + 0: MD_TEXTUAL_LITERAL@101..102 " " [] [] + 6: MD_TEXTUAL@102..103 + 0: MD_TEXTUAL_LITERAL@102..103 "<" [] [] + 7: MD_TEXTUAL@103..114 + 0: MD_TEXTUAL_LITERAL@103..114 "example.com" [] [] + 8: MD_TEXTUAL@114..115 + 0: MD_TEXTUAL_LITERAL@114..115 ">" [] [] + 9: MD_TEXTUAL@115..116 + 0: MD_TEXTUAL_LITERAL@115..116 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@116..117 + 0: NEWLINE@116..117 "\n" [] [] + 6: MD_PARAGRAPH@117..168 + 0: MD_INLINE_ITEM_LIST@117..168 + 0: MD_TEXTUAL@117..135 + 0: MD_TEXTUAL_LITERAL@117..135 "Mixed text: Visit " [] [] + 1: MD_AUTOLINK@135..157 + 0: L_ANGLE@135..136 "<" [] [] + 1: MD_INLINE_ITEM_LIST@136..156 + 0: MD_TEXTUAL@136..147 + 0: MD_TEXTUAL_LITERAL@136..147 "http://rust" [] [] + 1: MD_TEXTUAL@147..148 + 0: MD_TEXTUAL_LITERAL@147..148 "-" [] [] + 2: MD_TEXTUAL@148..156 + 0: MD_TEXTUAL_LITERAL@148..156 "lang.org" [] [] + 2: R_ANGLE@156..157 ">" [] [] + 2: MD_TEXTUAL@157..167 + 0: MD_TEXTUAL_LITERAL@157..167 " for more." [] [] + 3: MD_TEXTUAL@167..168 + 0: MD_TEXTUAL_LITERAL@167..168 "\n" [] [] + 1: (empty) + 2: EOF@168..168 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote.md new file mode 100644 index 000000000000..041984494079 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote.md @@ -0,0 +1,4 @@ +> This is a quote +> It continues here + +> Another quote block diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote.md.snap new file mode 100644 index 000000000000..e891805d69ab --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote.md.snap @@ -0,0 +1,103 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +> This is a quote +> It continues here + +> Another quote block + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@0..1 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..17 "This is a quote" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@17..18 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@18..37 "It continues here" [Skipped(">"), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..38 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@38..39 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@39..40 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..60 "Another quote block" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@60..61 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + eof_token: EOF@61..61 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..61 + 0: (empty) + 1: MD_BLOCK_LIST@0..61 + 0: MD_QUOTE@0..38 + 0: R_ANGLE@0..1 ">" [] [] + 1: MD_BLOCK_LIST@1..38 + 0: MD_PARAGRAPH@1..38 + 0: MD_INLINE_ITEM_LIST@1..38 + 0: MD_TEXTUAL@1..17 + 0: MD_TEXTUAL_LITERAL@1..17 "This is a quote" [Skipped(" ")] [] + 1: MD_TEXTUAL@17..18 + 0: MD_TEXTUAL_LITERAL@17..18 "\n" [] [] + 2: MD_TEXTUAL@18..37 + 0: MD_TEXTUAL_LITERAL@18..37 "It continues here" [Skipped(">"), Skipped(" ")] [] + 3: MD_TEXTUAL@37..38 + 0: MD_TEXTUAL_LITERAL@37..38 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@38..39 + 0: NEWLINE@38..39 "\n" [] [] + 2: MD_QUOTE@39..61 + 0: R_ANGLE@39..40 ">" [] [] + 1: MD_BLOCK_LIST@40..61 + 0: MD_PARAGRAPH@40..61 + 0: MD_INLINE_ITEM_LIST@40..61 + 0: MD_TEXTUAL@40..60 + 0: MD_TEXTUAL_LITERAL@40..60 "Another quote block" [Skipped(" ")] [] + 1: MD_TEXTUAL@60..61 + 0: MD_TEXTUAL_LITERAL@60..61 "\n" [] [] + 1: (empty) + 2: EOF@61..61 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote_grouping.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote_grouping.md new file mode 100644 index 000000000000..b6c5ded632cf --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote_grouping.md @@ -0,0 +1,7 @@ +> a +> b + +> a +> b + +> > nested diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote_grouping.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote_grouping.md.snap new file mode 100644 index 000000000000..ba825c224b0d --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/block_quote_grouping.md.snap @@ -0,0 +1,155 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +> a +> b + +> a +> b + +> > nested + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@0..1 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..3 "a" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@3..4 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@4..7 "b" [Skipped(">"), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@7..8 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@8..9 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@9..10 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..13 " a" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..14 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..17 "b" [Skipped(">"), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@17..18 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@18..19 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@19..20 ">" [] [], + content: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@20..22 ">" [Skipped(" ")] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@22..29 "nested" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..30 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + }, + ], + eof_token: EOF@30..30 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..30 + 0: (empty) + 1: MD_BLOCK_LIST@0..30 + 0: MD_QUOTE@0..8 + 0: R_ANGLE@0..1 ">" [] [] + 1: MD_BLOCK_LIST@1..8 + 0: MD_PARAGRAPH@1..8 + 0: MD_INLINE_ITEM_LIST@1..8 + 0: MD_TEXTUAL@1..3 + 0: MD_TEXTUAL_LITERAL@1..3 "a" [Skipped(" ")] [] + 1: MD_TEXTUAL@3..4 + 0: MD_TEXTUAL_LITERAL@3..4 "\n" [] [] + 2: MD_TEXTUAL@4..7 + 0: MD_TEXTUAL_LITERAL@4..7 "b" [Skipped(">"), Skipped(" ")] [] + 3: MD_TEXTUAL@7..8 + 0: MD_TEXTUAL_LITERAL@7..8 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@8..9 + 0: NEWLINE@8..9 "\n" [] [] + 2: MD_QUOTE@9..18 + 0: R_ANGLE@9..10 ">" [] [] + 1: MD_BLOCK_LIST@10..18 + 0: MD_PARAGRAPH@10..18 + 0: MD_INLINE_ITEM_LIST@10..18 + 0: MD_TEXTUAL@10..13 + 0: MD_TEXTUAL_LITERAL@10..13 " a" [Skipped(" ")] [] + 1: MD_TEXTUAL@13..14 + 0: MD_TEXTUAL_LITERAL@13..14 "\n" [] [] + 2: MD_TEXTUAL@14..17 + 0: MD_TEXTUAL_LITERAL@14..17 "b" [Skipped(">"), Skipped(" ")] [] + 3: MD_TEXTUAL@17..18 + 0: MD_TEXTUAL_LITERAL@17..18 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@18..19 + 0: NEWLINE@18..19 "\n" [] [] + 4: MD_QUOTE@19..30 + 0: R_ANGLE@19..20 ">" [] [] + 1: MD_BLOCK_LIST@20..30 + 0: MD_QUOTE@20..30 + 0: R_ANGLE@20..22 ">" [Skipped(" ")] [] + 1: MD_BLOCK_LIST@22..30 + 0: MD_PARAGRAPH@22..30 + 0: MD_INLINE_ITEM_LIST@22..30 + 0: MD_TEXTUAL@22..29 + 0: MD_TEXTUAL_LITERAL@22..29 "nested" [Skipped(" ")] [] + 1: MD_TEXTUAL@29..30 + 0: MD_TEXTUAL_LITERAL@29..30 "\n" [] [] + 1: (empty) + 2: EOF@30..30 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/bullet_list.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/bullet_list.md new file mode 100644 index 000000000000..429b37d1d126 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/bullet_list.md @@ -0,0 +1,9 @@ +- Item one +- Item two +- Item three + +* Alternative marker +* Another item + ++ Plus marker ++ Another plus item diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/bullet_list.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/bullet_list.md.snap new file mode 100644 index 000000000000..fb093c848224 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/bullet_list.md.snap @@ -0,0 +1,251 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +- Item one +- Item two +- Item three + +* Alternative marker +* Another item + ++ Plus marker ++ Another plus item + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdBulletListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: MINUS@0..1 "-" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..10 " Item one" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..11 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBullet { + bullet: MINUS@11..12 "-" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@12..21 " Item two" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..22 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBullet { + bullet: MINUS@22..23 "-" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..34 " Item three" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@34..35 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@35..36 "\n" [] [], + }, + ], + }, + ], + }, + MdBulletListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: STAR@36..37 "*" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..56 " Alternative marker" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..57 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBullet { + bullet: STAR@57..58 "*" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..71 " Another item" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@71..72 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@72..73 "\n" [] [], + }, + ], + }, + ], + }, + MdBulletListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: PLUS@73..74 "+" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@74..86 " Plus marker" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@86..87 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBullet { + bullet: PLUS@87..88 "+" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@88..106 " Another plus item" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@106..107 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + }, + ], + eof_token: EOF@107..107 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..107 + 0: (empty) + 1: MD_BLOCK_LIST@0..107 + 0: MD_BULLET_LIST_ITEM@0..36 + 0: MD_BULLET_LIST@0..36 + 0: MD_BULLET@0..11 + 0: MINUS@0..1 "-" [] [] + 1: MD_BLOCK_LIST@1..11 + 0: MD_PARAGRAPH@1..11 + 0: MD_INLINE_ITEM_LIST@1..11 + 0: MD_TEXTUAL@1..10 + 0: MD_TEXTUAL_LITERAL@1..10 " Item one" [] [] + 1: MD_TEXTUAL@10..11 + 0: MD_TEXTUAL_LITERAL@10..11 "\n" [] [] + 1: (empty) + 1: MD_BULLET@11..22 + 0: MINUS@11..12 "-" [] [] + 1: MD_BLOCK_LIST@12..22 + 0: MD_PARAGRAPH@12..22 + 0: MD_INLINE_ITEM_LIST@12..22 + 0: MD_TEXTUAL@12..21 + 0: MD_TEXTUAL_LITERAL@12..21 " Item two" [] [] + 1: MD_TEXTUAL@21..22 + 0: MD_TEXTUAL_LITERAL@21..22 "\n" [] [] + 1: (empty) + 2: MD_BULLET@22..36 + 0: MINUS@22..23 "-" [] [] + 1: MD_BLOCK_LIST@23..36 + 0: MD_PARAGRAPH@23..35 + 0: MD_INLINE_ITEM_LIST@23..35 + 0: MD_TEXTUAL@23..34 + 0: MD_TEXTUAL_LITERAL@23..34 " Item three" [] [] + 1: MD_TEXTUAL@34..35 + 0: MD_TEXTUAL_LITERAL@34..35 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@35..36 + 0: NEWLINE@35..36 "\n" [] [] + 1: MD_BULLET_LIST_ITEM@36..73 + 0: MD_BULLET_LIST@36..73 + 0: MD_BULLET@36..57 + 0: STAR@36..37 "*" [] [] + 1: MD_BLOCK_LIST@37..57 + 0: MD_PARAGRAPH@37..57 + 0: MD_INLINE_ITEM_LIST@37..57 + 0: MD_TEXTUAL@37..56 + 0: MD_TEXTUAL_LITERAL@37..56 " Alternative marker" [] [] + 1: MD_TEXTUAL@56..57 + 0: MD_TEXTUAL_LITERAL@56..57 "\n" [] [] + 1: (empty) + 1: MD_BULLET@57..73 + 0: STAR@57..58 "*" [] [] + 1: MD_BLOCK_LIST@58..73 + 0: MD_PARAGRAPH@58..72 + 0: MD_INLINE_ITEM_LIST@58..72 + 0: MD_TEXTUAL@58..71 + 0: MD_TEXTUAL_LITERAL@58..71 " Another item" [] [] + 1: MD_TEXTUAL@71..72 + 0: MD_TEXTUAL_LITERAL@71..72 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@72..73 + 0: NEWLINE@72..73 "\n" [] [] + 2: MD_BULLET_LIST_ITEM@73..107 + 0: MD_BULLET_LIST@73..107 + 0: MD_BULLET@73..87 + 0: PLUS@73..74 "+" [] [] + 1: MD_BLOCK_LIST@74..87 + 0: MD_PARAGRAPH@74..87 + 0: MD_INLINE_ITEM_LIST@74..87 + 0: MD_TEXTUAL@74..86 + 0: MD_TEXTUAL_LITERAL@74..86 " Plus marker" [] [] + 1: MD_TEXTUAL@86..87 + 0: MD_TEXTUAL_LITERAL@86..87 "\n" [] [] + 1: (empty) + 1: MD_BULLET@87..107 + 0: PLUS@87..88 "+" [] [] + 1: MD_BLOCK_LIST@88..107 + 0: MD_PARAGRAPH@88..107 + 0: MD_INLINE_ITEM_LIST@88..107 + 0: MD_TEXTUAL@88..106 + 0: MD_TEXTUAL_LITERAL@88..106 " Another plus item" [] [] + 1: MD_TEXTUAL@106..107 + 0: MD_TEXTUAL_LITERAL@106..107 "\n" [] [] + 1: (empty) + 2: EOF@107..107 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/edge_cases.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/edge_cases.md new file mode 100644 index 000000000000..70e6f15306a0 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/edge_cases.md @@ -0,0 +1,30 @@ +#NoSpaceHeading + +##AlsoNoSpace + +foo --- bar is not a thematic break + +inline ~~~ tilde is not a fence + + *code with* inline markers + + more code **here** too + +# Tab after hash is valid heading + +## Multiple hashes with tab + +Paragraph here + # not heading due to 4+ spaces + - not list due to 4+ spaces + > not quote due to 4+ spaces +continues as paragraph + +Another para + # this IS a heading (only 3 spaces) + +Para with indented fence + ``` + not a code fence + ``` +still the same paragraph diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/edge_cases.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/edge_cases.md.snap new file mode 100644 index 000000000000..cde8ba7c6e29 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/edge_cases.md.snap @@ -0,0 +1,577 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +#NoSpaceHeading + +##AlsoNoSpace + +foo --- bar is not a thematic break + +inline ~~~ tilde is not a fence + + *code with* inline markers + + more code **here** too + +# Tab after hash is valid heading + +## Multiple hashes with tab + +Paragraph here + # not heading due to 4+ spaces + - not list due to 4+ spaces + > not quote due to 4+ spaces +continues as paragraph + +Another para + # this IS a heading (only 3 spaces) + +Para with indented fence + ``` + not a code fence + ``` +still the same paragraph + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..1 "#" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..15 "NoSpaceHeading" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..16 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@16..17 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@17..19 "##" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@19..30 "AlsoNoSpace" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@31..32 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..36 "foo " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@36..37 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..38 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@38..39 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@39..67 " bar is not a thematic break" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@67..68 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@68..69 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@69..76 "inline " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@76..79 "~~~" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@79..100 " tilde is not a fence" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@100..101 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@101..102 "\n" [] [], + }, + MdIndentCodeBlock { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@102..107 "*" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@107..116 "code with" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@116..117 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@117..132 " inline markers" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@132..133 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@133..134 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@134..148 "more code " [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@148..150 "**" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@150..154 "here" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@154..156 "**" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@156..160 " too" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@160..161 "\n" [] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@161..162 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@162..163 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@163..195 "\tTab after hash is valid heading" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@195..196 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@196..197 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@197..199 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@199..224 "\tMultiple hashes with tab" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@224..225 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@225..226 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@226..240 "Paragraph here" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@240..241 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@241..246 "#" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@246..267 " not heading due to 4" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@267..268 "+" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@268..275 " spaces" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@275..276 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@276..281 "-" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@281..299 " not list due to 4" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@299..300 "+" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@300..307 " spaces" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@307..308 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@308..313 ">" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@313..332 " not quote due to 4" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@332..333 "+" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@333..340 " spaces" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@340..341 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@341..363 "continues as paragraph" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@363..364 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@364..365 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@365..377 "Another para" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@377..378 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@378..382 "#" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@382..401 " this IS a heading " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@401..402 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@402..415 "only 3 spaces" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@415..416 ")" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@416..417 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@417..418 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@418..442 "Para with indented fence" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@442..443 "\n" [] [], + }, + MdInlineCode { + l_tick_token: BACKTICK@443..450 "```" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@450..451 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@451..471 " not a code fence" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@471..472 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@472..476 " " [] [], + }, + ], + r_tick_token: BACKTICK@476..479 "```" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@479..480 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@480..504 "still the same paragraph" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@504..505 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@505..505 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..505 + 0: (empty) + 1: MD_BLOCK_LIST@0..505 + 0: MD_PARAGRAPH@0..16 + 0: MD_INLINE_ITEM_LIST@0..16 + 0: MD_TEXTUAL@0..1 + 0: MD_TEXTUAL_LITERAL@0..1 "#" [] [] + 1: MD_TEXTUAL@1..15 + 0: MD_TEXTUAL_LITERAL@1..15 "NoSpaceHeading" [] [] + 2: MD_TEXTUAL@15..16 + 0: MD_TEXTUAL_LITERAL@15..16 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@16..17 + 0: NEWLINE@16..17 "\n" [] [] + 2: MD_PARAGRAPH@17..31 + 0: MD_INLINE_ITEM_LIST@17..31 + 0: MD_TEXTUAL@17..19 + 0: MD_TEXTUAL_LITERAL@17..19 "##" [] [] + 1: MD_TEXTUAL@19..30 + 0: MD_TEXTUAL_LITERAL@19..30 "AlsoNoSpace" [] [] + 2: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@31..32 + 0: NEWLINE@31..32 "\n" [] [] + 4: MD_PARAGRAPH@32..68 + 0: MD_INLINE_ITEM_LIST@32..68 + 0: MD_TEXTUAL@32..36 + 0: MD_TEXTUAL_LITERAL@32..36 "foo " [] [] + 1: MD_TEXTUAL@36..37 + 0: MD_TEXTUAL_LITERAL@36..37 "-" [] [] + 2: MD_TEXTUAL@37..38 + 0: MD_TEXTUAL_LITERAL@37..38 "-" [] [] + 3: MD_TEXTUAL@38..39 + 0: MD_TEXTUAL_LITERAL@38..39 "-" [] [] + 4: MD_TEXTUAL@39..67 + 0: MD_TEXTUAL_LITERAL@39..67 " bar is not a thematic break" [] [] + 5: MD_TEXTUAL@67..68 + 0: MD_TEXTUAL_LITERAL@67..68 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@68..69 + 0: NEWLINE@68..69 "\n" [] [] + 6: MD_PARAGRAPH@69..101 + 0: MD_INLINE_ITEM_LIST@69..101 + 0: MD_TEXTUAL@69..76 + 0: MD_TEXTUAL_LITERAL@69..76 "inline " [] [] + 1: MD_TEXTUAL@76..79 + 0: MD_TEXTUAL_LITERAL@76..79 "~~~" [] [] + 2: MD_TEXTUAL@79..100 + 0: MD_TEXTUAL_LITERAL@79..100 " tilde is not a fence" [] [] + 3: MD_TEXTUAL@100..101 + 0: MD_TEXTUAL_LITERAL@100..101 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@101..102 + 0: NEWLINE@101..102 "\n" [] [] + 8: MD_INDENT_CODE_BLOCK@102..161 + 0: MD_INLINE_ITEM_LIST@102..161 + 0: MD_TEXTUAL@102..107 + 0: MD_TEXTUAL_LITERAL@102..107 "*" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_TEXTUAL@107..116 + 0: MD_TEXTUAL_LITERAL@107..116 "code with" [] [] + 2: MD_TEXTUAL@116..117 + 0: MD_TEXTUAL_LITERAL@116..117 "*" [] [] + 3: MD_TEXTUAL@117..132 + 0: MD_TEXTUAL_LITERAL@117..132 " inline markers" [] [] + 4: MD_TEXTUAL@132..133 + 0: MD_TEXTUAL_LITERAL@132..133 "\n" [] [] + 5: MD_TEXTUAL@133..134 + 0: MD_TEXTUAL_LITERAL@133..134 "\n" [] [] + 6: MD_TEXTUAL@134..148 + 0: MD_TEXTUAL_LITERAL@134..148 "more code " [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 7: MD_TEXTUAL@148..150 + 0: MD_TEXTUAL_LITERAL@148..150 "**" [] [] + 8: MD_TEXTUAL@150..154 + 0: MD_TEXTUAL_LITERAL@150..154 "here" [] [] + 9: MD_TEXTUAL@154..156 + 0: MD_TEXTUAL_LITERAL@154..156 "**" [] [] + 10: MD_TEXTUAL@156..160 + 0: MD_TEXTUAL_LITERAL@156..160 " too" [] [] + 11: MD_TEXTUAL@160..161 + 0: MD_TEXTUAL_LITERAL@160..161 "\n" [] [] + 9: MD_NEWLINE@161..162 + 0: NEWLINE@161..162 "\n" [] [] + 10: MD_HEADER@162..195 + 0: MD_HASH_LIST@162..163 + 0: MD_HASH@162..163 + 0: HASH@162..163 "#" [] [] + 1: MD_PARAGRAPH@163..195 + 0: MD_INLINE_ITEM_LIST@163..195 + 0: MD_TEXTUAL@163..195 + 0: MD_TEXTUAL_LITERAL@163..195 "\tTab after hash is valid heading" [] [] + 1: (empty) + 2: MD_HASH_LIST@195..195 + 11: MD_NEWLINE@195..196 + 0: NEWLINE@195..196 "\n" [] [] + 12: MD_NEWLINE@196..197 + 0: NEWLINE@196..197 "\n" [] [] + 13: MD_HEADER@197..224 + 0: MD_HASH_LIST@197..199 + 0: MD_HASH@197..199 + 0: HASH@197..199 "##" [] [] + 1: MD_PARAGRAPH@199..224 + 0: MD_INLINE_ITEM_LIST@199..224 + 0: MD_TEXTUAL@199..224 + 0: MD_TEXTUAL_LITERAL@199..224 "\tMultiple hashes with tab" [] [] + 1: (empty) + 2: MD_HASH_LIST@224..224 + 14: MD_NEWLINE@224..225 + 0: NEWLINE@224..225 "\n" [] [] + 15: MD_NEWLINE@225..226 + 0: NEWLINE@225..226 "\n" [] [] + 16: MD_PARAGRAPH@226..364 + 0: MD_INLINE_ITEM_LIST@226..364 + 0: MD_TEXTUAL@226..240 + 0: MD_TEXTUAL_LITERAL@226..240 "Paragraph here" [] [] + 1: MD_TEXTUAL@240..241 + 0: MD_TEXTUAL_LITERAL@240..241 "\n" [] [] + 2: MD_TEXTUAL@241..246 + 0: MD_TEXTUAL_LITERAL@241..246 "#" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 3: MD_TEXTUAL@246..267 + 0: MD_TEXTUAL_LITERAL@246..267 " not heading due to 4" [] [] + 4: MD_TEXTUAL@267..268 + 0: MD_TEXTUAL_LITERAL@267..268 "+" [] [] + 5: MD_TEXTUAL@268..275 + 0: MD_TEXTUAL_LITERAL@268..275 " spaces" [] [] + 6: MD_TEXTUAL@275..276 + 0: MD_TEXTUAL_LITERAL@275..276 "\n" [] [] + 7: MD_TEXTUAL@276..281 + 0: MD_TEXTUAL_LITERAL@276..281 "-" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 8: MD_TEXTUAL@281..299 + 0: MD_TEXTUAL_LITERAL@281..299 " not list due to 4" [] [] + 9: MD_TEXTUAL@299..300 + 0: MD_TEXTUAL_LITERAL@299..300 "+" [] [] + 10: MD_TEXTUAL@300..307 + 0: MD_TEXTUAL_LITERAL@300..307 " spaces" [] [] + 11: MD_TEXTUAL@307..308 + 0: MD_TEXTUAL_LITERAL@307..308 "\n" [] [] + 12: MD_TEXTUAL@308..313 + 0: MD_TEXTUAL_LITERAL@308..313 ">" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 13: MD_TEXTUAL@313..332 + 0: MD_TEXTUAL_LITERAL@313..332 " not quote due to 4" [] [] + 14: MD_TEXTUAL@332..333 + 0: MD_TEXTUAL_LITERAL@332..333 "+" [] [] + 15: MD_TEXTUAL@333..340 + 0: MD_TEXTUAL_LITERAL@333..340 " spaces" [] [] + 16: MD_TEXTUAL@340..341 + 0: MD_TEXTUAL_LITERAL@340..341 "\n" [] [] + 17: MD_TEXTUAL@341..363 + 0: MD_TEXTUAL_LITERAL@341..363 "continues as paragraph" [] [] + 18: MD_TEXTUAL@363..364 + 0: MD_TEXTUAL_LITERAL@363..364 "\n" [] [] + 1: (empty) + 17: MD_NEWLINE@364..365 + 0: NEWLINE@364..365 "\n" [] [] + 18: MD_PARAGRAPH@365..378 + 0: MD_INLINE_ITEM_LIST@365..378 + 0: MD_TEXTUAL@365..377 + 0: MD_TEXTUAL_LITERAL@365..377 "Another para" [] [] + 1: MD_TEXTUAL@377..378 + 0: MD_TEXTUAL_LITERAL@377..378 "\n" [] [] + 1: (empty) + 19: MD_HEADER@378..416 + 0: MD_HASH_LIST@378..382 + 0: MD_HASH@378..382 + 0: HASH@378..382 "#" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_PARAGRAPH@382..416 + 0: MD_INLINE_ITEM_LIST@382..416 + 0: MD_TEXTUAL@382..401 + 0: MD_TEXTUAL_LITERAL@382..401 " this IS a heading " [] [] + 1: MD_TEXTUAL@401..402 + 0: MD_TEXTUAL_LITERAL@401..402 "(" [] [] + 2: MD_TEXTUAL@402..415 + 0: MD_TEXTUAL_LITERAL@402..415 "only 3 spaces" [] [] + 3: MD_TEXTUAL@415..416 + 0: MD_TEXTUAL_LITERAL@415..416 ")" [] [] + 1: (empty) + 2: MD_HASH_LIST@416..416 + 20: MD_NEWLINE@416..417 + 0: NEWLINE@416..417 "\n" [] [] + 21: MD_NEWLINE@417..418 + 0: NEWLINE@417..418 "\n" [] [] + 22: MD_PARAGRAPH@418..505 + 0: MD_INLINE_ITEM_LIST@418..505 + 0: MD_TEXTUAL@418..442 + 0: MD_TEXTUAL_LITERAL@418..442 "Para with indented fence" [] [] + 1: MD_TEXTUAL@442..443 + 0: MD_TEXTUAL_LITERAL@442..443 "\n" [] [] + 2: MD_INLINE_CODE@443..479 + 0: BACKTICK@443..450 "```" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_INLINE_ITEM_LIST@450..476 + 0: MD_TEXTUAL@450..451 + 0: MD_TEXTUAL_LITERAL@450..451 "\n" [] [] + 1: MD_TEXTUAL@451..471 + 0: MD_TEXTUAL_LITERAL@451..471 " not a code fence" [] [] + 2: MD_TEXTUAL@471..472 + 0: MD_TEXTUAL_LITERAL@471..472 "\n" [] [] + 3: MD_TEXTUAL@472..476 + 0: MD_TEXTUAL_LITERAL@472..476 " " [] [] + 2: BACKTICK@476..479 "```" [] [] + 3: MD_TEXTUAL@479..480 + 0: MD_TEXTUAL_LITERAL@479..480 "\n" [] [] + 4: MD_TEXTUAL@480..504 + 0: MD_TEXTUAL_LITERAL@480..504 "still the same paragraph" [] [] + 5: MD_TEXTUAL@504..505 + 0: MD_TEXTUAL_LITERAL@504..505 "\n" [] [] + 1: (empty) + 2: EOF@505..505 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_complex.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_complex.md new file mode 100644 index 000000000000..b813c0dad3f0 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_complex.md @@ -0,0 +1,13 @@ +Nested: **bold *and italic* text** + +Overlapping: *foo**bar**baz* + +Rule of 3: ***bold italic*** + +Multiple runs: *a **b** c* + +Not emphasis: * not * emphasis * + +Intraword: foo*bar*baz + +Intraword underscore: foo_bar_baz diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_complex.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_complex.md.snap new file mode 100644 index 000000000000..a232804e6cd1 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_complex.md.snap @@ -0,0 +1,396 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Nested: **bold *and italic* text** + +Overlapping: *foo**bar**baz* + +Rule of 3: ***bold italic*** + +Multiple runs: *a **b** c* + +Not emphasis: * not * emphasis * + +Intraword: foo*bar*baz + +Intraword underscore: foo_bar_baz + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..8 "Nested: " [] [], + }, + MdInlineEmphasis { + l_fence: DOUBLE_STAR@8..10 "**" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..15 "bold " [] [], + }, + MdInlineItalic { + l_fence: STAR@15..16 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..26 "and italic" [] [], + }, + ], + r_fence: STAR@26..27 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@27..32 " text" [] [], + }, + ], + r_fence: DOUBLE_STAR@32..34 "**" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@34..35 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@35..36 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@36..49 "Overlapping: " [] [], + }, + MdInlineItalic { + l_fence: STAR@49..50 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@50..53 "foo" [] [], + }, + MdInlineEmphasis { + l_fence: DOUBLE_STAR@53..55 "**" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@55..58 "bar" [] [], + }, + ], + r_fence: DOUBLE_STAR@58..60 "**" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@60..63 "baz" [] [], + }, + ], + r_fence: STAR@63..64 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@64..65 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@65..66 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@66..77 "Rule of 3: " [] [], + }, + MdInlineItalic { + l_fence: STAR@77..78 "*" [] [], + content: MdInlineItemList [ + MdInlineEmphasis { + l_fence: DOUBLE_STAR@78..80 "**" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..91 "bold italic" [] [], + }, + ], + r_fence: DOUBLE_STAR@91..93 "**" [] [], + }, + ], + r_fence: STAR@93..94 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@94..95 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@95..96 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@96..111 "Multiple runs: " [] [], + }, + MdInlineItalic { + l_fence: STAR@111..112 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@112..114 "a " [] [], + }, + MdInlineEmphasis { + l_fence: DOUBLE_STAR@114..116 "**" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@116..117 "b" [] [], + }, + ], + r_fence: DOUBLE_STAR@117..119 "**" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@119..121 " c" [] [], + }, + ], + r_fence: STAR@121..122 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@122..123 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@123..124 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@124..138 "Not emphasis: " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@138..139 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@139..144 " not " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@144..145 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@145..155 " emphasis " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@155..156 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@156..157 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@157..158 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@158..172 "Intraword: foo" [] [], + }, + MdInlineItalic { + l_fence: STAR@172..173 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@173..176 "bar" [] [], + }, + ], + r_fence: STAR@176..177 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@177..180 "baz" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@180..181 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@181..182 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@182..207 "Intraword underscore: foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@207..208 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@208..211 "bar" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@211..212 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@212..215 "baz" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@215..216 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@216..216 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..216 + 0: (empty) + 1: MD_BLOCK_LIST@0..216 + 0: MD_PARAGRAPH@0..35 + 0: MD_INLINE_ITEM_LIST@0..35 + 0: MD_TEXTUAL@0..8 + 0: MD_TEXTUAL_LITERAL@0..8 "Nested: " [] [] + 1: MD_INLINE_EMPHASIS@8..34 + 0: DOUBLE_STAR@8..10 "**" [] [] + 1: MD_INLINE_ITEM_LIST@10..32 + 0: MD_TEXTUAL@10..15 + 0: MD_TEXTUAL_LITERAL@10..15 "bold " [] [] + 1: MD_INLINE_ITALIC@15..27 + 0: STAR@15..16 "*" [] [] + 1: MD_INLINE_ITEM_LIST@16..26 + 0: MD_TEXTUAL@16..26 + 0: MD_TEXTUAL_LITERAL@16..26 "and italic" [] [] + 2: STAR@26..27 "*" [] [] + 2: MD_TEXTUAL@27..32 + 0: MD_TEXTUAL_LITERAL@27..32 " text" [] [] + 2: DOUBLE_STAR@32..34 "**" [] [] + 2: MD_TEXTUAL@34..35 + 0: MD_TEXTUAL_LITERAL@34..35 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@35..36 + 0: NEWLINE@35..36 "\n" [] [] + 2: MD_PARAGRAPH@36..65 + 0: MD_INLINE_ITEM_LIST@36..65 + 0: MD_TEXTUAL@36..49 + 0: MD_TEXTUAL_LITERAL@36..49 "Overlapping: " [] [] + 1: MD_INLINE_ITALIC@49..64 + 0: STAR@49..50 "*" [] [] + 1: MD_INLINE_ITEM_LIST@50..63 + 0: MD_TEXTUAL@50..53 + 0: MD_TEXTUAL_LITERAL@50..53 "foo" [] [] + 1: MD_INLINE_EMPHASIS@53..60 + 0: DOUBLE_STAR@53..55 "**" [] [] + 1: MD_INLINE_ITEM_LIST@55..58 + 0: MD_TEXTUAL@55..58 + 0: MD_TEXTUAL_LITERAL@55..58 "bar" [] [] + 2: DOUBLE_STAR@58..60 "**" [] [] + 2: MD_TEXTUAL@60..63 + 0: MD_TEXTUAL_LITERAL@60..63 "baz" [] [] + 2: STAR@63..64 "*" [] [] + 2: MD_TEXTUAL@64..65 + 0: MD_TEXTUAL_LITERAL@64..65 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@65..66 + 0: NEWLINE@65..66 "\n" [] [] + 4: MD_PARAGRAPH@66..95 + 0: MD_INLINE_ITEM_LIST@66..95 + 0: MD_TEXTUAL@66..77 + 0: MD_TEXTUAL_LITERAL@66..77 "Rule of 3: " [] [] + 1: MD_INLINE_ITALIC@77..94 + 0: STAR@77..78 "*" [] [] + 1: MD_INLINE_ITEM_LIST@78..93 + 0: MD_INLINE_EMPHASIS@78..93 + 0: DOUBLE_STAR@78..80 "**" [] [] + 1: MD_INLINE_ITEM_LIST@80..91 + 0: MD_TEXTUAL@80..91 + 0: MD_TEXTUAL_LITERAL@80..91 "bold italic" [] [] + 2: DOUBLE_STAR@91..93 "**" [] [] + 2: STAR@93..94 "*" [] [] + 2: MD_TEXTUAL@94..95 + 0: MD_TEXTUAL_LITERAL@94..95 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@95..96 + 0: NEWLINE@95..96 "\n" [] [] + 6: MD_PARAGRAPH@96..123 + 0: MD_INLINE_ITEM_LIST@96..123 + 0: MD_TEXTUAL@96..111 + 0: MD_TEXTUAL_LITERAL@96..111 "Multiple runs: " [] [] + 1: MD_INLINE_ITALIC@111..122 + 0: STAR@111..112 "*" [] [] + 1: MD_INLINE_ITEM_LIST@112..121 + 0: MD_TEXTUAL@112..114 + 0: MD_TEXTUAL_LITERAL@112..114 "a " [] [] + 1: MD_INLINE_EMPHASIS@114..119 + 0: DOUBLE_STAR@114..116 "**" [] [] + 1: MD_INLINE_ITEM_LIST@116..117 + 0: MD_TEXTUAL@116..117 + 0: MD_TEXTUAL_LITERAL@116..117 "b" [] [] + 2: DOUBLE_STAR@117..119 "**" [] [] + 2: MD_TEXTUAL@119..121 + 0: MD_TEXTUAL_LITERAL@119..121 " c" [] [] + 2: STAR@121..122 "*" [] [] + 2: MD_TEXTUAL@122..123 + 0: MD_TEXTUAL_LITERAL@122..123 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@123..124 + 0: NEWLINE@123..124 "\n" [] [] + 8: MD_PARAGRAPH@124..157 + 0: MD_INLINE_ITEM_LIST@124..157 + 0: MD_TEXTUAL@124..138 + 0: MD_TEXTUAL_LITERAL@124..138 "Not emphasis: " [] [] + 1: MD_TEXTUAL@138..139 + 0: MD_TEXTUAL_LITERAL@138..139 "*" [] [] + 2: MD_TEXTUAL@139..144 + 0: MD_TEXTUAL_LITERAL@139..144 " not " [] [] + 3: MD_TEXTUAL@144..145 + 0: MD_TEXTUAL_LITERAL@144..145 "*" [] [] + 4: MD_TEXTUAL@145..155 + 0: MD_TEXTUAL_LITERAL@145..155 " emphasis " [] [] + 5: MD_TEXTUAL@155..156 + 0: MD_TEXTUAL_LITERAL@155..156 "*" [] [] + 6: MD_TEXTUAL@156..157 + 0: MD_TEXTUAL_LITERAL@156..157 "\n" [] [] + 1: (empty) + 9: MD_NEWLINE@157..158 + 0: NEWLINE@157..158 "\n" [] [] + 10: MD_PARAGRAPH@158..181 + 0: MD_INLINE_ITEM_LIST@158..181 + 0: MD_TEXTUAL@158..172 + 0: MD_TEXTUAL_LITERAL@158..172 "Intraword: foo" [] [] + 1: MD_INLINE_ITALIC@172..177 + 0: STAR@172..173 "*" [] [] + 1: MD_INLINE_ITEM_LIST@173..176 + 0: MD_TEXTUAL@173..176 + 0: MD_TEXTUAL_LITERAL@173..176 "bar" [] [] + 2: STAR@176..177 "*" [] [] + 2: MD_TEXTUAL@177..180 + 0: MD_TEXTUAL_LITERAL@177..180 "baz" [] [] + 3: MD_TEXTUAL@180..181 + 0: MD_TEXTUAL_LITERAL@180..181 "\n" [] [] + 1: (empty) + 11: MD_NEWLINE@181..182 + 0: NEWLINE@181..182 "\n" [] [] + 12: MD_PARAGRAPH@182..216 + 0: MD_INLINE_ITEM_LIST@182..216 + 0: MD_TEXTUAL@182..207 + 0: MD_TEXTUAL_LITERAL@182..207 "Intraword underscore: foo" [] [] + 1: MD_TEXTUAL@207..208 + 0: MD_TEXTUAL_LITERAL@207..208 "_" [] [] + 2: MD_TEXTUAL@208..211 + 0: MD_TEXTUAL_LITERAL@208..211 "bar" [] [] + 3: MD_TEXTUAL@211..212 + 0: MD_TEXTUAL_LITERAL@211..212 "_" [] [] + 4: MD_TEXTUAL@212..215 + 0: MD_TEXTUAL_LITERAL@212..215 "baz" [] [] + 5: MD_TEXTUAL@215..216 + 0: MD_TEXTUAL_LITERAL@215..216 "\n" [] [] + 1: (empty) + 2: EOF@216..216 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_crossing.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_crossing.md new file mode 100644 index 000000000000..3cfca8723433 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_crossing.md @@ -0,0 +1 @@ +Crossing: **a *b** c* diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_crossing.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_crossing.md.snap new file mode 100644 index 000000000000..0eb8d238c8ca --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_crossing.md.snap @@ -0,0 +1,96 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Crossing: **a *b** c* + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..10 "Crossing: " [] [], + }, + MdInlineItalic { + l_fence: STAR@10..11 "*" [] [], + content: MdInlineItemList [ + MdInlineItalic { + l_fence: STAR@11..12 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@12..14 "a " [] [], + }, + MdInlineItalic { + l_fence: STAR@14..15 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..16 "b" [] [], + }, + ], + r_fence: STAR@16..17 "*" [] [], + }, + ], + r_fence: STAR@17..18 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@18..20 " c" [] [], + }, + ], + r_fence: STAR@20..21 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..22 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@22..22 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..22 + 0: (empty) + 1: MD_BLOCK_LIST@0..22 + 0: MD_PARAGRAPH@0..22 + 0: MD_INLINE_ITEM_LIST@0..22 + 0: MD_TEXTUAL@0..10 + 0: MD_TEXTUAL_LITERAL@0..10 "Crossing: " [] [] + 1: MD_INLINE_ITALIC@10..21 + 0: STAR@10..11 "*" [] [] + 1: MD_INLINE_ITEM_LIST@11..20 + 0: MD_INLINE_ITALIC@11..18 + 0: STAR@11..12 "*" [] [] + 1: MD_INLINE_ITEM_LIST@12..17 + 0: MD_TEXTUAL@12..14 + 0: MD_TEXTUAL_LITERAL@12..14 "a " [] [] + 1: MD_INLINE_ITALIC@14..17 + 0: STAR@14..15 "*" [] [] + 1: MD_INLINE_ITEM_LIST@15..16 + 0: MD_TEXTUAL@15..16 + 0: MD_TEXTUAL_LITERAL@15..16 "b" [] [] + 2: STAR@16..17 "*" [] [] + 2: STAR@17..18 "*" [] [] + 1: MD_TEXTUAL@18..20 + 0: MD_TEXTUAL_LITERAL@18..20 " c" [] [] + 2: STAR@20..21 "*" [] [] + 2: MD_TEXTUAL@21..22 + 0: MD_TEXTUAL_LITERAL@21..22 "\n" [] [] + 1: (empty) + 2: EOF@22..22 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_edge_cases.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_edge_cases.md new file mode 100644 index 000000000000..b22601f3ad7c --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_edge_cases.md @@ -0,0 +1,17 @@ +Code spans block emphasis: `*not emphasis*` + +Escaped asterisks: \*not emphasis\* + +HTML with asterisks: *between tags* + +HTML attribute: attr + +Autolink: + +Link text: [*emphasis*](https://example.com) + +Reference link text: [*emphasis*][label] + +Image alt: ![*emphasis*][label] + +[label]: https://example.com diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_edge_cases.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_edge_cases.md.snap new file mode 100644 index 000000000000..86f83db91dd9 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_edge_cases.md.snap @@ -0,0 +1,601 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Code spans block emphasis: `*not emphasis*` + +Escaped asterisks: \*not emphasis\* + +HTML with asterisks: *between tags* + +HTML attribute: attr + +Autolink: + +Link text: [*emphasis*](https://example.com) + +Reference link text: [*emphasis*][label] + +Image alt: ![*emphasis*][label] + +[label]: https://example.com + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..27 "Code spans block emphasis: " [] [], + }, + MdInlineCode { + l_tick_token: BACKTICK@27..28 "`" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..29 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..41 "not emphasis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@41..42 "*" [] [], + }, + ], + r_tick_token: BACKTICK@42..43 "`" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@43..44 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@44..45 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@45..64 "Escaped asterisks: " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@64..66 "\\*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@66..78 "not emphasis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@78..80 "\\*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..81 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@81..82 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@82..103 "HTML with asterisks: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@103..104 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@104..108 "span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@108..109 ">" [] [], + }, + ], + }, + MdInlineItalic { + l_fence: STAR@109..110 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@110..122 "between tags" [] [], + }, + ], + r_fence: STAR@122..123 "*" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@123..124 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@124..129 "/span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@129..130 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@130..131 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@131..132 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@132..148 "HTML attribute: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@148..149 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@149..161 "span title=\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@161..162 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@162..174 "not emphasis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@174..175 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@175..176 "\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@176..177 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@177..181 "attr" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@181..182 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@182..187 "/span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@187..188 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@188..189 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@189..190 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@190..200 "Autolink: " [] [], + }, + MdAutolink { + l_angle_token: L_ANGLE@200..201 "<" [] [], + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@201..221 "https://example.com/" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@221..222 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@222..225 "not" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@225..226 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@226..234 "emphasis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@234..235 "*" [] [], + }, + ], + r_angle_token: R_ANGLE@235..236 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@236..237 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@237..238 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@238..249 "Link text: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@249..250 "[" [] [], + text: MdInlineItemList [ + MdInlineItalic { + l_fence: STAR@250..251 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@251..259 "emphasis" [] [], + }, + ], + r_fence: STAR@259..260 "*" [] [], + }, + ], + r_brack_token: R_BRACK@260..261 "]" [] [], + l_paren_token: L_PAREN@261..262 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@262..281 "https://example.com" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@281..282 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@282..283 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@283..284 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@284..305 "Reference link text: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@305..306 "[" [] [], + text: MdInlineItemList [ + MdInlineItalic { + l_fence: STAR@306..307 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@307..315 "emphasis" [] [], + }, + ], + r_fence: STAR@315..316 "*" [] [], + }, + ], + r_brack_token: R_BRACK@316..317 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@317..318 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@318..323 "label" [] [], + }, + ], + r_brack_token: R_BRACK@323..324 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@324..325 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@325..326 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@326..337 "Image alt: " [] [], + }, + MdReferenceImage { + excl_token: BANG@337..338 "!" [] [], + l_brack_token: L_BRACK@338..339 "[" [] [], + alt: MdInlineItemList [ + MdInlineItalic { + l_fence: STAR@339..340 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@340..348 "emphasis" [] [], + }, + ], + r_fence: STAR@348..349 "*" [] [], + }, + ], + r_brack_token: R_BRACK@349..350 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@350..351 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@351..356 "label" [] [], + }, + ], + r_brack_token: R_BRACK@356..357 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@357..358 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@358..359 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@359..360 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@360..365 "label" [] [], + }, + ], + }, + r_brack_token: R_BRACK@365..366 "]" [] [], + colon_token: COLON@366..367 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@367..368 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@368..387 "https://example.com" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@387..388 "\n" [] [], + }, + ], + eof_token: EOF@388..388 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..388 + 0: (empty) + 1: MD_BLOCK_LIST@0..388 + 0: MD_PARAGRAPH@0..44 + 0: MD_INLINE_ITEM_LIST@0..44 + 0: MD_TEXTUAL@0..27 + 0: MD_TEXTUAL_LITERAL@0..27 "Code spans block emphasis: " [] [] + 1: MD_INLINE_CODE@27..43 + 0: BACKTICK@27..28 "`" [] [] + 1: MD_INLINE_ITEM_LIST@28..42 + 0: MD_TEXTUAL@28..29 + 0: MD_TEXTUAL_LITERAL@28..29 "*" [] [] + 1: MD_TEXTUAL@29..41 + 0: MD_TEXTUAL_LITERAL@29..41 "not emphasis" [] [] + 2: MD_TEXTUAL@41..42 + 0: MD_TEXTUAL_LITERAL@41..42 "*" [] [] + 2: BACKTICK@42..43 "`" [] [] + 2: MD_TEXTUAL@43..44 + 0: MD_TEXTUAL_LITERAL@43..44 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@44..45 + 0: NEWLINE@44..45 "\n" [] [] + 2: MD_PARAGRAPH@45..81 + 0: MD_INLINE_ITEM_LIST@45..81 + 0: MD_TEXTUAL@45..64 + 0: MD_TEXTUAL_LITERAL@45..64 "Escaped asterisks: " [] [] + 1: MD_TEXTUAL@64..66 + 0: MD_TEXTUAL_LITERAL@64..66 "\\*" [] [] + 2: MD_TEXTUAL@66..78 + 0: MD_TEXTUAL_LITERAL@66..78 "not emphasis" [] [] + 3: MD_TEXTUAL@78..80 + 0: MD_TEXTUAL_LITERAL@78..80 "\\*" [] [] + 4: MD_TEXTUAL@80..81 + 0: MD_TEXTUAL_LITERAL@80..81 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@81..82 + 0: NEWLINE@81..82 "\n" [] [] + 4: MD_PARAGRAPH@82..131 + 0: MD_INLINE_ITEM_LIST@82..131 + 0: MD_TEXTUAL@82..103 + 0: MD_TEXTUAL_LITERAL@82..103 "HTML with asterisks: " [] [] + 1: MD_INLINE_HTML@103..109 + 0: MD_INLINE_ITEM_LIST@103..109 + 0: MD_TEXTUAL@103..104 + 0: MD_TEXTUAL_LITERAL@103..104 "<" [] [] + 1: MD_TEXTUAL@104..108 + 0: MD_TEXTUAL_LITERAL@104..108 "span" [] [] + 2: MD_TEXTUAL@108..109 + 0: MD_TEXTUAL_LITERAL@108..109 ">" [] [] + 2: MD_INLINE_ITALIC@109..123 + 0: STAR@109..110 "*" [] [] + 1: MD_INLINE_ITEM_LIST@110..122 + 0: MD_TEXTUAL@110..122 + 0: MD_TEXTUAL_LITERAL@110..122 "between tags" [] [] + 2: STAR@122..123 "*" [] [] + 3: MD_INLINE_HTML@123..130 + 0: MD_INLINE_ITEM_LIST@123..130 + 0: MD_TEXTUAL@123..124 + 0: MD_TEXTUAL_LITERAL@123..124 "<" [] [] + 1: MD_TEXTUAL@124..129 + 0: MD_TEXTUAL_LITERAL@124..129 "/span" [] [] + 2: MD_TEXTUAL@129..130 + 0: MD_TEXTUAL_LITERAL@129..130 ">" [] [] + 4: MD_TEXTUAL@130..131 + 0: MD_TEXTUAL_LITERAL@130..131 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@131..132 + 0: NEWLINE@131..132 "\n" [] [] + 6: MD_PARAGRAPH@132..189 + 0: MD_INLINE_ITEM_LIST@132..189 + 0: MD_TEXTUAL@132..148 + 0: MD_TEXTUAL_LITERAL@132..148 "HTML attribute: " [] [] + 1: MD_INLINE_HTML@148..177 + 0: MD_INLINE_ITEM_LIST@148..177 + 0: MD_TEXTUAL@148..149 + 0: MD_TEXTUAL_LITERAL@148..149 "<" [] [] + 1: MD_TEXTUAL@149..161 + 0: MD_TEXTUAL_LITERAL@149..161 "span title=\"" [] [] + 2: MD_TEXTUAL@161..162 + 0: MD_TEXTUAL_LITERAL@161..162 "*" [] [] + 3: MD_TEXTUAL@162..174 + 0: MD_TEXTUAL_LITERAL@162..174 "not emphasis" [] [] + 4: MD_TEXTUAL@174..175 + 0: MD_TEXTUAL_LITERAL@174..175 "*" [] [] + 5: MD_TEXTUAL@175..176 + 0: MD_TEXTUAL_LITERAL@175..176 "\"" [] [] + 6: MD_TEXTUAL@176..177 + 0: MD_TEXTUAL_LITERAL@176..177 ">" [] [] + 2: MD_TEXTUAL@177..181 + 0: MD_TEXTUAL_LITERAL@177..181 "attr" [] [] + 3: MD_INLINE_HTML@181..188 + 0: MD_INLINE_ITEM_LIST@181..188 + 0: MD_TEXTUAL@181..182 + 0: MD_TEXTUAL_LITERAL@181..182 "<" [] [] + 1: MD_TEXTUAL@182..187 + 0: MD_TEXTUAL_LITERAL@182..187 "/span" [] [] + 2: MD_TEXTUAL@187..188 + 0: MD_TEXTUAL_LITERAL@187..188 ">" [] [] + 4: MD_TEXTUAL@188..189 + 0: MD_TEXTUAL_LITERAL@188..189 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@189..190 + 0: NEWLINE@189..190 "\n" [] [] + 8: MD_PARAGRAPH@190..237 + 0: MD_INLINE_ITEM_LIST@190..237 + 0: MD_TEXTUAL@190..200 + 0: MD_TEXTUAL_LITERAL@190..200 "Autolink: " [] [] + 1: MD_AUTOLINK@200..236 + 0: L_ANGLE@200..201 "<" [] [] + 1: MD_INLINE_ITEM_LIST@201..235 + 0: MD_TEXTUAL@201..221 + 0: MD_TEXTUAL_LITERAL@201..221 "https://example.com/" [] [] + 1: MD_TEXTUAL@221..222 + 0: MD_TEXTUAL_LITERAL@221..222 "*" [] [] + 2: MD_TEXTUAL@222..225 + 0: MD_TEXTUAL_LITERAL@222..225 "not" [] [] + 3: MD_TEXTUAL@225..226 + 0: MD_TEXTUAL_LITERAL@225..226 "-" [] [] + 4: MD_TEXTUAL@226..234 + 0: MD_TEXTUAL_LITERAL@226..234 "emphasis" [] [] + 5: MD_TEXTUAL@234..235 + 0: MD_TEXTUAL_LITERAL@234..235 "*" [] [] + 2: R_ANGLE@235..236 ">" [] [] + 2: MD_TEXTUAL@236..237 + 0: MD_TEXTUAL_LITERAL@236..237 "\n" [] [] + 1: (empty) + 9: MD_NEWLINE@237..238 + 0: NEWLINE@237..238 "\n" [] [] + 10: MD_PARAGRAPH@238..283 + 0: MD_INLINE_ITEM_LIST@238..283 + 0: MD_TEXTUAL@238..249 + 0: MD_TEXTUAL_LITERAL@238..249 "Link text: " [] [] + 1: MD_INLINE_LINK@249..282 + 0: L_BRACK@249..250 "[" [] [] + 1: MD_INLINE_ITEM_LIST@250..260 + 0: MD_INLINE_ITALIC@250..260 + 0: STAR@250..251 "*" [] [] + 1: MD_INLINE_ITEM_LIST@251..259 + 0: MD_TEXTUAL@251..259 + 0: MD_TEXTUAL_LITERAL@251..259 "emphasis" [] [] + 2: STAR@259..260 "*" [] [] + 2: R_BRACK@260..261 "]" [] [] + 3: L_PAREN@261..262 "(" [] [] + 4: MD_INLINE_ITEM_LIST@262..281 + 0: MD_TEXTUAL@262..281 + 0: MD_TEXTUAL_LITERAL@262..281 "https://example.com" [] [] + 5: (empty) + 6: R_PAREN@281..282 ")" [] [] + 2: MD_TEXTUAL@282..283 + 0: MD_TEXTUAL_LITERAL@282..283 "\n" [] [] + 1: (empty) + 11: MD_NEWLINE@283..284 + 0: NEWLINE@283..284 "\n" [] [] + 12: MD_PARAGRAPH@284..325 + 0: MD_INLINE_ITEM_LIST@284..325 + 0: MD_TEXTUAL@284..305 + 0: MD_TEXTUAL_LITERAL@284..305 "Reference link text: " [] [] + 1: MD_REFERENCE_LINK@305..324 + 0: L_BRACK@305..306 "[" [] [] + 1: MD_INLINE_ITEM_LIST@306..316 + 0: MD_INLINE_ITALIC@306..316 + 0: STAR@306..307 "*" [] [] + 1: MD_INLINE_ITEM_LIST@307..315 + 0: MD_TEXTUAL@307..315 + 0: MD_TEXTUAL_LITERAL@307..315 "emphasis" [] [] + 2: STAR@315..316 "*" [] [] + 2: R_BRACK@316..317 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@317..324 + 0: L_BRACK@317..318 "[" [] [] + 1: MD_INLINE_ITEM_LIST@318..323 + 0: MD_TEXTUAL@318..323 + 0: MD_TEXTUAL_LITERAL@318..323 "label" [] [] + 2: R_BRACK@323..324 "]" [] [] + 2: MD_TEXTUAL@324..325 + 0: MD_TEXTUAL_LITERAL@324..325 "\n" [] [] + 1: (empty) + 13: MD_NEWLINE@325..326 + 0: NEWLINE@325..326 "\n" [] [] + 14: MD_PARAGRAPH@326..358 + 0: MD_INLINE_ITEM_LIST@326..358 + 0: MD_TEXTUAL@326..337 + 0: MD_TEXTUAL_LITERAL@326..337 "Image alt: " [] [] + 1: MD_REFERENCE_IMAGE@337..357 + 0: BANG@337..338 "!" [] [] + 1: L_BRACK@338..339 "[" [] [] + 2: MD_INLINE_ITEM_LIST@339..349 + 0: MD_INLINE_ITALIC@339..349 + 0: STAR@339..340 "*" [] [] + 1: MD_INLINE_ITEM_LIST@340..348 + 0: MD_TEXTUAL@340..348 + 0: MD_TEXTUAL_LITERAL@340..348 "emphasis" [] [] + 2: STAR@348..349 "*" [] [] + 3: R_BRACK@349..350 "]" [] [] + 4: MD_REFERENCE_LINK_LABEL@350..357 + 0: L_BRACK@350..351 "[" [] [] + 1: MD_INLINE_ITEM_LIST@351..356 + 0: MD_TEXTUAL@351..356 + 0: MD_TEXTUAL_LITERAL@351..356 "label" [] [] + 2: R_BRACK@356..357 "]" [] [] + 2: MD_TEXTUAL@357..358 + 0: MD_TEXTUAL_LITERAL@357..358 "\n" [] [] + 1: (empty) + 15: MD_NEWLINE@358..359 + 0: NEWLINE@358..359 "\n" [] [] + 16: MD_LINK_REFERENCE_DEFINITION@359..387 + 0: L_BRACK@359..360 "[" [] [] + 1: MD_LINK_LABEL@360..365 + 0: MD_INLINE_ITEM_LIST@360..365 + 0: MD_TEXTUAL@360..365 + 0: MD_TEXTUAL_LITERAL@360..365 "label" [] [] + 2: R_BRACK@365..366 "]" [] [] + 3: COLON@366..367 ":" [] [] + 4: MD_LINK_DESTINATION@367..387 + 0: MD_INLINE_ITEM_LIST@367..387 + 0: MD_TEXTUAL@367..368 + 0: MD_TEXTUAL_LITERAL@367..368 " " [] [] + 1: MD_TEXTUAL@368..387 + 0: MD_TEXTUAL_LITERAL@368..387 "https://example.com" [] [] + 5: (empty) + 17: MD_NEWLINE@387..388 + 0: NEWLINE@387..388 "\n" [] [] + 2: EOF@388..388 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_flanking.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_flanking.md new file mode 100644 index 000000000000..7932e8867517 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_flanking.md @@ -0,0 +1,11 @@ +Valid emphasis: *italic* and **bold** + +Valid underscore: _italic_ and __bold__ + +Asterisk after space: foo * bar* should not be emphasis + +Underscore intraword: foo_bar_baz (not emphasis) + +Valid underscore at word boundary: _word_ + +Mixed: foo *bar* baz diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_flanking.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_flanking.md.snap new file mode 100644 index 000000000000..f2d04b523405 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_flanking.md.snap @@ -0,0 +1,336 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Valid emphasis: *italic* and **bold** + +Valid underscore: _italic_ and __bold__ + +Asterisk after space: foo * bar* should not be emphasis + +Underscore intraword: foo_bar_baz (not emphasis) + +Valid underscore at word boundary: _word_ + +Mixed: foo *bar* baz + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..16 "Valid emphasis: " [] [], + }, + MdInlineItalic { + l_fence: STAR@16..17 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@17..23 "italic" [] [], + }, + ], + r_fence: STAR@23..24 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..29 " and " [] [], + }, + MdInlineEmphasis { + l_fence: DOUBLE_STAR@29..31 "**" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..35 "bold" [] [], + }, + ], + r_fence: DOUBLE_STAR@35..37 "**" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..38 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@38..39 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@39..57 "Valid underscore: " [] [], + }, + MdInlineItalic { + l_fence: UNDERSCORE@57..58 "_" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..64 "italic" [] [], + }, + ], + r_fence: UNDERSCORE@64..65 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@65..70 " and " [] [], + }, + MdInlineEmphasis { + l_fence: DOUBLE_UNDERSCORE@70..72 "__" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@72..76 "bold" [] [], + }, + ], + r_fence: DOUBLE_UNDERSCORE@76..78 "__" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@78..79 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@79..80 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..106 "Asterisk after space: foo " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@106..107 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@107..111 " bar" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@111..112 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@112..135 " should not be emphasis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@135..136 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@136..137 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@137..162 "Underscore intraword: foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@162..163 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@163..166 "bar" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@166..167 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@167..171 "baz " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@171..172 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@172..184 "not emphasis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@184..185 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@185..186 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@186..187 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@187..222 "Valid underscore at word boundary: " [] [], + }, + MdInlineItalic { + l_fence: UNDERSCORE@222..223 "_" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@223..227 "word" [] [], + }, + ], + r_fence: UNDERSCORE@227..228 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@228..229 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@229..230 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@230..241 "Mixed: foo " [] [], + }, + MdInlineItalic { + l_fence: STAR@241..242 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@242..245 "bar" [] [], + }, + ], + r_fence: STAR@245..246 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@246..250 " baz" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@250..251 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@251..251 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..251 + 0: (empty) + 1: MD_BLOCK_LIST@0..251 + 0: MD_PARAGRAPH@0..38 + 0: MD_INLINE_ITEM_LIST@0..38 + 0: MD_TEXTUAL@0..16 + 0: MD_TEXTUAL_LITERAL@0..16 "Valid emphasis: " [] [] + 1: MD_INLINE_ITALIC@16..24 + 0: STAR@16..17 "*" [] [] + 1: MD_INLINE_ITEM_LIST@17..23 + 0: MD_TEXTUAL@17..23 + 0: MD_TEXTUAL_LITERAL@17..23 "italic" [] [] + 2: STAR@23..24 "*" [] [] + 2: MD_TEXTUAL@24..29 + 0: MD_TEXTUAL_LITERAL@24..29 " and " [] [] + 3: MD_INLINE_EMPHASIS@29..37 + 0: DOUBLE_STAR@29..31 "**" [] [] + 1: MD_INLINE_ITEM_LIST@31..35 + 0: MD_TEXTUAL@31..35 + 0: MD_TEXTUAL_LITERAL@31..35 "bold" [] [] + 2: DOUBLE_STAR@35..37 "**" [] [] + 4: MD_TEXTUAL@37..38 + 0: MD_TEXTUAL_LITERAL@37..38 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@38..39 + 0: NEWLINE@38..39 "\n" [] [] + 2: MD_PARAGRAPH@39..79 + 0: MD_INLINE_ITEM_LIST@39..79 + 0: MD_TEXTUAL@39..57 + 0: MD_TEXTUAL_LITERAL@39..57 "Valid underscore: " [] [] + 1: MD_INLINE_ITALIC@57..65 + 0: UNDERSCORE@57..58 "_" [] [] + 1: MD_INLINE_ITEM_LIST@58..64 + 0: MD_TEXTUAL@58..64 + 0: MD_TEXTUAL_LITERAL@58..64 "italic" [] [] + 2: UNDERSCORE@64..65 "_" [] [] + 2: MD_TEXTUAL@65..70 + 0: MD_TEXTUAL_LITERAL@65..70 " and " [] [] + 3: MD_INLINE_EMPHASIS@70..78 + 0: DOUBLE_UNDERSCORE@70..72 "__" [] [] + 1: MD_INLINE_ITEM_LIST@72..76 + 0: MD_TEXTUAL@72..76 + 0: MD_TEXTUAL_LITERAL@72..76 "bold" [] [] + 2: DOUBLE_UNDERSCORE@76..78 "__" [] [] + 4: MD_TEXTUAL@78..79 + 0: MD_TEXTUAL_LITERAL@78..79 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@79..80 + 0: NEWLINE@79..80 "\n" [] [] + 4: MD_PARAGRAPH@80..136 + 0: MD_INLINE_ITEM_LIST@80..136 + 0: MD_TEXTUAL@80..106 + 0: MD_TEXTUAL_LITERAL@80..106 "Asterisk after space: foo " [] [] + 1: MD_TEXTUAL@106..107 + 0: MD_TEXTUAL_LITERAL@106..107 "*" [] [] + 2: MD_TEXTUAL@107..111 + 0: MD_TEXTUAL_LITERAL@107..111 " bar" [] [] + 3: MD_TEXTUAL@111..112 + 0: MD_TEXTUAL_LITERAL@111..112 "*" [] [] + 4: MD_TEXTUAL@112..135 + 0: MD_TEXTUAL_LITERAL@112..135 " should not be emphasis" [] [] + 5: MD_TEXTUAL@135..136 + 0: MD_TEXTUAL_LITERAL@135..136 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@136..137 + 0: NEWLINE@136..137 "\n" [] [] + 6: MD_PARAGRAPH@137..186 + 0: MD_INLINE_ITEM_LIST@137..186 + 0: MD_TEXTUAL@137..162 + 0: MD_TEXTUAL_LITERAL@137..162 "Underscore intraword: foo" [] [] + 1: MD_TEXTUAL@162..163 + 0: MD_TEXTUAL_LITERAL@162..163 "_" [] [] + 2: MD_TEXTUAL@163..166 + 0: MD_TEXTUAL_LITERAL@163..166 "bar" [] [] + 3: MD_TEXTUAL@166..167 + 0: MD_TEXTUAL_LITERAL@166..167 "_" [] [] + 4: MD_TEXTUAL@167..171 + 0: MD_TEXTUAL_LITERAL@167..171 "baz " [] [] + 5: MD_TEXTUAL@171..172 + 0: MD_TEXTUAL_LITERAL@171..172 "(" [] [] + 6: MD_TEXTUAL@172..184 + 0: MD_TEXTUAL_LITERAL@172..184 "not emphasis" [] [] + 7: MD_TEXTUAL@184..185 + 0: MD_TEXTUAL_LITERAL@184..185 ")" [] [] + 8: MD_TEXTUAL@185..186 + 0: MD_TEXTUAL_LITERAL@185..186 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@186..187 + 0: NEWLINE@186..187 "\n" [] [] + 8: MD_PARAGRAPH@187..229 + 0: MD_INLINE_ITEM_LIST@187..229 + 0: MD_TEXTUAL@187..222 + 0: MD_TEXTUAL_LITERAL@187..222 "Valid underscore at word boundary: " [] [] + 1: MD_INLINE_ITALIC@222..228 + 0: UNDERSCORE@222..223 "_" [] [] + 1: MD_INLINE_ITEM_LIST@223..227 + 0: MD_TEXTUAL@223..227 + 0: MD_TEXTUAL_LITERAL@223..227 "word" [] [] + 2: UNDERSCORE@227..228 "_" [] [] + 2: MD_TEXTUAL@228..229 + 0: MD_TEXTUAL_LITERAL@228..229 "\n" [] [] + 1: (empty) + 9: MD_NEWLINE@229..230 + 0: NEWLINE@229..230 "\n" [] [] + 10: MD_PARAGRAPH@230..251 + 0: MD_INLINE_ITEM_LIST@230..251 + 0: MD_TEXTUAL@230..241 + 0: MD_TEXTUAL_LITERAL@230..241 "Mixed: foo " [] [] + 1: MD_INLINE_ITALIC@241..246 + 0: STAR@241..242 "*" [] [] + 1: MD_INLINE_ITEM_LIST@242..245 + 0: MD_TEXTUAL@242..245 + 0: MD_TEXTUAL_LITERAL@242..245 "bar" [] [] + 2: STAR@245..246 "*" [] [] + 2: MD_TEXTUAL@246..250 + 0: MD_TEXTUAL_LITERAL@246..250 " baz" [] [] + 3: MD_TEXTUAL@250..251 + 0: MD_TEXTUAL_LITERAL@250..251 "\n" [] [] + 1: (empty) + 2: EOF@251..251 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_link_text.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_link_text.md new file mode 100644 index 000000000000..38ee5393aedf --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_link_text.md @@ -0,0 +1 @@ +Link with emphasis: [a *b* c](u) diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_link_text.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_link_text.md.snap new file mode 100644 index 000000000000..fbb8f0a4daaf --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/emphasis_link_text.md.snap @@ -0,0 +1,100 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Link with emphasis: [a *b* c](u) + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..20 "Link with emphasis: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@20..21 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..23 "a " [] [], + }, + MdInlineItalic { + l_fence: STAR@23..24 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..25 "b" [] [], + }, + ], + r_fence: STAR@25..26 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@26..28 " c" [] [], + }, + ], + r_brack_token: R_BRACK@28..29 "]" [] [], + l_paren_token: L_PAREN@29..30 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 "u" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@31..32 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..33 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@33..33 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..33 + 0: (empty) + 1: MD_BLOCK_LIST@0..33 + 0: MD_PARAGRAPH@0..33 + 0: MD_INLINE_ITEM_LIST@0..33 + 0: MD_TEXTUAL@0..20 + 0: MD_TEXTUAL_LITERAL@0..20 "Link with emphasis: " [] [] + 1: MD_INLINE_LINK@20..32 + 0: L_BRACK@20..21 "[" [] [] + 1: MD_INLINE_ITEM_LIST@21..28 + 0: MD_TEXTUAL@21..23 + 0: MD_TEXTUAL_LITERAL@21..23 "a " [] [] + 1: MD_INLINE_ITALIC@23..26 + 0: STAR@23..24 "*" [] [] + 1: MD_INLINE_ITEM_LIST@24..25 + 0: MD_TEXTUAL@24..25 + 0: MD_TEXTUAL_LITERAL@24..25 "b" [] [] + 2: STAR@25..26 "*" [] [] + 2: MD_TEXTUAL@26..28 + 0: MD_TEXTUAL_LITERAL@26..28 " c" [] [] + 2: R_BRACK@28..29 "]" [] [] + 3: L_PAREN@29..30 "(" [] [] + 4: MD_INLINE_ITEM_LIST@30..31 + 0: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 "u" [] [] + 5: (empty) + 6: R_PAREN@31..32 ")" [] [] + 2: MD_TEXTUAL@32..33 + 0: MD_TEXTUAL_LITERAL@32..33 "\n" [] [] + 1: (empty) + 2: EOF@33..33 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/entity_references.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/entity_references.md new file mode 100644 index 000000000000..86ae24156e52 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/entity_references.md @@ -0,0 +1,11 @@ +Named entities: & ©   + +Decimal entities: { A + +Hex entities:  / + +Mixed: Use <div> for HTML. + +Invalid (remain as text): ¬anentity_with_underscore; � &#xGGGG; &; &# & foo; + +Edge cases: &a; (too short) &abcdefghijklmnopqrstuvwxyz01234567890; (too long) diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/entity_references.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/entity_references.md.snap new file mode 100644 index 000000000000..172f63426c7e --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/entity_references.md.snap @@ -0,0 +1,401 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Named entities: & ©   + +Decimal entities: { A + +Hex entities:  / + +Mixed: Use <div> for HTML. + +Invalid (remain as text): ¬anentity_with_underscore; � &#xGGGG; &; &# & foo; + +Edge cases: &a; (too short) &abcdefghijklmnopqrstuvwxyz01234567890; (too long) + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..16 "Named entities: " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@16..21 "&" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..22 " " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@22..28 "©" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..29 " " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@29..35 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@35..36 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@36..37 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..55 "Decimal entities: " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@55..61 "{" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@61..62 " " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@62..67 "A" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@67..68 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@68..69 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@69..83 "Hex entities: " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@83..89 "" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@89..90 " " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@90..96 "/" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@96..97 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@97..98 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@98..109 "Mixed: Use " [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@109..113 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@113..116 "div" [] [], + }, + MdEntityReference { + value_token: MD_ENTITY_LITERAL@116..120 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@120..130 " for HTML." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@130..131 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@131..132 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@132..140 "Invalid " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@140..141 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@141..155 "remain as text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@155..156 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@156..157 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@157..158 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@158..170 "¬anentity" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@170..171 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@171..175 "with" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@175..176 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@176..188 "underscore; " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@188..189 "&" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@189..190 "#" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@190..200 "12345678; " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@200..201 "&" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@201..202 "#" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@202..209 "xGGGG; " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@209..212 "&; " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@212..213 "&" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@213..214 "#" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@214..215 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@215..221 "& foo;" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@221..222 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@222..223 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@223..235 "Edge cases: " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@235..239 "&a; " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@239..240 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@240..249 "too short" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@249..250 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@250..251 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@251..291 "&abcdefghijklmnopqrstuvwxyz01234567890; " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@291..292 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@292..300 "too long" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@300..301 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@301..302 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@302..302 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..302 + 0: (empty) + 1: MD_BLOCK_LIST@0..302 + 0: MD_PARAGRAPH@0..36 + 0: MD_INLINE_ITEM_LIST@0..36 + 0: MD_TEXTUAL@0..16 + 0: MD_TEXTUAL_LITERAL@0..16 "Named entities: " [] [] + 1: MD_ENTITY_REFERENCE@16..21 + 0: MD_ENTITY_LITERAL@16..21 "&" [] [] + 2: MD_TEXTUAL@21..22 + 0: MD_TEXTUAL_LITERAL@21..22 " " [] [] + 3: MD_ENTITY_REFERENCE@22..28 + 0: MD_ENTITY_LITERAL@22..28 "©" [] [] + 4: MD_TEXTUAL@28..29 + 0: MD_TEXTUAL_LITERAL@28..29 " " [] [] + 5: MD_ENTITY_REFERENCE@29..35 + 0: MD_ENTITY_LITERAL@29..35 " " [] [] + 6: MD_TEXTUAL@35..36 + 0: MD_TEXTUAL_LITERAL@35..36 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@36..37 + 0: NEWLINE@36..37 "\n" [] [] + 2: MD_PARAGRAPH@37..68 + 0: MD_INLINE_ITEM_LIST@37..68 + 0: MD_TEXTUAL@37..55 + 0: MD_TEXTUAL_LITERAL@37..55 "Decimal entities: " [] [] + 1: MD_ENTITY_REFERENCE@55..61 + 0: MD_ENTITY_LITERAL@55..61 "{" [] [] + 2: MD_TEXTUAL@61..62 + 0: MD_TEXTUAL_LITERAL@61..62 " " [] [] + 3: MD_ENTITY_REFERENCE@62..67 + 0: MD_ENTITY_LITERAL@62..67 "A" [] [] + 4: MD_TEXTUAL@67..68 + 0: MD_TEXTUAL_LITERAL@67..68 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@68..69 + 0: NEWLINE@68..69 "\n" [] [] + 4: MD_PARAGRAPH@69..97 + 0: MD_INLINE_ITEM_LIST@69..97 + 0: MD_TEXTUAL@69..83 + 0: MD_TEXTUAL_LITERAL@69..83 "Hex entities: " [] [] + 1: MD_ENTITY_REFERENCE@83..89 + 0: MD_ENTITY_LITERAL@83..89 "" [] [] + 2: MD_TEXTUAL@89..90 + 0: MD_TEXTUAL_LITERAL@89..90 " " [] [] + 3: MD_ENTITY_REFERENCE@90..96 + 0: MD_ENTITY_LITERAL@90..96 "/" [] [] + 4: MD_TEXTUAL@96..97 + 0: MD_TEXTUAL_LITERAL@96..97 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@97..98 + 0: NEWLINE@97..98 "\n" [] [] + 6: MD_PARAGRAPH@98..131 + 0: MD_INLINE_ITEM_LIST@98..131 + 0: MD_TEXTUAL@98..109 + 0: MD_TEXTUAL_LITERAL@98..109 "Mixed: Use " [] [] + 1: MD_ENTITY_REFERENCE@109..113 + 0: MD_ENTITY_LITERAL@109..113 "<" [] [] + 2: MD_TEXTUAL@113..116 + 0: MD_TEXTUAL_LITERAL@113..116 "div" [] [] + 3: MD_ENTITY_REFERENCE@116..120 + 0: MD_ENTITY_LITERAL@116..120 ">" [] [] + 4: MD_TEXTUAL@120..130 + 0: MD_TEXTUAL_LITERAL@120..130 " for HTML." [] [] + 5: MD_TEXTUAL@130..131 + 0: MD_TEXTUAL_LITERAL@130..131 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@131..132 + 0: NEWLINE@131..132 "\n" [] [] + 8: MD_PARAGRAPH@132..222 + 0: MD_INLINE_ITEM_LIST@132..222 + 0: MD_TEXTUAL@132..140 + 0: MD_TEXTUAL_LITERAL@132..140 "Invalid " [] [] + 1: MD_TEXTUAL@140..141 + 0: MD_TEXTUAL_LITERAL@140..141 "(" [] [] + 2: MD_TEXTUAL@141..155 + 0: MD_TEXTUAL_LITERAL@141..155 "remain as text" [] [] + 3: MD_TEXTUAL@155..156 + 0: MD_TEXTUAL_LITERAL@155..156 ")" [] [] + 4: MD_TEXTUAL@156..157 + 0: MD_TEXTUAL_LITERAL@156..157 ":" [] [] + 5: MD_TEXTUAL@157..158 + 0: MD_TEXTUAL_LITERAL@157..158 " " [] [] + 6: MD_TEXTUAL@158..170 + 0: MD_TEXTUAL_LITERAL@158..170 "¬anentity" [] [] + 7: MD_TEXTUAL@170..171 + 0: MD_TEXTUAL_LITERAL@170..171 "_" [] [] + 8: MD_TEXTUAL@171..175 + 0: MD_TEXTUAL_LITERAL@171..175 "with" [] [] + 9: MD_TEXTUAL@175..176 + 0: MD_TEXTUAL_LITERAL@175..176 "_" [] [] + 10: MD_TEXTUAL@176..188 + 0: MD_TEXTUAL_LITERAL@176..188 "underscore; " [] [] + 11: MD_TEXTUAL@188..189 + 0: MD_TEXTUAL_LITERAL@188..189 "&" [] [] + 12: MD_TEXTUAL@189..190 + 0: MD_TEXTUAL_LITERAL@189..190 "#" [] [] + 13: MD_TEXTUAL@190..200 + 0: MD_TEXTUAL_LITERAL@190..200 "12345678; " [] [] + 14: MD_TEXTUAL@200..201 + 0: MD_TEXTUAL_LITERAL@200..201 "&" [] [] + 15: MD_TEXTUAL@201..202 + 0: MD_TEXTUAL_LITERAL@201..202 "#" [] [] + 16: MD_TEXTUAL@202..209 + 0: MD_TEXTUAL_LITERAL@202..209 "xGGGG; " [] [] + 17: MD_TEXTUAL@209..212 + 0: MD_TEXTUAL_LITERAL@209..212 "&; " [] [] + 18: MD_TEXTUAL@212..213 + 0: MD_TEXTUAL_LITERAL@212..213 "&" [] [] + 19: MD_TEXTUAL@213..214 + 0: MD_TEXTUAL_LITERAL@213..214 "#" [] [] + 20: MD_TEXTUAL@214..215 + 0: MD_TEXTUAL_LITERAL@214..215 " " [] [] + 21: MD_TEXTUAL@215..221 + 0: MD_TEXTUAL_LITERAL@215..221 "& foo;" [] [] + 22: MD_TEXTUAL@221..222 + 0: MD_TEXTUAL_LITERAL@221..222 "\n" [] [] + 1: (empty) + 9: MD_NEWLINE@222..223 + 0: NEWLINE@222..223 "\n" [] [] + 10: MD_PARAGRAPH@223..302 + 0: MD_INLINE_ITEM_LIST@223..302 + 0: MD_TEXTUAL@223..235 + 0: MD_TEXTUAL_LITERAL@223..235 "Edge cases: " [] [] + 1: MD_TEXTUAL@235..239 + 0: MD_TEXTUAL_LITERAL@235..239 "&a; " [] [] + 2: MD_TEXTUAL@239..240 + 0: MD_TEXTUAL_LITERAL@239..240 "(" [] [] + 3: MD_TEXTUAL@240..249 + 0: MD_TEXTUAL_LITERAL@240..249 "too short" [] [] + 4: MD_TEXTUAL@249..250 + 0: MD_TEXTUAL_LITERAL@249..250 ")" [] [] + 5: MD_TEXTUAL@250..251 + 0: MD_TEXTUAL_LITERAL@250..251 " " [] [] + 6: MD_TEXTUAL@251..291 + 0: MD_TEXTUAL_LITERAL@251..291 "&abcdefghijklmnopqrstuvwxyz01234567890; " [] [] + 7: MD_TEXTUAL@291..292 + 0: MD_TEXTUAL_LITERAL@291..292 "(" [] [] + 8: MD_TEXTUAL@292..300 + 0: MD_TEXTUAL_LITERAL@292..300 "too long" [] [] + 9: MD_TEXTUAL@300..301 + 0: MD_TEXTUAL_LITERAL@300..301 ")" [] [] + 10: MD_TEXTUAL@301..302 + 0: MD_TEXTUAL_LITERAL@301..302 "\n" [] [] + 1: (empty) + 2: EOF@302..302 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_advanced.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_advanced.md new file mode 100644 index 000000000000..29029ab58d49 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_advanced.md @@ -0,0 +1,38 @@ +Basic (3 backticks): +``` +code +``` + +Longer fence (5 backticks): +````` +code with ``` inside +````` + +Tildes: +~~~ +code +~~~ + +Mixed (should not close): +``` +code +~~~ +still code +``` + +Indented closing (valid): +``` +code + ``` + +Short closing (invalid - treated as content): +```` +code +``` +still code +```` + +Indented opening (stripped from content): + ``` + code line + ``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_advanced.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_advanced.md.snap new file mode 100644 index 000000000000..9ef15e62f4b4 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_advanced.md.snap @@ -0,0 +1,630 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Basic (3 backticks): +``` +code +``` + +Longer fence (5 backticks): +````` +code with ``` inside +````` + +Tildes: +~~~ +code +~~~ + +Mixed (should not close): +``` +code +~~~ +still code +``` + +Indented closing (valid): +``` +code + ``` + +Short closing (invalid - treated as content): +```` +code +``` +still code +```` + +Indented opening (stripped from content): + ``` + code line + ``` + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..6 "Basic " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@6..7 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@7..18 "3 backticks" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@18..19 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@19..20 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@20..21 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@21..24 "```" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..25 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@25..29 "code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..30 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@30..33 "```" [] [], + }, + MdNewline { + value_token: NEWLINE@33..34 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@34..35 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@35..48 "Longer fence " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@48..49 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@49..60 "5 backticks" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@60..61 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@61..62 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@62..63 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@63..68 "`````" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..69 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@69..79 "code with " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@79..82 "```" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@82..89 " inside" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@89..90 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@90..95 "`````" [] [], + }, + MdNewline { + value_token: NEWLINE@95..96 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@96..97 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@97..104 "Tildes:" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@104..105 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_TILDE@105..108 "~~~" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@108..109 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@109..113 "code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@113..114 "\n" [] [], + }, + ], + r_fence: TRIPLE_TILDE@114..117 "~~~" [] [], + }, + MdNewline { + value_token: NEWLINE@117..118 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@118..119 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@119..125 "Mixed " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@125..126 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@126..142 "should not close" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@142..143 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@143..144 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@144..145 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@145..148 "```" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@148..149 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@149..153 "code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@153..154 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@154..157 "~~~" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@157..158 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@158..168 "still code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@168..169 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@169..172 "```" [] [], + }, + MdNewline { + value_token: NEWLINE@172..173 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@173..174 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@174..191 "Indented closing " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@191..192 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@192..197 "valid" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@197..198 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@198..199 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@199..200 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@200..203 "```" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@203..204 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@204..208 "code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@208..209 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@209..215 "```" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@215..216 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@216..217 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@217..231 "Short closing " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@231..232 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@232..240 "invalid " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@240..241 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@241..260 " treated as content" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@260..261 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@261..262 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@262..263 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@263..267 "````" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@267..268 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@268..272 "code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@272..273 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@273..276 "```" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@276..277 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@277..287 "still code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@287..288 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@288..292 "````" [] [], + }, + MdNewline { + value_token: NEWLINE@292..293 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@293..294 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@294..311 "Indented opening " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@311..312 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@312..333 "stripped from content" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@333..334 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@334..335 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@335..336 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@336..341 "```" [Skipped(" "), Skipped(" ")] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@341..342 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@342..353 "code line" [Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@353..354 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@354..359 "```" [Skipped(" "), Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@359..360 "\n" [] [], + }, + ], + eof_token: EOF@360..360 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..360 + 0: (empty) + 1: MD_BLOCK_LIST@0..360 + 0: MD_PARAGRAPH@0..21 + 0: MD_INLINE_ITEM_LIST@0..21 + 0: MD_TEXTUAL@0..6 + 0: MD_TEXTUAL_LITERAL@0..6 "Basic " [] [] + 1: MD_TEXTUAL@6..7 + 0: MD_TEXTUAL_LITERAL@6..7 "(" [] [] + 2: MD_TEXTUAL@7..18 + 0: MD_TEXTUAL_LITERAL@7..18 "3 backticks" [] [] + 3: MD_TEXTUAL@18..19 + 0: MD_TEXTUAL_LITERAL@18..19 ")" [] [] + 4: MD_TEXTUAL@19..20 + 0: MD_TEXTUAL_LITERAL@19..20 ":" [] [] + 5: MD_TEXTUAL@20..21 + 0: MD_TEXTUAL_LITERAL@20..21 "\n" [] [] + 1: (empty) + 1: MD_FENCED_CODE_BLOCK@21..33 + 0: TRIPLE_BACKTICK@21..24 "```" [] [] + 1: MD_CODE_NAME_LIST@24..24 + 2: MD_INLINE_ITEM_LIST@24..30 + 0: MD_TEXTUAL@24..25 + 0: MD_TEXTUAL_LITERAL@24..25 "\n" [] [] + 1: MD_TEXTUAL@25..29 + 0: MD_TEXTUAL_LITERAL@25..29 "code" [] [] + 2: MD_TEXTUAL@29..30 + 0: MD_TEXTUAL_LITERAL@29..30 "\n" [] [] + 3: TRIPLE_BACKTICK@30..33 "```" [] [] + 2: MD_NEWLINE@33..34 + 0: NEWLINE@33..34 "\n" [] [] + 3: MD_NEWLINE@34..35 + 0: NEWLINE@34..35 "\n" [] [] + 4: MD_PARAGRAPH@35..63 + 0: MD_INLINE_ITEM_LIST@35..63 + 0: MD_TEXTUAL@35..48 + 0: MD_TEXTUAL_LITERAL@35..48 "Longer fence " [] [] + 1: MD_TEXTUAL@48..49 + 0: MD_TEXTUAL_LITERAL@48..49 "(" [] [] + 2: MD_TEXTUAL@49..60 + 0: MD_TEXTUAL_LITERAL@49..60 "5 backticks" [] [] + 3: MD_TEXTUAL@60..61 + 0: MD_TEXTUAL_LITERAL@60..61 ")" [] [] + 4: MD_TEXTUAL@61..62 + 0: MD_TEXTUAL_LITERAL@61..62 ":" [] [] + 5: MD_TEXTUAL@62..63 + 0: MD_TEXTUAL_LITERAL@62..63 "\n" [] [] + 1: (empty) + 5: MD_FENCED_CODE_BLOCK@63..95 + 0: TRIPLE_BACKTICK@63..68 "`````" [] [] + 1: MD_CODE_NAME_LIST@68..68 + 2: MD_INLINE_ITEM_LIST@68..90 + 0: MD_TEXTUAL@68..69 + 0: MD_TEXTUAL_LITERAL@68..69 "\n" [] [] + 1: MD_TEXTUAL@69..79 + 0: MD_TEXTUAL_LITERAL@69..79 "code with " [] [] + 2: MD_TEXTUAL@79..82 + 0: MD_TEXTUAL_LITERAL@79..82 "```" [] [] + 3: MD_TEXTUAL@82..89 + 0: MD_TEXTUAL_LITERAL@82..89 " inside" [] [] + 4: MD_TEXTUAL@89..90 + 0: MD_TEXTUAL_LITERAL@89..90 "\n" [] [] + 3: TRIPLE_BACKTICK@90..95 "`````" [] [] + 6: MD_NEWLINE@95..96 + 0: NEWLINE@95..96 "\n" [] [] + 7: MD_NEWLINE@96..97 + 0: NEWLINE@96..97 "\n" [] [] + 8: MD_PARAGRAPH@97..105 + 0: MD_INLINE_ITEM_LIST@97..105 + 0: MD_TEXTUAL@97..104 + 0: MD_TEXTUAL_LITERAL@97..104 "Tildes:" [] [] + 1: MD_TEXTUAL@104..105 + 0: MD_TEXTUAL_LITERAL@104..105 "\n" [] [] + 1: (empty) + 9: MD_FENCED_CODE_BLOCK@105..117 + 0: TRIPLE_TILDE@105..108 "~~~" [] [] + 1: MD_CODE_NAME_LIST@108..108 + 2: MD_INLINE_ITEM_LIST@108..114 + 0: MD_TEXTUAL@108..109 + 0: MD_TEXTUAL_LITERAL@108..109 "\n" [] [] + 1: MD_TEXTUAL@109..113 + 0: MD_TEXTUAL_LITERAL@109..113 "code" [] [] + 2: MD_TEXTUAL@113..114 + 0: MD_TEXTUAL_LITERAL@113..114 "\n" [] [] + 3: TRIPLE_TILDE@114..117 "~~~" [] [] + 10: MD_NEWLINE@117..118 + 0: NEWLINE@117..118 "\n" [] [] + 11: MD_NEWLINE@118..119 + 0: NEWLINE@118..119 "\n" [] [] + 12: MD_PARAGRAPH@119..145 + 0: MD_INLINE_ITEM_LIST@119..145 + 0: MD_TEXTUAL@119..125 + 0: MD_TEXTUAL_LITERAL@119..125 "Mixed " [] [] + 1: MD_TEXTUAL@125..126 + 0: MD_TEXTUAL_LITERAL@125..126 "(" [] [] + 2: MD_TEXTUAL@126..142 + 0: MD_TEXTUAL_LITERAL@126..142 "should not close" [] [] + 3: MD_TEXTUAL@142..143 + 0: MD_TEXTUAL_LITERAL@142..143 ")" [] [] + 4: MD_TEXTUAL@143..144 + 0: MD_TEXTUAL_LITERAL@143..144 ":" [] [] + 5: MD_TEXTUAL@144..145 + 0: MD_TEXTUAL_LITERAL@144..145 "\n" [] [] + 1: (empty) + 13: MD_FENCED_CODE_BLOCK@145..172 + 0: TRIPLE_BACKTICK@145..148 "```" [] [] + 1: MD_CODE_NAME_LIST@148..148 + 2: MD_INLINE_ITEM_LIST@148..169 + 0: MD_TEXTUAL@148..149 + 0: MD_TEXTUAL_LITERAL@148..149 "\n" [] [] + 1: MD_TEXTUAL@149..153 + 0: MD_TEXTUAL_LITERAL@149..153 "code" [] [] + 2: MD_TEXTUAL@153..154 + 0: MD_TEXTUAL_LITERAL@153..154 "\n" [] [] + 3: MD_TEXTUAL@154..157 + 0: MD_TEXTUAL_LITERAL@154..157 "~~~" [] [] + 4: MD_TEXTUAL@157..158 + 0: MD_TEXTUAL_LITERAL@157..158 "\n" [] [] + 5: MD_TEXTUAL@158..168 + 0: MD_TEXTUAL_LITERAL@158..168 "still code" [] [] + 6: MD_TEXTUAL@168..169 + 0: MD_TEXTUAL_LITERAL@168..169 "\n" [] [] + 3: TRIPLE_BACKTICK@169..172 "```" [] [] + 14: MD_NEWLINE@172..173 + 0: NEWLINE@172..173 "\n" [] [] + 15: MD_NEWLINE@173..174 + 0: NEWLINE@173..174 "\n" [] [] + 16: MD_PARAGRAPH@174..200 + 0: MD_INLINE_ITEM_LIST@174..200 + 0: MD_TEXTUAL@174..191 + 0: MD_TEXTUAL_LITERAL@174..191 "Indented closing " [] [] + 1: MD_TEXTUAL@191..192 + 0: MD_TEXTUAL_LITERAL@191..192 "(" [] [] + 2: MD_TEXTUAL@192..197 + 0: MD_TEXTUAL_LITERAL@192..197 "valid" [] [] + 3: MD_TEXTUAL@197..198 + 0: MD_TEXTUAL_LITERAL@197..198 ")" [] [] + 4: MD_TEXTUAL@198..199 + 0: MD_TEXTUAL_LITERAL@198..199 ":" [] [] + 5: MD_TEXTUAL@199..200 + 0: MD_TEXTUAL_LITERAL@199..200 "\n" [] [] + 1: (empty) + 17: MD_FENCED_CODE_BLOCK@200..215 + 0: TRIPLE_BACKTICK@200..203 "```" [] [] + 1: MD_CODE_NAME_LIST@203..203 + 2: MD_INLINE_ITEM_LIST@203..209 + 0: MD_TEXTUAL@203..204 + 0: MD_TEXTUAL_LITERAL@203..204 "\n" [] [] + 1: MD_TEXTUAL@204..208 + 0: MD_TEXTUAL_LITERAL@204..208 "code" [] [] + 2: MD_TEXTUAL@208..209 + 0: MD_TEXTUAL_LITERAL@208..209 "\n" [] [] + 3: TRIPLE_BACKTICK@209..215 "```" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 18: MD_NEWLINE@215..216 + 0: NEWLINE@215..216 "\n" [] [] + 19: MD_NEWLINE@216..217 + 0: NEWLINE@216..217 "\n" [] [] + 20: MD_PARAGRAPH@217..263 + 0: MD_INLINE_ITEM_LIST@217..263 + 0: MD_TEXTUAL@217..231 + 0: MD_TEXTUAL_LITERAL@217..231 "Short closing " [] [] + 1: MD_TEXTUAL@231..232 + 0: MD_TEXTUAL_LITERAL@231..232 "(" [] [] + 2: MD_TEXTUAL@232..240 + 0: MD_TEXTUAL_LITERAL@232..240 "invalid " [] [] + 3: MD_TEXTUAL@240..241 + 0: MD_TEXTUAL_LITERAL@240..241 "-" [] [] + 4: MD_TEXTUAL@241..260 + 0: MD_TEXTUAL_LITERAL@241..260 " treated as content" [] [] + 5: MD_TEXTUAL@260..261 + 0: MD_TEXTUAL_LITERAL@260..261 ")" [] [] + 6: MD_TEXTUAL@261..262 + 0: MD_TEXTUAL_LITERAL@261..262 ":" [] [] + 7: MD_TEXTUAL@262..263 + 0: MD_TEXTUAL_LITERAL@262..263 "\n" [] [] + 1: (empty) + 21: MD_FENCED_CODE_BLOCK@263..292 + 0: TRIPLE_BACKTICK@263..267 "````" [] [] + 1: MD_CODE_NAME_LIST@267..267 + 2: MD_INLINE_ITEM_LIST@267..288 + 0: MD_TEXTUAL@267..268 + 0: MD_TEXTUAL_LITERAL@267..268 "\n" [] [] + 1: MD_TEXTUAL@268..272 + 0: MD_TEXTUAL_LITERAL@268..272 "code" [] [] + 2: MD_TEXTUAL@272..273 + 0: MD_TEXTUAL_LITERAL@272..273 "\n" [] [] + 3: MD_TEXTUAL@273..276 + 0: MD_TEXTUAL_LITERAL@273..276 "```" [] [] + 4: MD_TEXTUAL@276..277 + 0: MD_TEXTUAL_LITERAL@276..277 "\n" [] [] + 5: MD_TEXTUAL@277..287 + 0: MD_TEXTUAL_LITERAL@277..287 "still code" [] [] + 6: MD_TEXTUAL@287..288 + 0: MD_TEXTUAL_LITERAL@287..288 "\n" [] [] + 3: TRIPLE_BACKTICK@288..292 "````" [] [] + 22: MD_NEWLINE@292..293 + 0: NEWLINE@292..293 "\n" [] [] + 23: MD_NEWLINE@293..294 + 0: NEWLINE@293..294 "\n" [] [] + 24: MD_PARAGRAPH@294..336 + 0: MD_INLINE_ITEM_LIST@294..336 + 0: MD_TEXTUAL@294..311 + 0: MD_TEXTUAL_LITERAL@294..311 "Indented opening " [] [] + 1: MD_TEXTUAL@311..312 + 0: MD_TEXTUAL_LITERAL@311..312 "(" [] [] + 2: MD_TEXTUAL@312..333 + 0: MD_TEXTUAL_LITERAL@312..333 "stripped from content" [] [] + 3: MD_TEXTUAL@333..334 + 0: MD_TEXTUAL_LITERAL@333..334 ")" [] [] + 4: MD_TEXTUAL@334..335 + 0: MD_TEXTUAL_LITERAL@334..335 ":" [] [] + 5: MD_TEXTUAL@335..336 + 0: MD_TEXTUAL_LITERAL@335..336 "\n" [] [] + 1: (empty) + 25: MD_FENCED_CODE_BLOCK@336..359 + 0: TRIPLE_BACKTICK@336..341 "```" [Skipped(" "), Skipped(" ")] [] + 1: MD_CODE_NAME_LIST@341..341 + 2: MD_INLINE_ITEM_LIST@341..354 + 0: MD_TEXTUAL@341..342 + 0: MD_TEXTUAL_LITERAL@341..342 "\n" [] [] + 1: MD_TEXTUAL@342..353 + 0: MD_TEXTUAL_LITERAL@342..353 "code line" [Skipped(" "), Skipped(" ")] [] + 2: MD_TEXTUAL@353..354 + 0: MD_TEXTUAL_LITERAL@353..354 "\n" [] [] + 3: TRIPLE_BACKTICK@354..359 "```" [Skipped(" "), Skipped(" ")] [] + 26: MD_NEWLINE@359..360 + 0: NEWLINE@359..360 "\n" [] [] + 2: EOF@360..360 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_block.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_block.md new file mode 100644 index 000000000000..bf110cc260d0 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_block.md @@ -0,0 +1,15 @@ +```javascript +const x = 1; +``` + +``` +No language +``` + +~~~python +x = 1 +~~~ + +~~~ +Tilde no language +~~~ diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_block.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_block.md.snap new file mode 100644 index 000000000000..84d49e8b788d --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_block.md.snap @@ -0,0 +1,202 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +```javascript +const x = 1; +``` + +``` +No language +``` + +~~~python +x = 1 +~~~ + +~~~ +Tilde no language +~~~ + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@0..3 "```" [] [], + code_list: MdCodeNameList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@3..13 "javascript" [] [], + }, + ], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..14 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..26 "const x = 1;" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@26..27 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@27..30 "```" [] [], + }, + MdNewline { + value_token: NEWLINE@30..31 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@31..32 "\n" [] [], + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@32..35 "```" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@35..36 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@36..47 "No language" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@47..48 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@48..51 "```" [] [], + }, + MdNewline { + value_token: NEWLINE@51..52 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@52..53 "\n" [] [], + }, + MdFencedCodeBlock { + l_fence: TRIPLE_TILDE@53..56 "~~~" [] [], + code_list: MdCodeNameList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..62 "python" [] [], + }, + ], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@62..63 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@63..68 "x = 1" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..69 "\n" [] [], + }, + ], + r_fence: TRIPLE_TILDE@69..72 "~~~" [] [], + }, + MdNewline { + value_token: NEWLINE@72..73 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@73..74 "\n" [] [], + }, + MdFencedCodeBlock { + l_fence: TRIPLE_TILDE@74..77 "~~~" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@77..78 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@78..95 "Tilde no language" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@95..96 "\n" [] [], + }, + ], + r_fence: TRIPLE_TILDE@96..99 "~~~" [] [], + }, + MdNewline { + value_token: NEWLINE@99..100 "\n" [] [], + }, + ], + eof_token: EOF@100..100 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..100 + 0: (empty) + 1: MD_BLOCK_LIST@0..100 + 0: MD_FENCED_CODE_BLOCK@0..30 + 0: TRIPLE_BACKTICK@0..3 "```" [] [] + 1: MD_CODE_NAME_LIST@3..13 + 0: MD_TEXTUAL@3..13 + 0: MD_TEXTUAL_LITERAL@3..13 "javascript" [] [] + 2: MD_INLINE_ITEM_LIST@13..27 + 0: MD_TEXTUAL@13..14 + 0: MD_TEXTUAL_LITERAL@13..14 "\n" [] [] + 1: MD_TEXTUAL@14..26 + 0: MD_TEXTUAL_LITERAL@14..26 "const x = 1;" [] [] + 2: MD_TEXTUAL@26..27 + 0: MD_TEXTUAL_LITERAL@26..27 "\n" [] [] + 3: TRIPLE_BACKTICK@27..30 "```" [] [] + 1: MD_NEWLINE@30..31 + 0: NEWLINE@30..31 "\n" [] [] + 2: MD_NEWLINE@31..32 + 0: NEWLINE@31..32 "\n" [] [] + 3: MD_FENCED_CODE_BLOCK@32..51 + 0: TRIPLE_BACKTICK@32..35 "```" [] [] + 1: MD_CODE_NAME_LIST@35..35 + 2: MD_INLINE_ITEM_LIST@35..48 + 0: MD_TEXTUAL@35..36 + 0: MD_TEXTUAL_LITERAL@35..36 "\n" [] [] + 1: MD_TEXTUAL@36..47 + 0: MD_TEXTUAL_LITERAL@36..47 "No language" [] [] + 2: MD_TEXTUAL@47..48 + 0: MD_TEXTUAL_LITERAL@47..48 "\n" [] [] + 3: TRIPLE_BACKTICK@48..51 "```" [] [] + 4: MD_NEWLINE@51..52 + 0: NEWLINE@51..52 "\n" [] [] + 5: MD_NEWLINE@52..53 + 0: NEWLINE@52..53 "\n" [] [] + 6: MD_FENCED_CODE_BLOCK@53..72 + 0: TRIPLE_TILDE@53..56 "~~~" [] [] + 1: MD_CODE_NAME_LIST@56..62 + 0: MD_TEXTUAL@56..62 + 0: MD_TEXTUAL_LITERAL@56..62 "python" [] [] + 2: MD_INLINE_ITEM_LIST@62..69 + 0: MD_TEXTUAL@62..63 + 0: MD_TEXTUAL_LITERAL@62..63 "\n" [] [] + 1: MD_TEXTUAL@63..68 + 0: MD_TEXTUAL_LITERAL@63..68 "x = 1" [] [] + 2: MD_TEXTUAL@68..69 + 0: MD_TEXTUAL_LITERAL@68..69 "\n" [] [] + 3: TRIPLE_TILDE@69..72 "~~~" [] [] + 7: MD_NEWLINE@72..73 + 0: NEWLINE@72..73 "\n" [] [] + 8: MD_NEWLINE@73..74 + 0: NEWLINE@73..74 "\n" [] [] + 9: MD_FENCED_CODE_BLOCK@74..99 + 0: TRIPLE_TILDE@74..77 "~~~" [] [] + 1: MD_CODE_NAME_LIST@77..77 + 2: MD_INLINE_ITEM_LIST@77..96 + 0: MD_TEXTUAL@77..78 + 0: MD_TEXTUAL_LITERAL@77..78 "\n" [] [] + 1: MD_TEXTUAL@78..95 + 0: MD_TEXTUAL_LITERAL@78..95 "Tilde no language" [] [] + 2: MD_TEXTUAL@95..96 + 0: MD_TEXTUAL_LITERAL@95..96 "\n" [] [] + 3: TRIPLE_TILDE@96..99 "~~~" [] [] + 10: MD_NEWLINE@99..100 + 0: NEWLINE@99..100 "\n" [] [] + 2: EOF@100..100 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_indentation.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_indentation.md new file mode 100644 index 000000000000..7557babe62d0 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_indentation.md @@ -0,0 +1,3 @@ +``` + indented +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_indentation.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_indentation.md.snap new file mode 100644 index 000000000000..f9c5d4777fee --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_indentation.md.snap @@ -0,0 +1,77 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +``` + indented +``` + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@0..3 "```" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@3..4 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@4..5 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@5..6 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@6..14 "indented" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..15 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@15..18 "```" [] [], + }, + MdNewline { + value_token: NEWLINE@18..19 "\n" [] [], + }, + ], + eof_token: EOF@19..19 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..19 + 0: (empty) + 1: MD_BLOCK_LIST@0..19 + 0: MD_FENCED_CODE_BLOCK@0..18 + 0: TRIPLE_BACKTICK@0..3 "```" [] [] + 1: MD_CODE_NAME_LIST@3..3 + 2: MD_INLINE_ITEM_LIST@3..15 + 0: MD_TEXTUAL@3..4 + 0: MD_TEXTUAL_LITERAL@3..4 "\n" [] [] + 1: MD_TEXTUAL@4..5 + 0: MD_TEXTUAL_LITERAL@4..5 " " [] [] + 2: MD_TEXTUAL@5..6 + 0: MD_TEXTUAL_LITERAL@5..6 " " [] [] + 3: MD_TEXTUAL@6..14 + 0: MD_TEXTUAL_LITERAL@6..14 "indented" [] [] + 4: MD_TEXTUAL@14..15 + 0: MD_TEXTUAL_LITERAL@14..15 "\n" [] [] + 3: TRIPLE_BACKTICK@15..18 "```" [] [] + 1: MD_NEWLINE@18..19 + 0: NEWLINE@18..19 "\n" [] [] + 2: EOF@19..19 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_info_backtick.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_info_backtick.md new file mode 100644 index 000000000000..44fd1b333ee5 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_info_backtick.md @@ -0,0 +1,2 @@ +Backtick in info string should not open fence: +``` lang`uage` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_info_backtick.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_info_backtick.md.snap new file mode 100644 index 000000000000..d20be009ce40 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/fenced_code_info_backtick.md.snap @@ -0,0 +1,82 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Backtick in info string should not open fence: +``` lang`uage` + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..46 "Backtick in info string should not open fence:" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@46..47 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@47..50 "```" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@50..55 " lang" [] [], + }, + MdInlineCode { + l_tick_token: BACKTICK@55..56 "`" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..60 "uage" [] [], + }, + ], + r_tick_token: BACKTICK@60..61 "`" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@61..62 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@62..62 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..62 + 0: (empty) + 1: MD_BLOCK_LIST@0..62 + 0: MD_PARAGRAPH@0..62 + 0: MD_INLINE_ITEM_LIST@0..62 + 0: MD_TEXTUAL@0..46 + 0: MD_TEXTUAL_LITERAL@0..46 "Backtick in info string should not open fence:" [] [] + 1: MD_TEXTUAL@46..47 + 0: MD_TEXTUAL_LITERAL@46..47 "\n" [] [] + 2: MD_TEXTUAL@47..50 + 0: MD_TEXTUAL_LITERAL@47..50 "```" [] [] + 3: MD_TEXTUAL@50..55 + 0: MD_TEXTUAL_LITERAL@50..55 " lang" [] [] + 4: MD_INLINE_CODE@55..61 + 0: BACKTICK@55..56 "`" [] [] + 1: MD_INLINE_ITEM_LIST@56..60 + 0: MD_TEXTUAL@56..60 + 0: MD_TEXTUAL_LITERAL@56..60 "uage" [] [] + 2: BACKTICK@60..61 "`" [] [] + 5: MD_TEXTUAL@61..62 + 0: MD_TEXTUAL_LITERAL@61..62 "\n" [] [] + 1: (empty) + 2: EOF@62..62 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/hard_line_break.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/hard_line_break.md new file mode 100644 index 000000000000..a86cbf987a97 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/hard_line_break.md @@ -0,0 +1,5 @@ +Line one +Line two + +Backslash\ +line break diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/hard_line_break.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/hard_line_break.md.snap new file mode 100644 index 000000000000..e432d8d2039a --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/hard_line_break.md.snap @@ -0,0 +1,98 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Line one +Line two + +Backslash\ +line break + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..8 "Line one" [] [], + }, + MdHardLine { + value_token: MD_HARD_LINE_LITERAL@8..11 " \n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@11..19 "Line two" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@19..20 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@20..21 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..30 "Backslash" [] [], + }, + MdHardLine { + value_token: MD_HARD_LINE_LITERAL@30..32 "\\\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..42 "line break" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..43 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@43..43 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..43 + 0: (empty) + 1: MD_BLOCK_LIST@0..43 + 0: MD_PARAGRAPH@0..20 + 0: MD_INLINE_ITEM_LIST@0..20 + 0: MD_TEXTUAL@0..8 + 0: MD_TEXTUAL_LITERAL@0..8 "Line one" [] [] + 1: MD_HARD_LINE@8..11 + 0: MD_HARD_LINE_LITERAL@8..11 " \n" [] [] + 2: MD_TEXTUAL@11..19 + 0: MD_TEXTUAL_LITERAL@11..19 "Line two" [] [] + 3: MD_TEXTUAL@19..20 + 0: MD_TEXTUAL_LITERAL@19..20 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@20..21 + 0: NEWLINE@20..21 "\n" [] [] + 2: MD_PARAGRAPH@21..43 + 0: MD_INLINE_ITEM_LIST@21..43 + 0: MD_TEXTUAL@21..30 + 0: MD_TEXTUAL_LITERAL@21..30 "Backslash" [] [] + 1: MD_HARD_LINE@30..32 + 0: MD_HARD_LINE_LITERAL@30..32 "\\\n" [] [] + 2: MD_TEXTUAL@32..42 + 0: MD_TEXTUAL_LITERAL@32..42 "line break" [] [] + 3: MD_TEXTUAL@42..43 + 0: MD_TEXTUAL_LITERAL@42..43 "\n" [] [] + 1: (empty) + 2: EOF@43..43 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/header.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/header.md new file mode 100644 index 000000000000..bbb7f21371b0 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/header.md @@ -0,0 +1,17 @@ +# Heading 1 + +## Heading 2 + +### Heading 3 + +#### Heading 4 + +##### Heading 5 + +###### Heading 6 + +# Trailing hash # + +## Multiple trailing ## + +### Mixed # content ## with ### trailing #### diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/header.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/header.md.snap new file mode 100644 index 000000000000..7faa757adb91 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/header.md.snap @@ -0,0 +1,416 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +# Heading 1 + +## Heading 2 + +### Heading 3 + +#### Heading 4 + +##### Heading 5 + +###### Heading 6 + +# Trailing hash # + +## Multiple trailing ## + +### Mixed # content ## with ### trailing #### + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@0..1 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..11 " Heading 1" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@11..12 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@12..13 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@13..15 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..25 " Heading 2" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@25..26 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@26..27 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@27..30 "###" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..40 " Heading 3" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@40..41 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@41..42 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@42..46 "####" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@46..56 " Heading 4" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@56..57 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@57..58 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@58..63 "#####" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@63..73 " Heading 5" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@73..74 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@74..75 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@75..81 "######" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@81..91 " Heading 6" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@91..92 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@92..93 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@93..94 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@94..108 " Trailing hash" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [ + MdHash { + hash_token: HASH@108..110 "#" [Skipped(" ")] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@110..111 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@111..112 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@112..114 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@114..132 " Multiple trailing" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [ + MdHash { + hash_token: HASH@132..135 "##" [Skipped(" ")] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@135..136 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@136..137 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@137..140 "###" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@140..147 " Mixed " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@147..148 "#" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@148..157 " content " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@157..159 "##" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@159..165 " with " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@165..168 "###" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@168..177 " trailing" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [ + MdHash { + hash_token: HASH@177..182 "####" [Skipped(" ")] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@182..183 "\n" [] [], + }, + ], + eof_token: EOF@183..183 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..183 + 0: (empty) + 1: MD_BLOCK_LIST@0..183 + 0: MD_HEADER@0..11 + 0: MD_HASH_LIST@0..1 + 0: MD_HASH@0..1 + 0: HASH@0..1 "#" [] [] + 1: MD_PARAGRAPH@1..11 + 0: MD_INLINE_ITEM_LIST@1..11 + 0: MD_TEXTUAL@1..11 + 0: MD_TEXTUAL_LITERAL@1..11 " Heading 1" [] [] + 1: (empty) + 2: MD_HASH_LIST@11..11 + 1: MD_NEWLINE@11..12 + 0: NEWLINE@11..12 "\n" [] [] + 2: MD_NEWLINE@12..13 + 0: NEWLINE@12..13 "\n" [] [] + 3: MD_HEADER@13..25 + 0: MD_HASH_LIST@13..15 + 0: MD_HASH@13..15 + 0: HASH@13..15 "##" [] [] + 1: MD_PARAGRAPH@15..25 + 0: MD_INLINE_ITEM_LIST@15..25 + 0: MD_TEXTUAL@15..25 + 0: MD_TEXTUAL_LITERAL@15..25 " Heading 2" [] [] + 1: (empty) + 2: MD_HASH_LIST@25..25 + 4: MD_NEWLINE@25..26 + 0: NEWLINE@25..26 "\n" [] [] + 5: MD_NEWLINE@26..27 + 0: NEWLINE@26..27 "\n" [] [] + 6: MD_HEADER@27..40 + 0: MD_HASH_LIST@27..30 + 0: MD_HASH@27..30 + 0: HASH@27..30 "###" [] [] + 1: MD_PARAGRAPH@30..40 + 0: MD_INLINE_ITEM_LIST@30..40 + 0: MD_TEXTUAL@30..40 + 0: MD_TEXTUAL_LITERAL@30..40 " Heading 3" [] [] + 1: (empty) + 2: MD_HASH_LIST@40..40 + 7: MD_NEWLINE@40..41 + 0: NEWLINE@40..41 "\n" [] [] + 8: MD_NEWLINE@41..42 + 0: NEWLINE@41..42 "\n" [] [] + 9: MD_HEADER@42..56 + 0: MD_HASH_LIST@42..46 + 0: MD_HASH@42..46 + 0: HASH@42..46 "####" [] [] + 1: MD_PARAGRAPH@46..56 + 0: MD_INLINE_ITEM_LIST@46..56 + 0: MD_TEXTUAL@46..56 + 0: MD_TEXTUAL_LITERAL@46..56 " Heading 4" [] [] + 1: (empty) + 2: MD_HASH_LIST@56..56 + 10: MD_NEWLINE@56..57 + 0: NEWLINE@56..57 "\n" [] [] + 11: MD_NEWLINE@57..58 + 0: NEWLINE@57..58 "\n" [] [] + 12: MD_HEADER@58..73 + 0: MD_HASH_LIST@58..63 + 0: MD_HASH@58..63 + 0: HASH@58..63 "#####" [] [] + 1: MD_PARAGRAPH@63..73 + 0: MD_INLINE_ITEM_LIST@63..73 + 0: MD_TEXTUAL@63..73 + 0: MD_TEXTUAL_LITERAL@63..73 " Heading 5" [] [] + 1: (empty) + 2: MD_HASH_LIST@73..73 + 13: MD_NEWLINE@73..74 + 0: NEWLINE@73..74 "\n" [] [] + 14: MD_NEWLINE@74..75 + 0: NEWLINE@74..75 "\n" [] [] + 15: MD_HEADER@75..91 + 0: MD_HASH_LIST@75..81 + 0: MD_HASH@75..81 + 0: HASH@75..81 "######" [] [] + 1: MD_PARAGRAPH@81..91 + 0: MD_INLINE_ITEM_LIST@81..91 + 0: MD_TEXTUAL@81..91 + 0: MD_TEXTUAL_LITERAL@81..91 " Heading 6" [] [] + 1: (empty) + 2: MD_HASH_LIST@91..91 + 16: MD_NEWLINE@91..92 + 0: NEWLINE@91..92 "\n" [] [] + 17: MD_NEWLINE@92..93 + 0: NEWLINE@92..93 "\n" [] [] + 18: MD_HEADER@93..110 + 0: MD_HASH_LIST@93..94 + 0: MD_HASH@93..94 + 0: HASH@93..94 "#" [] [] + 1: MD_PARAGRAPH@94..108 + 0: MD_INLINE_ITEM_LIST@94..108 + 0: MD_TEXTUAL@94..108 + 0: MD_TEXTUAL_LITERAL@94..108 " Trailing hash" [] [] + 1: (empty) + 2: MD_HASH_LIST@108..110 + 0: MD_HASH@108..110 + 0: HASH@108..110 "#" [Skipped(" ")] [] + 19: MD_NEWLINE@110..111 + 0: NEWLINE@110..111 "\n" [] [] + 20: MD_NEWLINE@111..112 + 0: NEWLINE@111..112 "\n" [] [] + 21: MD_HEADER@112..135 + 0: MD_HASH_LIST@112..114 + 0: MD_HASH@112..114 + 0: HASH@112..114 "##" [] [] + 1: MD_PARAGRAPH@114..132 + 0: MD_INLINE_ITEM_LIST@114..132 + 0: MD_TEXTUAL@114..132 + 0: MD_TEXTUAL_LITERAL@114..132 " Multiple trailing" [] [] + 1: (empty) + 2: MD_HASH_LIST@132..135 + 0: MD_HASH@132..135 + 0: HASH@132..135 "##" [Skipped(" ")] [] + 22: MD_NEWLINE@135..136 + 0: NEWLINE@135..136 "\n" [] [] + 23: MD_NEWLINE@136..137 + 0: NEWLINE@136..137 "\n" [] [] + 24: MD_HEADER@137..182 + 0: MD_HASH_LIST@137..140 + 0: MD_HASH@137..140 + 0: HASH@137..140 "###" [] [] + 1: MD_PARAGRAPH@140..177 + 0: MD_INLINE_ITEM_LIST@140..177 + 0: MD_TEXTUAL@140..147 + 0: MD_TEXTUAL_LITERAL@140..147 " Mixed " [] [] + 1: MD_TEXTUAL@147..148 + 0: MD_TEXTUAL_LITERAL@147..148 "#" [] [] + 2: MD_TEXTUAL@148..157 + 0: MD_TEXTUAL_LITERAL@148..157 " content " [] [] + 3: MD_TEXTUAL@157..159 + 0: MD_TEXTUAL_LITERAL@157..159 "##" [] [] + 4: MD_TEXTUAL@159..165 + 0: MD_TEXTUAL_LITERAL@159..165 " with " [] [] + 5: MD_TEXTUAL@165..168 + 0: MD_TEXTUAL_LITERAL@165..168 "###" [] [] + 6: MD_TEXTUAL@168..177 + 0: MD_TEXTUAL_LITERAL@168..177 " trailing" [] [] + 1: (empty) + 2: MD_HASH_LIST@177..182 + 0: MD_HASH@177..182 + 0: HASH@177..182 "####" [Skipped(" ")] [] + 25: MD_NEWLINE@182..183 + 0: NEWLINE@182..183 "\n" [] [] + 2: EOF@183..183 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/html_block.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/html_block.md new file mode 100644 index 000000000000..8c96bb22befc --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/html_block.md @@ -0,0 +1,6 @@ +
    +This is an HTML block. +It continues until blank line. +
    + +Next paragraph after blank line. diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/html_block.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/html_block.md.snap new file mode 100644 index 000000000000..a0dae7edfdb3 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/html_block.md.snap @@ -0,0 +1,127 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +
    +This is an HTML block. +It continues until blank line. +
    + +Next paragraph after blank line. + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdHtmlBlock { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..1 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..4 "div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@4..5 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@5..6 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@6..28 "This is an HTML block." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..29 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..59 "It continues until blank line." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@59..60 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@60..61 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@61..65 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@65..66 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@66..67 "\n" [] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@67..68 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..100 "Next paragraph after blank line." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@100..101 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@101..101 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..101 + 0: (empty) + 1: MD_BLOCK_LIST@0..101 + 0: MD_HTML_BLOCK@0..67 + 0: MD_INLINE_ITEM_LIST@0..67 + 0: MD_TEXTUAL@0..1 + 0: MD_TEXTUAL_LITERAL@0..1 "<" [] [] + 1: MD_TEXTUAL@1..4 + 0: MD_TEXTUAL_LITERAL@1..4 "div" [] [] + 2: MD_TEXTUAL@4..5 + 0: MD_TEXTUAL_LITERAL@4..5 ">" [] [] + 3: MD_TEXTUAL@5..6 + 0: MD_TEXTUAL_LITERAL@5..6 "\n" [] [] + 4: MD_TEXTUAL@6..28 + 0: MD_TEXTUAL_LITERAL@6..28 "This is an HTML block." [] [] + 5: MD_TEXTUAL@28..29 + 0: MD_TEXTUAL_LITERAL@28..29 "\n" [] [] + 6: MD_TEXTUAL@29..59 + 0: MD_TEXTUAL_LITERAL@29..59 "It continues until blank line." [] [] + 7: MD_TEXTUAL@59..60 + 0: MD_TEXTUAL_LITERAL@59..60 "\n" [] [] + 8: MD_TEXTUAL@60..61 + 0: MD_TEXTUAL_LITERAL@60..61 "<" [] [] + 9: MD_TEXTUAL@61..65 + 0: MD_TEXTUAL_LITERAL@61..65 "/div" [] [] + 10: MD_TEXTUAL@65..66 + 0: MD_TEXTUAL_LITERAL@65..66 ">" [] [] + 11: MD_TEXTUAL@66..67 + 0: MD_TEXTUAL_LITERAL@66..67 "\n" [] [] + 1: MD_NEWLINE@67..68 + 0: NEWLINE@67..68 "\n" [] [] + 2: MD_PARAGRAPH@68..101 + 0: MD_INLINE_ITEM_LIST@68..101 + 0: MD_TEXTUAL@68..100 + 0: MD_TEXTUAL_LITERAL@68..100 "Next paragraph after blank line." [] [] + 1: MD_TEXTUAL@100..101 + 0: MD_TEXTUAL_LITERAL@100..101 "\n" [] [] + 1: (empty) + 2: EOF@101..101 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/indent_code_block.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/indent_code_block.md new file mode 100644 index 000000000000..9dcdb50c8250 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/indent_code_block.md @@ -0,0 +1,8 @@ + function hello() { + console.log("indented"); + } + +Regular paragraph here. + + More code + continues here diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/indent_code_block.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/indent_code_block.md.snap new file mode 100644 index 000000000000..55c95bf17724 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/indent_code_block.md.snap @@ -0,0 +1,170 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` + function hello() { + console.log("indented"); + } + +Regular paragraph here. + + More code + continues here + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdIndentCodeBlock { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..18 "function hello" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@18..19 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@19..20 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@20..22 " {" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@22..23 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..28 " " [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..42 "console.log" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..43 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@43..53 "\"indented\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@53..54 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@54..55 ";" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@55..56 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..61 "}" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@61..62 "\n" [] [], + }, + ], + }, + MdNewline { + value_token: NEWLINE@62..63 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@63..86 "Regular paragraph here." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@86..87 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@87..88 "\n" [] [], + }, + MdIndentCodeBlock { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@88..101 "More code" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@101..102 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@102..120 "continues here" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@120..121 "\n" [] [], + }, + ], + }, + ], + eof_token: EOF@121..121 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..121 + 0: (empty) + 1: MD_BLOCK_LIST@0..121 + 0: MD_INDENT_CODE_BLOCK@0..62 + 0: MD_INLINE_ITEM_LIST@0..62 + 0: MD_TEXTUAL@0..18 + 0: MD_TEXTUAL_LITERAL@0..18 "function hello" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_TEXTUAL@18..19 + 0: MD_TEXTUAL_LITERAL@18..19 "(" [] [] + 2: MD_TEXTUAL@19..20 + 0: MD_TEXTUAL_LITERAL@19..20 ")" [] [] + 3: MD_TEXTUAL@20..22 + 0: MD_TEXTUAL_LITERAL@20..22 " {" [] [] + 4: MD_TEXTUAL@22..23 + 0: MD_TEXTUAL_LITERAL@22..23 "\n" [] [] + 5: MD_TEXTUAL@23..28 + 0: MD_TEXTUAL_LITERAL@23..28 " " [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 6: MD_TEXTUAL@28..42 + 0: MD_TEXTUAL_LITERAL@28..42 "console.log" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 7: MD_TEXTUAL@42..43 + 0: MD_TEXTUAL_LITERAL@42..43 "(" [] [] + 8: MD_TEXTUAL@43..53 + 0: MD_TEXTUAL_LITERAL@43..53 "\"indented\"" [] [] + 9: MD_TEXTUAL@53..54 + 0: MD_TEXTUAL_LITERAL@53..54 ")" [] [] + 10: MD_TEXTUAL@54..55 + 0: MD_TEXTUAL_LITERAL@54..55 ";" [] [] + 11: MD_TEXTUAL@55..56 + 0: MD_TEXTUAL_LITERAL@55..56 "\n" [] [] + 12: MD_TEXTUAL@56..61 + 0: MD_TEXTUAL_LITERAL@56..61 "}" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 13: MD_TEXTUAL@61..62 + 0: MD_TEXTUAL_LITERAL@61..62 "\n" [] [] + 1: MD_NEWLINE@62..63 + 0: NEWLINE@62..63 "\n" [] [] + 2: MD_PARAGRAPH@63..87 + 0: MD_INLINE_ITEM_LIST@63..87 + 0: MD_TEXTUAL@63..86 + 0: MD_TEXTUAL_LITERAL@63..86 "Regular paragraph here." [] [] + 1: MD_TEXTUAL@86..87 + 0: MD_TEXTUAL_LITERAL@86..87 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@87..88 + 0: NEWLINE@87..88 "\n" [] [] + 4: MD_INDENT_CODE_BLOCK@88..121 + 0: MD_INLINE_ITEM_LIST@88..121 + 0: MD_TEXTUAL@88..101 + 0: MD_TEXTUAL_LITERAL@88..101 "More code" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_TEXTUAL@101..102 + 0: MD_TEXTUAL_LITERAL@101..102 "\n" [] [] + 2: MD_TEXTUAL@102..120 + 0: MD_TEXTUAL_LITERAL@102..120 "continues here" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 3: MD_TEXTUAL@120..121 + 0: MD_TEXTUAL_LITERAL@120..121 "\n" [] [] + 2: EOF@121..121 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/indented_code_blank_lines.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/indented_code_blank_lines.md new file mode 100644 index 000000000000..8c556a8b92b5 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/indented_code_blank_lines.md @@ -0,0 +1,5 @@ + a + + b + + x diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/indented_code_blank_lines.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/indented_code_blank_lines.md.snap new file mode 100644 index 000000000000..eb0cb0ecf4f2 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/indented_code_blank_lines.md.snap @@ -0,0 +1,88 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` + a + + b + + x + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdIndentCodeBlock { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..5 "a" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@5..6 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@6..7 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@7..12 "b" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@12..13 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..14 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..19 " " [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@19..23 "x" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..24 "\n" [] [], + }, + ], + }, + ], + eof_token: EOF@24..24 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..24 + 0: (empty) + 1: MD_BLOCK_LIST@0..24 + 0: MD_INDENT_CODE_BLOCK@0..24 + 0: MD_INLINE_ITEM_LIST@0..24 + 0: MD_TEXTUAL@0..5 + 0: MD_TEXTUAL_LITERAL@0..5 "a" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_TEXTUAL@5..6 + 0: MD_TEXTUAL_LITERAL@5..6 "\n" [] [] + 2: MD_TEXTUAL@6..7 + 0: MD_TEXTUAL_LITERAL@6..7 "\n" [] [] + 3: MD_TEXTUAL@7..12 + 0: MD_TEXTUAL_LITERAL@7..12 "b" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 4: MD_TEXTUAL@12..13 + 0: MD_TEXTUAL_LITERAL@12..13 "\n" [] [] + 5: MD_TEXTUAL@13..14 + 0: MD_TEXTUAL_LITERAL@13..14 "\n" [] [] + 6: MD_TEXTUAL@14..19 + 0: MD_TEXTUAL_LITERAL@14..19 " " [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 7: MD_TEXTUAL@19..23 + 0: MD_TEXTUAL_LITERAL@19..23 "x" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 8: MD_TEXTUAL@23..24 + 0: MD_TEXTUAL_LITERAL@23..24 "\n" [] [] + 2: EOF@24..24 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_elements.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_elements.md new file mode 100644 index 000000000000..c0f5c4983ac7 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_elements.md @@ -0,0 +1,7 @@ +This has `inline code` in it. + +This is *italic* and this is **bold**. + +This is _also italic_ and __also bold__. + +Here is a [link](https://example.com) and an ![image](image.png). diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_elements.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_elements.md.snap new file mode 100644 index 000000000000..e1f710c9deb5 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_elements.md.snap @@ -0,0 +1,296 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has `inline code` in it. + +This is *italic* and this is **bold**. + +This is _also italic_ and __also bold__. + +Here is a [link](https://example.com) and an ![image](image.png). + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdInlineCode { + l_tick_token: BACKTICK@9..10 "`" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..21 "inline code" [] [], + }, + ], + r_tick_token: BACKTICK@21..22 "`" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@22..29 " in it." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..30 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@30..31 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..39 "This is " [] [], + }, + MdInlineItalic { + l_fence: STAR@39..40 "*" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..46 "italic" [] [], + }, + ], + r_fence: STAR@46..47 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@47..60 " and this is " [] [], + }, + MdInlineEmphasis { + l_fence: DOUBLE_STAR@60..62 "**" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@62..66 "bold" [] [], + }, + ], + r_fence: DOUBLE_STAR@66..68 "**" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..69 "." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@69..70 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@70..71 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@71..79 "This is " [] [], + }, + MdInlineItalic { + l_fence: UNDERSCORE@79..80 "_" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..91 "also italic" [] [], + }, + ], + r_fence: UNDERSCORE@91..92 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@92..97 " and " [] [], + }, + MdInlineEmphasis { + l_fence: DOUBLE_UNDERSCORE@97..99 "__" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@99..108 "also bold" [] [], + }, + ], + r_fence: DOUBLE_UNDERSCORE@108..110 "__" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@110..111 "." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@111..112 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@112..113 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@113..123 "Here is a " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@123..124 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@124..128 "link" [] [], + }, + ], + r_brack_token: R_BRACK@128..129 "]" [] [], + l_paren_token: L_PAREN@129..130 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@130..149 "https://example.com" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@149..150 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@150..158 " and an " [] [], + }, + MdInlineImage { + excl_token: BANG@158..159 "!" [] [], + l_brack_token: L_BRACK@159..160 "[" [] [], + alt: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@160..165 "image" [] [], + }, + ], + r_brack_token: R_BRACK@165..166 "]" [] [], + l_paren_token: L_PAREN@166..167 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@167..176 "image.png" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@176..177 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@177..178 "." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@178..179 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@179..179 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..179 + 0: (empty) + 1: MD_BLOCK_LIST@0..179 + 0: MD_PARAGRAPH@0..30 + 0: MD_INLINE_ITEM_LIST@0..30 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_INLINE_CODE@9..22 + 0: BACKTICK@9..10 "`" [] [] + 1: MD_INLINE_ITEM_LIST@10..21 + 0: MD_TEXTUAL@10..21 + 0: MD_TEXTUAL_LITERAL@10..21 "inline code" [] [] + 2: BACKTICK@21..22 "`" [] [] + 2: MD_TEXTUAL@22..29 + 0: MD_TEXTUAL_LITERAL@22..29 " in it." [] [] + 3: MD_TEXTUAL@29..30 + 0: MD_TEXTUAL_LITERAL@29..30 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@30..31 + 0: NEWLINE@30..31 "\n" [] [] + 2: MD_PARAGRAPH@31..70 + 0: MD_INLINE_ITEM_LIST@31..70 + 0: MD_TEXTUAL@31..39 + 0: MD_TEXTUAL_LITERAL@31..39 "This is " [] [] + 1: MD_INLINE_ITALIC@39..47 + 0: STAR@39..40 "*" [] [] + 1: MD_INLINE_ITEM_LIST@40..46 + 0: MD_TEXTUAL@40..46 + 0: MD_TEXTUAL_LITERAL@40..46 "italic" [] [] + 2: STAR@46..47 "*" [] [] + 2: MD_TEXTUAL@47..60 + 0: MD_TEXTUAL_LITERAL@47..60 " and this is " [] [] + 3: MD_INLINE_EMPHASIS@60..68 + 0: DOUBLE_STAR@60..62 "**" [] [] + 1: MD_INLINE_ITEM_LIST@62..66 + 0: MD_TEXTUAL@62..66 + 0: MD_TEXTUAL_LITERAL@62..66 "bold" [] [] + 2: DOUBLE_STAR@66..68 "**" [] [] + 4: MD_TEXTUAL@68..69 + 0: MD_TEXTUAL_LITERAL@68..69 "." [] [] + 5: MD_TEXTUAL@69..70 + 0: MD_TEXTUAL_LITERAL@69..70 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@70..71 + 0: NEWLINE@70..71 "\n" [] [] + 4: MD_PARAGRAPH@71..112 + 0: MD_INLINE_ITEM_LIST@71..112 + 0: MD_TEXTUAL@71..79 + 0: MD_TEXTUAL_LITERAL@71..79 "This is " [] [] + 1: MD_INLINE_ITALIC@79..92 + 0: UNDERSCORE@79..80 "_" [] [] + 1: MD_INLINE_ITEM_LIST@80..91 + 0: MD_TEXTUAL@80..91 + 0: MD_TEXTUAL_LITERAL@80..91 "also italic" [] [] + 2: UNDERSCORE@91..92 "_" [] [] + 2: MD_TEXTUAL@92..97 + 0: MD_TEXTUAL_LITERAL@92..97 " and " [] [] + 3: MD_INLINE_EMPHASIS@97..110 + 0: DOUBLE_UNDERSCORE@97..99 "__" [] [] + 1: MD_INLINE_ITEM_LIST@99..108 + 0: MD_TEXTUAL@99..108 + 0: MD_TEXTUAL_LITERAL@99..108 "also bold" [] [] + 2: DOUBLE_UNDERSCORE@108..110 "__" [] [] + 4: MD_TEXTUAL@110..111 + 0: MD_TEXTUAL_LITERAL@110..111 "." [] [] + 5: MD_TEXTUAL@111..112 + 0: MD_TEXTUAL_LITERAL@111..112 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@112..113 + 0: NEWLINE@112..113 "\n" [] [] + 6: MD_PARAGRAPH@113..179 + 0: MD_INLINE_ITEM_LIST@113..179 + 0: MD_TEXTUAL@113..123 + 0: MD_TEXTUAL_LITERAL@113..123 "Here is a " [] [] + 1: MD_INLINE_LINK@123..150 + 0: L_BRACK@123..124 "[" [] [] + 1: MD_INLINE_ITEM_LIST@124..128 + 0: MD_TEXTUAL@124..128 + 0: MD_TEXTUAL_LITERAL@124..128 "link" [] [] + 2: R_BRACK@128..129 "]" [] [] + 3: L_PAREN@129..130 "(" [] [] + 4: MD_INLINE_ITEM_LIST@130..149 + 0: MD_TEXTUAL@130..149 + 0: MD_TEXTUAL_LITERAL@130..149 "https://example.com" [] [] + 5: (empty) + 6: R_PAREN@149..150 ")" [] [] + 2: MD_TEXTUAL@150..158 + 0: MD_TEXTUAL_LITERAL@150..158 " and an " [] [] + 3: MD_INLINE_IMAGE@158..177 + 0: BANG@158..159 "!" [] [] + 1: L_BRACK@159..160 "[" [] [] + 2: MD_INLINE_ITEM_LIST@160..165 + 0: MD_TEXTUAL@160..165 + 0: MD_TEXTUAL_LITERAL@160..165 "image" [] [] + 3: R_BRACK@165..166 "]" [] [] + 4: L_PAREN@166..167 "(" [] [] + 5: MD_INLINE_ITEM_LIST@167..176 + 0: MD_TEXTUAL@167..176 + 0: MD_TEXTUAL_LITERAL@167..176 "image.png" [] [] + 6: (empty) + 7: R_PAREN@176..177 ")" [] [] + 4: MD_TEXTUAL@177..178 + 0: MD_TEXTUAL_LITERAL@177..178 "." [] [] + 5: MD_TEXTUAL@178..179 + 0: MD_TEXTUAL_LITERAL@178..179 "\n" [] [] + 1: (empty) + 2: EOF@179..179 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html.md new file mode 100644 index 000000000000..78c99f0396ef --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html.md @@ -0,0 +1,11 @@ +This has inline HTML in it. + +Link with anchor text. + +Self-closing:
    and + +Comment: inline. + +PI: here. + +CDATA: here. diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html.md.snap new file mode 100644 index 000000000000..3bc72109a7df --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html.md.snap @@ -0,0 +1,470 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has inline HTML in it. + +Link with anchor text. + +Self-closing:
    and + +Comment: inline. + +PI: here. + +CDATA: here. + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..14 "span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..15 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..26 "inline HTML" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@26..27 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@27..32 "/span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..33 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@33..40 " in it." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..41 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@41..42 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..52 "Link with " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@52..53 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@53..65 "a href=\"url\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@65..66 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@66..72 "anchor" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@72..73 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@73..75 "/a" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@75..76 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@76..82 " text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@82..83 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@83..84 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@84..88 "Self" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@88..89 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@89..98 "closing: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@98..99 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@99..102 "br/" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@102..103 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@103..108 " and " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@108..109 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@109..121 "img src=\"x\"/" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@121..122 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@122..123 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@123..124 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@124..133 "Comment: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@133..134 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@134..135 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@135..136 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@136..137 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@137..146 " comment " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@146..147 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@147..148 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@148..149 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@149..157 " inline." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@157..158 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@158..159 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@159..163 "PI: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@163..164 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@164..183 "?xml version=\"1.0\"?" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@183..184 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@184..190 " here." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@190..191 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@191..192 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@192..199 "CDATA: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@199..200 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@200..201 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@201..202 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@202..207 "CDATA" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@207..208 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@208..212 "text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@212..213 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@213..214 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@214..215 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@215..221 " here." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@221..222 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@222..222 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..222 + 0: (empty) + 1: MD_BLOCK_LIST@0..222 + 0: MD_PARAGRAPH@0..41 + 0: MD_INLINE_ITEM_LIST@0..41 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_INLINE_HTML@9..15 + 0: MD_INLINE_ITEM_LIST@9..15 + 0: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "<" [] [] + 1: MD_TEXTUAL@10..14 + 0: MD_TEXTUAL_LITERAL@10..14 "span" [] [] + 2: MD_TEXTUAL@14..15 + 0: MD_TEXTUAL_LITERAL@14..15 ">" [] [] + 2: MD_TEXTUAL@15..26 + 0: MD_TEXTUAL_LITERAL@15..26 "inline HTML" [] [] + 3: MD_INLINE_HTML@26..33 + 0: MD_INLINE_ITEM_LIST@26..33 + 0: MD_TEXTUAL@26..27 + 0: MD_TEXTUAL_LITERAL@26..27 "<" [] [] + 1: MD_TEXTUAL@27..32 + 0: MD_TEXTUAL_LITERAL@27..32 "/span" [] [] + 2: MD_TEXTUAL@32..33 + 0: MD_TEXTUAL_LITERAL@32..33 ">" [] [] + 4: MD_TEXTUAL@33..40 + 0: MD_TEXTUAL_LITERAL@33..40 " in it." [] [] + 5: MD_TEXTUAL@40..41 + 0: MD_TEXTUAL_LITERAL@40..41 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@41..42 + 0: NEWLINE@41..42 "\n" [] [] + 2: MD_PARAGRAPH@42..83 + 0: MD_INLINE_ITEM_LIST@42..83 + 0: MD_TEXTUAL@42..52 + 0: MD_TEXTUAL_LITERAL@42..52 "Link with " [] [] + 1: MD_INLINE_HTML@52..66 + 0: MD_INLINE_ITEM_LIST@52..66 + 0: MD_TEXTUAL@52..53 + 0: MD_TEXTUAL_LITERAL@52..53 "<" [] [] + 1: MD_TEXTUAL@53..65 + 0: MD_TEXTUAL_LITERAL@53..65 "a href=\"url\"" [] [] + 2: MD_TEXTUAL@65..66 + 0: MD_TEXTUAL_LITERAL@65..66 ">" [] [] + 2: MD_TEXTUAL@66..72 + 0: MD_TEXTUAL_LITERAL@66..72 "anchor" [] [] + 3: MD_INLINE_HTML@72..76 + 0: MD_INLINE_ITEM_LIST@72..76 + 0: MD_TEXTUAL@72..73 + 0: MD_TEXTUAL_LITERAL@72..73 "<" [] [] + 1: MD_TEXTUAL@73..75 + 0: MD_TEXTUAL_LITERAL@73..75 "/a" [] [] + 2: MD_TEXTUAL@75..76 + 0: MD_TEXTUAL_LITERAL@75..76 ">" [] [] + 4: MD_TEXTUAL@76..82 + 0: MD_TEXTUAL_LITERAL@76..82 " text." [] [] + 5: MD_TEXTUAL@82..83 + 0: MD_TEXTUAL_LITERAL@82..83 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@83..84 + 0: NEWLINE@83..84 "\n" [] [] + 4: MD_PARAGRAPH@84..123 + 0: MD_INLINE_ITEM_LIST@84..123 + 0: MD_TEXTUAL@84..88 + 0: MD_TEXTUAL_LITERAL@84..88 "Self" [] [] + 1: MD_TEXTUAL@88..89 + 0: MD_TEXTUAL_LITERAL@88..89 "-" [] [] + 2: MD_TEXTUAL@89..98 + 0: MD_TEXTUAL_LITERAL@89..98 "closing: " [] [] + 3: MD_INLINE_HTML@98..103 + 0: MD_INLINE_ITEM_LIST@98..103 + 0: MD_TEXTUAL@98..99 + 0: MD_TEXTUAL_LITERAL@98..99 "<" [] [] + 1: MD_TEXTUAL@99..102 + 0: MD_TEXTUAL_LITERAL@99..102 "br/" [] [] + 2: MD_TEXTUAL@102..103 + 0: MD_TEXTUAL_LITERAL@102..103 ">" [] [] + 4: MD_TEXTUAL@103..108 + 0: MD_TEXTUAL_LITERAL@103..108 " and " [] [] + 5: MD_INLINE_HTML@108..122 + 0: MD_INLINE_ITEM_LIST@108..122 + 0: MD_TEXTUAL@108..109 + 0: MD_TEXTUAL_LITERAL@108..109 "<" [] [] + 1: MD_TEXTUAL@109..121 + 0: MD_TEXTUAL_LITERAL@109..121 "img src=\"x\"/" [] [] + 2: MD_TEXTUAL@121..122 + 0: MD_TEXTUAL_LITERAL@121..122 ">" [] [] + 6: MD_TEXTUAL@122..123 + 0: MD_TEXTUAL_LITERAL@122..123 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@123..124 + 0: NEWLINE@123..124 "\n" [] [] + 6: MD_PARAGRAPH@124..158 + 0: MD_INLINE_ITEM_LIST@124..158 + 0: MD_TEXTUAL@124..133 + 0: MD_TEXTUAL_LITERAL@124..133 "Comment: " [] [] + 1: MD_INLINE_HTML@133..149 + 0: MD_INLINE_ITEM_LIST@133..149 + 0: MD_TEXTUAL@133..134 + 0: MD_TEXTUAL_LITERAL@133..134 "<" [] [] + 1: MD_TEXTUAL@134..135 + 0: MD_TEXTUAL_LITERAL@134..135 "!" [] [] + 2: MD_TEXTUAL@135..136 + 0: MD_TEXTUAL_LITERAL@135..136 "-" [] [] + 3: MD_TEXTUAL@136..137 + 0: MD_TEXTUAL_LITERAL@136..137 "-" [] [] + 4: MD_TEXTUAL@137..146 + 0: MD_TEXTUAL_LITERAL@137..146 " comment " [] [] + 5: MD_TEXTUAL@146..147 + 0: MD_TEXTUAL_LITERAL@146..147 "-" [] [] + 6: MD_TEXTUAL@147..148 + 0: MD_TEXTUAL_LITERAL@147..148 "-" [] [] + 7: MD_TEXTUAL@148..149 + 0: MD_TEXTUAL_LITERAL@148..149 ">" [] [] + 2: MD_TEXTUAL@149..157 + 0: MD_TEXTUAL_LITERAL@149..157 " inline." [] [] + 3: MD_TEXTUAL@157..158 + 0: MD_TEXTUAL_LITERAL@157..158 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@158..159 + 0: NEWLINE@158..159 "\n" [] [] + 8: MD_PARAGRAPH@159..191 + 0: MD_INLINE_ITEM_LIST@159..191 + 0: MD_TEXTUAL@159..163 + 0: MD_TEXTUAL_LITERAL@159..163 "PI: " [] [] + 1: MD_INLINE_HTML@163..184 + 0: MD_INLINE_ITEM_LIST@163..184 + 0: MD_TEXTUAL@163..164 + 0: MD_TEXTUAL_LITERAL@163..164 "<" [] [] + 1: MD_TEXTUAL@164..183 + 0: MD_TEXTUAL_LITERAL@164..183 "?xml version=\"1.0\"?" [] [] + 2: MD_TEXTUAL@183..184 + 0: MD_TEXTUAL_LITERAL@183..184 ">" [] [] + 2: MD_TEXTUAL@184..190 + 0: MD_TEXTUAL_LITERAL@184..190 " here." [] [] + 3: MD_TEXTUAL@190..191 + 0: MD_TEXTUAL_LITERAL@190..191 "\n" [] [] + 1: (empty) + 9: MD_NEWLINE@191..192 + 0: NEWLINE@191..192 "\n" [] [] + 10: MD_PARAGRAPH@192..222 + 0: MD_INLINE_ITEM_LIST@192..222 + 0: MD_TEXTUAL@192..199 + 0: MD_TEXTUAL_LITERAL@192..199 "CDATA: " [] [] + 1: MD_INLINE_HTML@199..215 + 0: MD_INLINE_ITEM_LIST@199..215 + 0: MD_TEXTUAL@199..200 + 0: MD_TEXTUAL_LITERAL@199..200 "<" [] [] + 1: MD_TEXTUAL@200..201 + 0: MD_TEXTUAL_LITERAL@200..201 "!" [] [] + 2: MD_TEXTUAL@201..202 + 0: MD_TEXTUAL_LITERAL@201..202 "[" [] [] + 3: MD_TEXTUAL@202..207 + 0: MD_TEXTUAL_LITERAL@202..207 "CDATA" [] [] + 4: MD_TEXTUAL@207..208 + 0: MD_TEXTUAL_LITERAL@207..208 "[" [] [] + 5: MD_TEXTUAL@208..212 + 0: MD_TEXTUAL_LITERAL@208..212 "text" [] [] + 6: MD_TEXTUAL@212..213 + 0: MD_TEXTUAL_LITERAL@212..213 "]" [] [] + 7: MD_TEXTUAL@213..214 + 0: MD_TEXTUAL_LITERAL@213..214 "]" [] [] + 8: MD_TEXTUAL@214..215 + 0: MD_TEXTUAL_LITERAL@214..215 ">" [] [] + 2: MD_TEXTUAL@215..221 + 0: MD_TEXTUAL_LITERAL@215..221 " here." [] [] + 3: MD_TEXTUAL@221..222 + 0: MD_TEXTUAL_LITERAL@221..222 "\n" [] [] + 1: (empty) + 2: EOF@222..222 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_edge_cases.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_edge_cases.md new file mode 100644 index 000000000000..2ece6b29a323 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_edge_cases.md @@ -0,0 +1,63 @@ +# Inline HTML Edge Cases + +## Basic Open Tags +Simple tag here. +With attrs
    content
    end. + +## Self-Closing Tags +Line break:
    here. +With space:
    there. +Input: field. + +## Closing Tags +Open bold text. +Nested double tags. + +## Comments +Simple inline. +Empty comment. +With dashes here. +Leading dash allowed. + +## Processing Instructions +XML: present. +PHP: code. + +## CDATA Sections +Data: here. +Special: &"]]> chars. + +## Declarations +Standard: declaration. +Lowercase: declaration. +Extended: test. + +## Attributes with Quotes +Single:
    text
    end. +Double:
    text
    end. +Both:
    text
    end. + +## Attributes with Special Chars +Spaces:
    text
    end. +Multiple:
    text
    end. +Unquoted:
    text
    end. +Underscore/colon:
    text
    end. +Boolean:
    text
    end. + +## Newline Cases (should parse as inline HTML) +Allowed:
    ok
    tag. +Allowed:
    ok
    tag. + +## Priority - Autolinks Should Win +URL: link. +Email: address. + +## Tag Names with Hyphens +Custom: content element. +Multiple: test tag. + +## Empty Tags +Empty open:
    tags. +Self close:
    break. diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_edge_cases.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_edge_cases.md.snap new file mode 100644 index 000000000000..1b0a0be924d5 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_edge_cases.md.snap @@ -0,0 +1,2769 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +# Inline HTML Edge Cases + +## Basic Open Tags +Simple tag here. +With attrs
    content
    end. + +## Self-Closing Tags +Line break:
    here. +With space:
    there. +Input: field. + +## Closing Tags +Open bold text. +Nested double tags. + +## Comments +Simple inline. +Empty comment. +With dashes here. +Leading dash allowed. + +## Processing Instructions +XML: present. +PHP: code. + +## CDATA Sections +Data: here. +Special: &"]]> chars. + +## Declarations +Standard: declaration. +Lowercase: declaration. +Extended: test. + +## Attributes with Quotes +Single:
    text
    end. +Double:
    text
    end. +Both:
    text
    end. + +## Attributes with Special Chars +Spaces:
    text
    end. +Multiple:
    text
    end. +Unquoted:
    text
    end. +Underscore/colon:
    text
    end. +Boolean:
    text
    end. + +## Newline Cases (should parse as inline HTML) +Allowed:
    ok
    tag. +Allowed:
    ok
    tag. + +## Priority - Autolinks Should Win +URL: link. +Email: address. + +## Tag Names with Hyphens +Custom: content element. +Multiple: test tag. + +## Empty Tags +Empty open:
    tags. +Self close:
    break. + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@0..1 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..24 " Inline HTML Edge Cases" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@24..25 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@25..26 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@26..28 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..44 " Basic Open Tags" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@44..45 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@45..52 "Simple " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@52..53 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@53..57 "span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@57..58 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..61 "tag" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@61..62 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@62..67 "/span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@67..68 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..74 " here." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@74..75 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@75..86 "With attrs " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@86..87 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@87..103 "div class=\"test\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@103..104 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@104..111 "content" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@111..112 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@112..116 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@116..117 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@117..122 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@122..123 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@123..124 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@124..126 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@126..131 " Self" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@131..132 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@132..144 "Closing Tags" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@144..145 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@145..157 "Line break: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@157..158 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@158..161 "br/" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@161..162 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@162..168 " here." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@168..169 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@169..181 "With space: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@181..182 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@182..186 "br /" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@186..187 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@187..194 " there." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@194..195 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@195..202 "Input: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@202..203 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@203..222 "input type=\"text\" /" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@222..223 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@223..230 " field." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@230..231 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@231..232 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@232..234 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@234..247 " Closing Tags" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@247..248 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@248..253 "Open " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@253..254 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@254..255 "b" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@255..256 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@256..260 "bold" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@260..261 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@261..263 "/b" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@263..264 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@264..270 " text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@270..271 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@271..278 "Nested " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@278..279 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@279..283 "span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@283..284 ">" [] [], + }, + ], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@284..285 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@285..291 "strong" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@291..292 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@292..298 "double" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@298..299 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@299..306 "/strong" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@306..307 ">" [] [], + }, + ], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@307..308 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@308..313 "/span" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@313..314 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@314..320 " tags." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@320..321 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@321..322 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@322..324 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@324..333 " Comments" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@333..334 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@334..341 "Simple " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@341..342 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@342..343 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@343..344 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@344..345 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@345..354 " comment " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@354..355 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@355..356 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@356..357 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@357..365 " inline." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@365..366 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@366..372 "Empty " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@372..373 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@373..374 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@374..375 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@375..376 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@376..377 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@377..378 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@378..379 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@379..380 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@380..389 " comment." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@389..390 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@390..402 "With dashes " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@402..403 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@403..404 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@404..405 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@405..406 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@406..409 "foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@409..410 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@410..413 "bar" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@413..414 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@414..415 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@415..416 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@416..422 " here." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@422..423 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@423..436 "Leading dash " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@436..437 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@437..438 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@438..439 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@439..440 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@440..441 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@441..442 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@442..443 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@443..444 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@444..445 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@445..454 " allowed." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@454..455 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@455..456 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@456..458 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@458..482 " Processing Instructions" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@482..483 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@483..488 "XML: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@488..489 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@489..508 "?xml version=\"1.0\"?" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@508..509 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@509..518 " present." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@518..519 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@519..524 "PHP: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@524..525 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@525..544 "?php echo \"test\"; ?" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@544..545 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@545..551 " code." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@551..552 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@552..553 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@553..555 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@555..570 " CDATA Sections" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@570..571 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@571..577 "Data: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@577..578 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@578..579 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@579..580 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@580..585 "CDATA" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@585..586 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@586..595 "some text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@595..596 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@596..597 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@597..598 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@598..604 " here." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@604..605 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@605..614 "Special: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@614..615 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@615..616 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@616..617 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@617..622 "CDATA" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@622..623 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@623..624 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@624..625 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@625..627 "&\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@627..628 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@628..629 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@629..630 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@630..637 " chars." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@637..638 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@638..639 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@639..641 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@641..654 " Declarations" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@654..655 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@655..665 "Standard: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@665..666 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@666..667 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@667..679 "DOCTYPE html" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@679..680 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@680..693 " declaration." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@693..694 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@694..705 "Lowercase: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@705..706 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@706..707 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@707..719 "doctype html" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@719..720 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@720..733 " declaration." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@733..734 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@734..744 "Extended: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@744..745 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@745..746 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@746..767 "DOCTYPE HTML PUBLIC \"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@767..768 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@768..793 "//W3C//DTD HTML 4.01//EN\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@793..794 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@794..800 " test." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@800..801 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@801..802 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@802..804 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@804..827 " Attributes with Quotes" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@827..828 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@828..836 "Single: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@836..837 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@837..855 "div class='quoted'" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@855..856 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@856..860 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@860..861 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@861..865 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@865..866 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@866..871 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@871..872 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@872..880 "Double: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@880..881 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@881..899 "div class=\"quoted\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@899..900 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@900..904 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@904..905 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@905..909 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@909..910 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@910..915 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@915..916 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@916..922 "Both: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@922..923 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@923..951 "div class=\"outer\" id='inner'" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@951..952 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@952..956 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@956..957 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@957..961 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@961..962 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@962..967 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@967..968 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@968..969 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@969..971 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@971..1001 " Attributes with Special Chars" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@1001..1002 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1002..1010 "Spaces: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1010..1011 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1011..1019 "div data" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1019..1020 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1020..1039 "value=\"with spaces\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1039..1040 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1040..1044 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1044..1045 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1045..1049 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1049..1050 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1050..1055 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1055..1056 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1056..1066 "Multiple: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1066..1067 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1067..1092 "div class=\"a\" id=\"b\" data" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1092..1093 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1093..1098 "x=\"c\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1098..1099 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1099..1103 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1103..1104 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1104..1108 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1108..1109 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1109..1114 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1114..1115 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1115..1125 "Unquoted: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1125..1126 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1126..1134 "div data" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1134..1135 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1135..1145 "x=foo data" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1145..1146 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1146..1151 "y=bar" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1151..1152 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1152..1155 "baz" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1155..1156 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1156..1160 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1160..1161 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1161..1165 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1165..1166 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1166..1171 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1171..1172 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1172..1190 "Underscore/colon: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1190..1191 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1191..1195 "div " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1195..1196 "_" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1196..1205 "x=1 x:y=2" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1205..1206 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1206..1210 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1210..1211 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1211..1215 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1215..1216 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1216..1221 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1221..1222 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1222..1231 "Boolean: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1231..1232 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1232..1244 "div disabled" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1244..1245 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1245..1249 "text" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1249..1250 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1250..1254 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1254..1255 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1255..1260 " end." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1260..1261 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@1261..1262 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@1262..1264 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1264..1279 " Newline Cases " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1279..1280 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1280..1307 "should parse as inline HTML" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1307..1308 ")" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@1308..1309 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1309..1318 "Allowed: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1318..1319 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1319..1322 "div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1322..1323 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1323..1335 "class=\"test\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1335..1336 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1336..1338 "ok" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1338..1339 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1339..1343 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1343..1344 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1344..1349 " tag." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1349..1350 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1350..1359 "Allowed: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1359..1360 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1360..1373 "div class=\"a\"" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1373..1374 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1374..1375 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1375..1377 "ok" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1377..1378 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1378..1382 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1382..1383 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1383..1388 " tag." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1388..1389 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@1389..1390 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@1390..1392 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1392..1402 " Priority " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1402..1403 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1403..1424 " Autolinks Should Win" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@1424..1425 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1425..1430 "URL: " [] [], + }, + MdAutolink { + l_angle_token: L_ANGLE@1430..1431 "<" [] [], + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1431..1450 "https://example.com" [] [], + }, + ], + r_angle_token: R_ANGLE@1450..1451 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1451..1457 " link." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1457..1458 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1458..1465 "Email: " [] [], + }, + MdAutolink { + l_angle_token: L_ANGLE@1465..1466 "<" [] [], + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1466..1482 "user@example.com" [] [], + }, + ], + r_angle_token: R_ANGLE@1482..1483 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1483..1492 " address." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1492..1493 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@1493..1494 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@1494..1496 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1496..1519 " Tag Names with Hyphens" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@1519..1520 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1520..1528 "Custom: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1528..1529 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1529..1531 "my" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1531..1532 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1532..1541 "component" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1541..1542 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1542..1549 "content" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1549..1550 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1550..1553 "/my" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1553..1554 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1554..1563 "component" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1563..1564 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1564..1573 " element." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1573..1574 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1574..1584 "Multiple: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1584..1585 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1585..1587 "my" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1587..1588 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1588..1594 "custom" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1594..1595 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1595..1602 "element" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1602..1603 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1603..1607 "test" [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1607..1608 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1608..1611 "/my" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1611..1612 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1612..1618 "custom" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1618..1619 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1619..1626 "element" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1626..1627 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1627..1632 " tag." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1632..1633 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@1633..1634 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@1634..1636 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1636..1647 " Empty Tags" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@1647..1648 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1648..1660 "Empty open: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1660..1661 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1661..1664 "div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1664..1665 ">" [] [], + }, + ], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1665..1666 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1666..1670 "/div" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1670..1671 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1671..1677 " tags." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1677..1678 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1678..1690 "Self close: " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1690..1691 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1691..1694 "br/" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1694..1695 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1695..1702 " break." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1702..1703 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@1703..1703 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..1703 + 0: (empty) + 1: MD_BLOCK_LIST@0..1703 + 0: MD_HEADER@0..24 + 0: MD_HASH_LIST@0..1 + 0: MD_HASH@0..1 + 0: HASH@0..1 "#" [] [] + 1: MD_PARAGRAPH@1..24 + 0: MD_INLINE_ITEM_LIST@1..24 + 0: MD_TEXTUAL@1..24 + 0: MD_TEXTUAL_LITERAL@1..24 " Inline HTML Edge Cases" [] [] + 1: (empty) + 2: MD_HASH_LIST@24..24 + 1: MD_NEWLINE@24..25 + 0: NEWLINE@24..25 "\n" [] [] + 2: MD_NEWLINE@25..26 + 0: NEWLINE@25..26 "\n" [] [] + 3: MD_HEADER@26..44 + 0: MD_HASH_LIST@26..28 + 0: MD_HASH@26..28 + 0: HASH@26..28 "##" [] [] + 1: MD_PARAGRAPH@28..44 + 0: MD_INLINE_ITEM_LIST@28..44 + 0: MD_TEXTUAL@28..44 + 0: MD_TEXTUAL_LITERAL@28..44 " Basic Open Tags" [] [] + 1: (empty) + 2: MD_HASH_LIST@44..44 + 4: MD_NEWLINE@44..45 + 0: NEWLINE@44..45 "\n" [] [] + 5: MD_PARAGRAPH@45..123 + 0: MD_INLINE_ITEM_LIST@45..123 + 0: MD_TEXTUAL@45..52 + 0: MD_TEXTUAL_LITERAL@45..52 "Simple " [] [] + 1: MD_INLINE_HTML@52..58 + 0: MD_INLINE_ITEM_LIST@52..58 + 0: MD_TEXTUAL@52..53 + 0: MD_TEXTUAL_LITERAL@52..53 "<" [] [] + 1: MD_TEXTUAL@53..57 + 0: MD_TEXTUAL_LITERAL@53..57 "span" [] [] + 2: MD_TEXTUAL@57..58 + 0: MD_TEXTUAL_LITERAL@57..58 ">" [] [] + 2: MD_TEXTUAL@58..61 + 0: MD_TEXTUAL_LITERAL@58..61 "tag" [] [] + 3: MD_INLINE_HTML@61..68 + 0: MD_INLINE_ITEM_LIST@61..68 + 0: MD_TEXTUAL@61..62 + 0: MD_TEXTUAL_LITERAL@61..62 "<" [] [] + 1: MD_TEXTUAL@62..67 + 0: MD_TEXTUAL_LITERAL@62..67 "/span" [] [] + 2: MD_TEXTUAL@67..68 + 0: MD_TEXTUAL_LITERAL@67..68 ">" [] [] + 4: MD_TEXTUAL@68..74 + 0: MD_TEXTUAL_LITERAL@68..74 " here." [] [] + 5: MD_TEXTUAL@74..75 + 0: MD_TEXTUAL_LITERAL@74..75 "\n" [] [] + 6: MD_TEXTUAL@75..86 + 0: MD_TEXTUAL_LITERAL@75..86 "With attrs " [] [] + 7: MD_INLINE_HTML@86..104 + 0: MD_INLINE_ITEM_LIST@86..104 + 0: MD_TEXTUAL@86..87 + 0: MD_TEXTUAL_LITERAL@86..87 "<" [] [] + 1: MD_TEXTUAL@87..103 + 0: MD_TEXTUAL_LITERAL@87..103 "div class=\"test\"" [] [] + 2: MD_TEXTUAL@103..104 + 0: MD_TEXTUAL_LITERAL@103..104 ">" [] [] + 8: MD_TEXTUAL@104..111 + 0: MD_TEXTUAL_LITERAL@104..111 "content" [] [] + 9: MD_INLINE_HTML@111..117 + 0: MD_INLINE_ITEM_LIST@111..117 + 0: MD_TEXTUAL@111..112 + 0: MD_TEXTUAL_LITERAL@111..112 "<" [] [] + 1: MD_TEXTUAL@112..116 + 0: MD_TEXTUAL_LITERAL@112..116 "/div" [] [] + 2: MD_TEXTUAL@116..117 + 0: MD_TEXTUAL_LITERAL@116..117 ">" [] [] + 10: MD_TEXTUAL@117..122 + 0: MD_TEXTUAL_LITERAL@117..122 " end." [] [] + 11: MD_TEXTUAL@122..123 + 0: MD_TEXTUAL_LITERAL@122..123 "\n" [] [] + 1: (empty) + 6: MD_NEWLINE@123..124 + 0: NEWLINE@123..124 "\n" [] [] + 7: MD_HEADER@124..144 + 0: MD_HASH_LIST@124..126 + 0: MD_HASH@124..126 + 0: HASH@124..126 "##" [] [] + 1: MD_PARAGRAPH@126..144 + 0: MD_INLINE_ITEM_LIST@126..144 + 0: MD_TEXTUAL@126..131 + 0: MD_TEXTUAL_LITERAL@126..131 " Self" [] [] + 1: MD_TEXTUAL@131..132 + 0: MD_TEXTUAL_LITERAL@131..132 "-" [] [] + 2: MD_TEXTUAL@132..144 + 0: MD_TEXTUAL_LITERAL@132..144 "Closing Tags" [] [] + 1: (empty) + 2: MD_HASH_LIST@144..144 + 8: MD_NEWLINE@144..145 + 0: NEWLINE@144..145 "\n" [] [] + 9: MD_PARAGRAPH@145..231 + 0: MD_INLINE_ITEM_LIST@145..231 + 0: MD_TEXTUAL@145..157 + 0: MD_TEXTUAL_LITERAL@145..157 "Line break: " [] [] + 1: MD_INLINE_HTML@157..162 + 0: MD_INLINE_ITEM_LIST@157..162 + 0: MD_TEXTUAL@157..158 + 0: MD_TEXTUAL_LITERAL@157..158 "<" [] [] + 1: MD_TEXTUAL@158..161 + 0: MD_TEXTUAL_LITERAL@158..161 "br/" [] [] + 2: MD_TEXTUAL@161..162 + 0: MD_TEXTUAL_LITERAL@161..162 ">" [] [] + 2: MD_TEXTUAL@162..168 + 0: MD_TEXTUAL_LITERAL@162..168 " here." [] [] + 3: MD_TEXTUAL@168..169 + 0: MD_TEXTUAL_LITERAL@168..169 "\n" [] [] + 4: MD_TEXTUAL@169..181 + 0: MD_TEXTUAL_LITERAL@169..181 "With space: " [] [] + 5: MD_INLINE_HTML@181..187 + 0: MD_INLINE_ITEM_LIST@181..187 + 0: MD_TEXTUAL@181..182 + 0: MD_TEXTUAL_LITERAL@181..182 "<" [] [] + 1: MD_TEXTUAL@182..186 + 0: MD_TEXTUAL_LITERAL@182..186 "br /" [] [] + 2: MD_TEXTUAL@186..187 + 0: MD_TEXTUAL_LITERAL@186..187 ">" [] [] + 6: MD_TEXTUAL@187..194 + 0: MD_TEXTUAL_LITERAL@187..194 " there." [] [] + 7: MD_TEXTUAL@194..195 + 0: MD_TEXTUAL_LITERAL@194..195 "\n" [] [] + 8: MD_TEXTUAL@195..202 + 0: MD_TEXTUAL_LITERAL@195..202 "Input: " [] [] + 9: MD_INLINE_HTML@202..223 + 0: MD_INLINE_ITEM_LIST@202..223 + 0: MD_TEXTUAL@202..203 + 0: MD_TEXTUAL_LITERAL@202..203 "<" [] [] + 1: MD_TEXTUAL@203..222 + 0: MD_TEXTUAL_LITERAL@203..222 "input type=\"text\" /" [] [] + 2: MD_TEXTUAL@222..223 + 0: MD_TEXTUAL_LITERAL@222..223 ">" [] [] + 10: MD_TEXTUAL@223..230 + 0: MD_TEXTUAL_LITERAL@223..230 " field." [] [] + 11: MD_TEXTUAL@230..231 + 0: MD_TEXTUAL_LITERAL@230..231 "\n" [] [] + 1: (empty) + 10: MD_NEWLINE@231..232 + 0: NEWLINE@231..232 "\n" [] [] + 11: MD_HEADER@232..247 + 0: MD_HASH_LIST@232..234 + 0: MD_HASH@232..234 + 0: HASH@232..234 "##" [] [] + 1: MD_PARAGRAPH@234..247 + 0: MD_INLINE_ITEM_LIST@234..247 + 0: MD_TEXTUAL@234..247 + 0: MD_TEXTUAL_LITERAL@234..247 " Closing Tags" [] [] + 1: (empty) + 2: MD_HASH_LIST@247..247 + 12: MD_NEWLINE@247..248 + 0: NEWLINE@247..248 "\n" [] [] + 13: MD_PARAGRAPH@248..321 + 0: MD_INLINE_ITEM_LIST@248..321 + 0: MD_TEXTUAL@248..253 + 0: MD_TEXTUAL_LITERAL@248..253 "Open " [] [] + 1: MD_INLINE_HTML@253..256 + 0: MD_INLINE_ITEM_LIST@253..256 + 0: MD_TEXTUAL@253..254 + 0: MD_TEXTUAL_LITERAL@253..254 "<" [] [] + 1: MD_TEXTUAL@254..255 + 0: MD_TEXTUAL_LITERAL@254..255 "b" [] [] + 2: MD_TEXTUAL@255..256 + 0: MD_TEXTUAL_LITERAL@255..256 ">" [] [] + 2: MD_TEXTUAL@256..260 + 0: MD_TEXTUAL_LITERAL@256..260 "bold" [] [] + 3: MD_INLINE_HTML@260..264 + 0: MD_INLINE_ITEM_LIST@260..264 + 0: MD_TEXTUAL@260..261 + 0: MD_TEXTUAL_LITERAL@260..261 "<" [] [] + 1: MD_TEXTUAL@261..263 + 0: MD_TEXTUAL_LITERAL@261..263 "/b" [] [] + 2: MD_TEXTUAL@263..264 + 0: MD_TEXTUAL_LITERAL@263..264 ">" [] [] + 4: MD_TEXTUAL@264..270 + 0: MD_TEXTUAL_LITERAL@264..270 " text." [] [] + 5: MD_TEXTUAL@270..271 + 0: MD_TEXTUAL_LITERAL@270..271 "\n" [] [] + 6: MD_TEXTUAL@271..278 + 0: MD_TEXTUAL_LITERAL@271..278 "Nested " [] [] + 7: MD_INLINE_HTML@278..284 + 0: MD_INLINE_ITEM_LIST@278..284 + 0: MD_TEXTUAL@278..279 + 0: MD_TEXTUAL_LITERAL@278..279 "<" [] [] + 1: MD_TEXTUAL@279..283 + 0: MD_TEXTUAL_LITERAL@279..283 "span" [] [] + 2: MD_TEXTUAL@283..284 + 0: MD_TEXTUAL_LITERAL@283..284 ">" [] [] + 8: MD_INLINE_HTML@284..292 + 0: MD_INLINE_ITEM_LIST@284..292 + 0: MD_TEXTUAL@284..285 + 0: MD_TEXTUAL_LITERAL@284..285 "<" [] [] + 1: MD_TEXTUAL@285..291 + 0: MD_TEXTUAL_LITERAL@285..291 "strong" [] [] + 2: MD_TEXTUAL@291..292 + 0: MD_TEXTUAL_LITERAL@291..292 ">" [] [] + 9: MD_TEXTUAL@292..298 + 0: MD_TEXTUAL_LITERAL@292..298 "double" [] [] + 10: MD_INLINE_HTML@298..307 + 0: MD_INLINE_ITEM_LIST@298..307 + 0: MD_TEXTUAL@298..299 + 0: MD_TEXTUAL_LITERAL@298..299 "<" [] [] + 1: MD_TEXTUAL@299..306 + 0: MD_TEXTUAL_LITERAL@299..306 "/strong" [] [] + 2: MD_TEXTUAL@306..307 + 0: MD_TEXTUAL_LITERAL@306..307 ">" [] [] + 11: MD_INLINE_HTML@307..314 + 0: MD_INLINE_ITEM_LIST@307..314 + 0: MD_TEXTUAL@307..308 + 0: MD_TEXTUAL_LITERAL@307..308 "<" [] [] + 1: MD_TEXTUAL@308..313 + 0: MD_TEXTUAL_LITERAL@308..313 "/span" [] [] + 2: MD_TEXTUAL@313..314 + 0: MD_TEXTUAL_LITERAL@313..314 ">" [] [] + 12: MD_TEXTUAL@314..320 + 0: MD_TEXTUAL_LITERAL@314..320 " tags." [] [] + 13: MD_TEXTUAL@320..321 + 0: MD_TEXTUAL_LITERAL@320..321 "\n" [] [] + 1: (empty) + 14: MD_NEWLINE@321..322 + 0: NEWLINE@321..322 "\n" [] [] + 15: MD_HEADER@322..333 + 0: MD_HASH_LIST@322..324 + 0: MD_HASH@322..324 + 0: HASH@322..324 "##" [] [] + 1: MD_PARAGRAPH@324..333 + 0: MD_INLINE_ITEM_LIST@324..333 + 0: MD_TEXTUAL@324..333 + 0: MD_TEXTUAL_LITERAL@324..333 " Comments" [] [] + 1: (empty) + 2: MD_HASH_LIST@333..333 + 16: MD_NEWLINE@333..334 + 0: NEWLINE@333..334 "\n" [] [] + 17: MD_PARAGRAPH@334..455 + 0: MD_INLINE_ITEM_LIST@334..455 + 0: MD_TEXTUAL@334..341 + 0: MD_TEXTUAL_LITERAL@334..341 "Simple " [] [] + 1: MD_INLINE_HTML@341..357 + 0: MD_INLINE_ITEM_LIST@341..357 + 0: MD_TEXTUAL@341..342 + 0: MD_TEXTUAL_LITERAL@341..342 "<" [] [] + 1: MD_TEXTUAL@342..343 + 0: MD_TEXTUAL_LITERAL@342..343 "!" [] [] + 2: MD_TEXTUAL@343..344 + 0: MD_TEXTUAL_LITERAL@343..344 "-" [] [] + 3: MD_TEXTUAL@344..345 + 0: MD_TEXTUAL_LITERAL@344..345 "-" [] [] + 4: MD_TEXTUAL@345..354 + 0: MD_TEXTUAL_LITERAL@345..354 " comment " [] [] + 5: MD_TEXTUAL@354..355 + 0: MD_TEXTUAL_LITERAL@354..355 "-" [] [] + 6: MD_TEXTUAL@355..356 + 0: MD_TEXTUAL_LITERAL@355..356 "-" [] [] + 7: MD_TEXTUAL@356..357 + 0: MD_TEXTUAL_LITERAL@356..357 ">" [] [] + 2: MD_TEXTUAL@357..365 + 0: MD_TEXTUAL_LITERAL@357..365 " inline." [] [] + 3: MD_TEXTUAL@365..366 + 0: MD_TEXTUAL_LITERAL@365..366 "\n" [] [] + 4: MD_TEXTUAL@366..372 + 0: MD_TEXTUAL_LITERAL@366..372 "Empty " [] [] + 5: MD_INLINE_HTML@372..380 + 0: MD_INLINE_ITEM_LIST@372..380 + 0: MD_TEXTUAL@372..373 + 0: MD_TEXTUAL_LITERAL@372..373 "<" [] [] + 1: MD_TEXTUAL@373..374 + 0: MD_TEXTUAL_LITERAL@373..374 "!" [] [] + 2: MD_TEXTUAL@374..375 + 0: MD_TEXTUAL_LITERAL@374..375 "-" [] [] + 3: MD_TEXTUAL@375..376 + 0: MD_TEXTUAL_LITERAL@375..376 "-" [] [] + 4: MD_TEXTUAL@376..377 + 0: MD_TEXTUAL_LITERAL@376..377 " " [] [] + 5: MD_TEXTUAL@377..378 + 0: MD_TEXTUAL_LITERAL@377..378 "-" [] [] + 6: MD_TEXTUAL@378..379 + 0: MD_TEXTUAL_LITERAL@378..379 "-" [] [] + 7: MD_TEXTUAL@379..380 + 0: MD_TEXTUAL_LITERAL@379..380 ">" [] [] + 6: MD_TEXTUAL@380..389 + 0: MD_TEXTUAL_LITERAL@380..389 " comment." [] [] + 7: MD_TEXTUAL@389..390 + 0: MD_TEXTUAL_LITERAL@389..390 "\n" [] [] + 8: MD_TEXTUAL@390..402 + 0: MD_TEXTUAL_LITERAL@390..402 "With dashes " [] [] + 9: MD_INLINE_HTML@402..416 + 0: MD_INLINE_ITEM_LIST@402..416 + 0: MD_TEXTUAL@402..403 + 0: MD_TEXTUAL_LITERAL@402..403 "<" [] [] + 1: MD_TEXTUAL@403..404 + 0: MD_TEXTUAL_LITERAL@403..404 "!" [] [] + 2: MD_TEXTUAL@404..405 + 0: MD_TEXTUAL_LITERAL@404..405 "-" [] [] + 3: MD_TEXTUAL@405..406 + 0: MD_TEXTUAL_LITERAL@405..406 "-" [] [] + 4: MD_TEXTUAL@406..409 + 0: MD_TEXTUAL_LITERAL@406..409 "foo" [] [] + 5: MD_TEXTUAL@409..410 + 0: MD_TEXTUAL_LITERAL@409..410 "-" [] [] + 6: MD_TEXTUAL@410..413 + 0: MD_TEXTUAL_LITERAL@410..413 "bar" [] [] + 7: MD_TEXTUAL@413..414 + 0: MD_TEXTUAL_LITERAL@413..414 "-" [] [] + 8: MD_TEXTUAL@414..415 + 0: MD_TEXTUAL_LITERAL@414..415 "-" [] [] + 9: MD_TEXTUAL@415..416 + 0: MD_TEXTUAL_LITERAL@415..416 ">" [] [] + 10: MD_TEXTUAL@416..422 + 0: MD_TEXTUAL_LITERAL@416..422 " here." [] [] + 11: MD_TEXTUAL@422..423 + 0: MD_TEXTUAL_LITERAL@422..423 "\n" [] [] + 12: MD_TEXTUAL@423..436 + 0: MD_TEXTUAL_LITERAL@423..436 "Leading dash " [] [] + 13: MD_INLINE_HTML@436..445 + 0: MD_INLINE_ITEM_LIST@436..445 + 0: MD_TEXTUAL@436..437 + 0: MD_TEXTUAL_LITERAL@436..437 "<" [] [] + 1: MD_TEXTUAL@437..438 + 0: MD_TEXTUAL_LITERAL@437..438 "!" [] [] + 2: MD_TEXTUAL@438..439 + 0: MD_TEXTUAL_LITERAL@438..439 "-" [] [] + 3: MD_TEXTUAL@439..440 + 0: MD_TEXTUAL_LITERAL@439..440 "-" [] [] + 4: MD_TEXTUAL@440..441 + 0: MD_TEXTUAL_LITERAL@440..441 "-" [] [] + 5: MD_TEXTUAL@441..442 + 0: MD_TEXTUAL_LITERAL@441..442 " " [] [] + 6: MD_TEXTUAL@442..443 + 0: MD_TEXTUAL_LITERAL@442..443 "-" [] [] + 7: MD_TEXTUAL@443..444 + 0: MD_TEXTUAL_LITERAL@443..444 "-" [] [] + 8: MD_TEXTUAL@444..445 + 0: MD_TEXTUAL_LITERAL@444..445 ">" [] [] + 14: MD_TEXTUAL@445..454 + 0: MD_TEXTUAL_LITERAL@445..454 " allowed." [] [] + 15: MD_TEXTUAL@454..455 + 0: MD_TEXTUAL_LITERAL@454..455 "\n" [] [] + 1: (empty) + 18: MD_NEWLINE@455..456 + 0: NEWLINE@455..456 "\n" [] [] + 19: MD_HEADER@456..482 + 0: MD_HASH_LIST@456..458 + 0: MD_HASH@456..458 + 0: HASH@456..458 "##" [] [] + 1: MD_PARAGRAPH@458..482 + 0: MD_INLINE_ITEM_LIST@458..482 + 0: MD_TEXTUAL@458..482 + 0: MD_TEXTUAL_LITERAL@458..482 " Processing Instructions" [] [] + 1: (empty) + 2: MD_HASH_LIST@482..482 + 20: MD_NEWLINE@482..483 + 0: NEWLINE@482..483 "\n" [] [] + 21: MD_PARAGRAPH@483..552 + 0: MD_INLINE_ITEM_LIST@483..552 + 0: MD_TEXTUAL@483..488 + 0: MD_TEXTUAL_LITERAL@483..488 "XML: " [] [] + 1: MD_INLINE_HTML@488..509 + 0: MD_INLINE_ITEM_LIST@488..509 + 0: MD_TEXTUAL@488..489 + 0: MD_TEXTUAL_LITERAL@488..489 "<" [] [] + 1: MD_TEXTUAL@489..508 + 0: MD_TEXTUAL_LITERAL@489..508 "?xml version=\"1.0\"?" [] [] + 2: MD_TEXTUAL@508..509 + 0: MD_TEXTUAL_LITERAL@508..509 ">" [] [] + 2: MD_TEXTUAL@509..518 + 0: MD_TEXTUAL_LITERAL@509..518 " present." [] [] + 3: MD_TEXTUAL@518..519 + 0: MD_TEXTUAL_LITERAL@518..519 "\n" [] [] + 4: MD_TEXTUAL@519..524 + 0: MD_TEXTUAL_LITERAL@519..524 "PHP: " [] [] + 5: MD_INLINE_HTML@524..545 + 0: MD_INLINE_ITEM_LIST@524..545 + 0: MD_TEXTUAL@524..525 + 0: MD_TEXTUAL_LITERAL@524..525 "<" [] [] + 1: MD_TEXTUAL@525..544 + 0: MD_TEXTUAL_LITERAL@525..544 "?php echo \"test\"; ?" [] [] + 2: MD_TEXTUAL@544..545 + 0: MD_TEXTUAL_LITERAL@544..545 ">" [] [] + 6: MD_TEXTUAL@545..551 + 0: MD_TEXTUAL_LITERAL@545..551 " code." [] [] + 7: MD_TEXTUAL@551..552 + 0: MD_TEXTUAL_LITERAL@551..552 "\n" [] [] + 1: (empty) + 22: MD_NEWLINE@552..553 + 0: NEWLINE@552..553 "\n" [] [] + 23: MD_HEADER@553..570 + 0: MD_HASH_LIST@553..555 + 0: MD_HASH@553..555 + 0: HASH@553..555 "##" [] [] + 1: MD_PARAGRAPH@555..570 + 0: MD_INLINE_ITEM_LIST@555..570 + 0: MD_TEXTUAL@555..570 + 0: MD_TEXTUAL_LITERAL@555..570 " CDATA Sections" [] [] + 1: (empty) + 2: MD_HASH_LIST@570..570 + 24: MD_NEWLINE@570..571 + 0: NEWLINE@570..571 "\n" [] [] + 25: MD_PARAGRAPH@571..638 + 0: MD_INLINE_ITEM_LIST@571..638 + 0: MD_TEXTUAL@571..577 + 0: MD_TEXTUAL_LITERAL@571..577 "Data: " [] [] + 1: MD_INLINE_HTML@577..598 + 0: MD_INLINE_ITEM_LIST@577..598 + 0: MD_TEXTUAL@577..578 + 0: MD_TEXTUAL_LITERAL@577..578 "<" [] [] + 1: MD_TEXTUAL@578..579 + 0: MD_TEXTUAL_LITERAL@578..579 "!" [] [] + 2: MD_TEXTUAL@579..580 + 0: MD_TEXTUAL_LITERAL@579..580 "[" [] [] + 3: MD_TEXTUAL@580..585 + 0: MD_TEXTUAL_LITERAL@580..585 "CDATA" [] [] + 4: MD_TEXTUAL@585..586 + 0: MD_TEXTUAL_LITERAL@585..586 "[" [] [] + 5: MD_TEXTUAL@586..595 + 0: MD_TEXTUAL_LITERAL@586..595 "some text" [] [] + 6: MD_TEXTUAL@595..596 + 0: MD_TEXTUAL_LITERAL@595..596 "]" [] [] + 7: MD_TEXTUAL@596..597 + 0: MD_TEXTUAL_LITERAL@596..597 "]" [] [] + 8: MD_TEXTUAL@597..598 + 0: MD_TEXTUAL_LITERAL@597..598 ">" [] [] + 2: MD_TEXTUAL@598..604 + 0: MD_TEXTUAL_LITERAL@598..604 " here." [] [] + 3: MD_TEXTUAL@604..605 + 0: MD_TEXTUAL_LITERAL@604..605 "\n" [] [] + 4: MD_TEXTUAL@605..614 + 0: MD_TEXTUAL_LITERAL@605..614 "Special: " [] [] + 5: MD_INLINE_HTML@614..630 + 0: MD_INLINE_ITEM_LIST@614..630 + 0: MD_TEXTUAL@614..615 + 0: MD_TEXTUAL_LITERAL@614..615 "<" [] [] + 1: MD_TEXTUAL@615..616 + 0: MD_TEXTUAL_LITERAL@615..616 "!" [] [] + 2: MD_TEXTUAL@616..617 + 0: MD_TEXTUAL_LITERAL@616..617 "[" [] [] + 3: MD_TEXTUAL@617..622 + 0: MD_TEXTUAL_LITERAL@617..622 "CDATA" [] [] + 4: MD_TEXTUAL@622..623 + 0: MD_TEXTUAL_LITERAL@622..623 "[" [] [] + 5: MD_TEXTUAL@623..624 + 0: MD_TEXTUAL_LITERAL@623..624 "<" [] [] + 6: MD_TEXTUAL@624..625 + 0: MD_TEXTUAL_LITERAL@624..625 ">" [] [] + 7: MD_TEXTUAL@625..627 + 0: MD_TEXTUAL_LITERAL@625..627 "&\"" [] [] + 8: MD_TEXTUAL@627..628 + 0: MD_TEXTUAL_LITERAL@627..628 "]" [] [] + 9: MD_TEXTUAL@628..629 + 0: MD_TEXTUAL_LITERAL@628..629 "]" [] [] + 10: MD_TEXTUAL@629..630 + 0: MD_TEXTUAL_LITERAL@629..630 ">" [] [] + 6: MD_TEXTUAL@630..637 + 0: MD_TEXTUAL_LITERAL@630..637 " chars." [] [] + 7: MD_TEXTUAL@637..638 + 0: MD_TEXTUAL_LITERAL@637..638 "\n" [] [] + 1: (empty) + 26: MD_NEWLINE@638..639 + 0: NEWLINE@638..639 "\n" [] [] + 27: MD_HEADER@639..654 + 0: MD_HASH_LIST@639..641 + 0: MD_HASH@639..641 + 0: HASH@639..641 "##" [] [] + 1: MD_PARAGRAPH@641..654 + 0: MD_INLINE_ITEM_LIST@641..654 + 0: MD_TEXTUAL@641..654 + 0: MD_TEXTUAL_LITERAL@641..654 " Declarations" [] [] + 1: (empty) + 2: MD_HASH_LIST@654..654 + 28: MD_NEWLINE@654..655 + 0: NEWLINE@654..655 "\n" [] [] + 29: MD_PARAGRAPH@655..801 + 0: MD_INLINE_ITEM_LIST@655..801 + 0: MD_TEXTUAL@655..665 + 0: MD_TEXTUAL_LITERAL@655..665 "Standard: " [] [] + 1: MD_INLINE_HTML@665..680 + 0: MD_INLINE_ITEM_LIST@665..680 + 0: MD_TEXTUAL@665..666 + 0: MD_TEXTUAL_LITERAL@665..666 "<" [] [] + 1: MD_TEXTUAL@666..667 + 0: MD_TEXTUAL_LITERAL@666..667 "!" [] [] + 2: MD_TEXTUAL@667..679 + 0: MD_TEXTUAL_LITERAL@667..679 "DOCTYPE html" [] [] + 3: MD_TEXTUAL@679..680 + 0: MD_TEXTUAL_LITERAL@679..680 ">" [] [] + 2: MD_TEXTUAL@680..693 + 0: MD_TEXTUAL_LITERAL@680..693 " declaration." [] [] + 3: MD_TEXTUAL@693..694 + 0: MD_TEXTUAL_LITERAL@693..694 "\n" [] [] + 4: MD_TEXTUAL@694..705 + 0: MD_TEXTUAL_LITERAL@694..705 "Lowercase: " [] [] + 5: MD_INLINE_HTML@705..720 + 0: MD_INLINE_ITEM_LIST@705..720 + 0: MD_TEXTUAL@705..706 + 0: MD_TEXTUAL_LITERAL@705..706 "<" [] [] + 1: MD_TEXTUAL@706..707 + 0: MD_TEXTUAL_LITERAL@706..707 "!" [] [] + 2: MD_TEXTUAL@707..719 + 0: MD_TEXTUAL_LITERAL@707..719 "doctype html" [] [] + 3: MD_TEXTUAL@719..720 + 0: MD_TEXTUAL_LITERAL@719..720 ">" [] [] + 6: MD_TEXTUAL@720..733 + 0: MD_TEXTUAL_LITERAL@720..733 " declaration." [] [] + 7: MD_TEXTUAL@733..734 + 0: MD_TEXTUAL_LITERAL@733..734 "\n" [] [] + 8: MD_TEXTUAL@734..744 + 0: MD_TEXTUAL_LITERAL@734..744 "Extended: " [] [] + 9: MD_INLINE_HTML@744..794 + 0: MD_INLINE_ITEM_LIST@744..794 + 0: MD_TEXTUAL@744..745 + 0: MD_TEXTUAL_LITERAL@744..745 "<" [] [] + 1: MD_TEXTUAL@745..746 + 0: MD_TEXTUAL_LITERAL@745..746 "!" [] [] + 2: MD_TEXTUAL@746..767 + 0: MD_TEXTUAL_LITERAL@746..767 "DOCTYPE HTML PUBLIC \"" [] [] + 3: MD_TEXTUAL@767..768 + 0: MD_TEXTUAL_LITERAL@767..768 "-" [] [] + 4: MD_TEXTUAL@768..793 + 0: MD_TEXTUAL_LITERAL@768..793 "//W3C//DTD HTML 4.01//EN\"" [] [] + 5: MD_TEXTUAL@793..794 + 0: MD_TEXTUAL_LITERAL@793..794 ">" [] [] + 10: MD_TEXTUAL@794..800 + 0: MD_TEXTUAL_LITERAL@794..800 " test." [] [] + 11: MD_TEXTUAL@800..801 + 0: MD_TEXTUAL_LITERAL@800..801 "\n" [] [] + 1: (empty) + 30: MD_NEWLINE@801..802 + 0: NEWLINE@801..802 "\n" [] [] + 31: MD_HEADER@802..827 + 0: MD_HASH_LIST@802..804 + 0: MD_HASH@802..804 + 0: HASH@802..804 "##" [] [] + 1: MD_PARAGRAPH@804..827 + 0: MD_INLINE_ITEM_LIST@804..827 + 0: MD_TEXTUAL@804..827 + 0: MD_TEXTUAL_LITERAL@804..827 " Attributes with Quotes" [] [] + 1: (empty) + 2: MD_HASH_LIST@827..827 + 32: MD_NEWLINE@827..828 + 0: NEWLINE@827..828 "\n" [] [] + 33: MD_PARAGRAPH@828..968 + 0: MD_INLINE_ITEM_LIST@828..968 + 0: MD_TEXTUAL@828..836 + 0: MD_TEXTUAL_LITERAL@828..836 "Single: " [] [] + 1: MD_INLINE_HTML@836..856 + 0: MD_INLINE_ITEM_LIST@836..856 + 0: MD_TEXTUAL@836..837 + 0: MD_TEXTUAL_LITERAL@836..837 "<" [] [] + 1: MD_TEXTUAL@837..855 + 0: MD_TEXTUAL_LITERAL@837..855 "div class='quoted'" [] [] + 2: MD_TEXTUAL@855..856 + 0: MD_TEXTUAL_LITERAL@855..856 ">" [] [] + 2: MD_TEXTUAL@856..860 + 0: MD_TEXTUAL_LITERAL@856..860 "text" [] [] + 3: MD_INLINE_HTML@860..866 + 0: MD_INLINE_ITEM_LIST@860..866 + 0: MD_TEXTUAL@860..861 + 0: MD_TEXTUAL_LITERAL@860..861 "<" [] [] + 1: MD_TEXTUAL@861..865 + 0: MD_TEXTUAL_LITERAL@861..865 "/div" [] [] + 2: MD_TEXTUAL@865..866 + 0: MD_TEXTUAL_LITERAL@865..866 ">" [] [] + 4: MD_TEXTUAL@866..871 + 0: MD_TEXTUAL_LITERAL@866..871 " end." [] [] + 5: MD_TEXTUAL@871..872 + 0: MD_TEXTUAL_LITERAL@871..872 "\n" [] [] + 6: MD_TEXTUAL@872..880 + 0: MD_TEXTUAL_LITERAL@872..880 "Double: " [] [] + 7: MD_INLINE_HTML@880..900 + 0: MD_INLINE_ITEM_LIST@880..900 + 0: MD_TEXTUAL@880..881 + 0: MD_TEXTUAL_LITERAL@880..881 "<" [] [] + 1: MD_TEXTUAL@881..899 + 0: MD_TEXTUAL_LITERAL@881..899 "div class=\"quoted\"" [] [] + 2: MD_TEXTUAL@899..900 + 0: MD_TEXTUAL_LITERAL@899..900 ">" [] [] + 8: MD_TEXTUAL@900..904 + 0: MD_TEXTUAL_LITERAL@900..904 "text" [] [] + 9: MD_INLINE_HTML@904..910 + 0: MD_INLINE_ITEM_LIST@904..910 + 0: MD_TEXTUAL@904..905 + 0: MD_TEXTUAL_LITERAL@904..905 "<" [] [] + 1: MD_TEXTUAL@905..909 + 0: MD_TEXTUAL_LITERAL@905..909 "/div" [] [] + 2: MD_TEXTUAL@909..910 + 0: MD_TEXTUAL_LITERAL@909..910 ">" [] [] + 10: MD_TEXTUAL@910..915 + 0: MD_TEXTUAL_LITERAL@910..915 " end." [] [] + 11: MD_TEXTUAL@915..916 + 0: MD_TEXTUAL_LITERAL@915..916 "\n" [] [] + 12: MD_TEXTUAL@916..922 + 0: MD_TEXTUAL_LITERAL@916..922 "Both: " [] [] + 13: MD_INLINE_HTML@922..952 + 0: MD_INLINE_ITEM_LIST@922..952 + 0: MD_TEXTUAL@922..923 + 0: MD_TEXTUAL_LITERAL@922..923 "<" [] [] + 1: MD_TEXTUAL@923..951 + 0: MD_TEXTUAL_LITERAL@923..951 "div class=\"outer\" id='inner'" [] [] + 2: MD_TEXTUAL@951..952 + 0: MD_TEXTUAL_LITERAL@951..952 ">" [] [] + 14: MD_TEXTUAL@952..956 + 0: MD_TEXTUAL_LITERAL@952..956 "text" [] [] + 15: MD_INLINE_HTML@956..962 + 0: MD_INLINE_ITEM_LIST@956..962 + 0: MD_TEXTUAL@956..957 + 0: MD_TEXTUAL_LITERAL@956..957 "<" [] [] + 1: MD_TEXTUAL@957..961 + 0: MD_TEXTUAL_LITERAL@957..961 "/div" [] [] + 2: MD_TEXTUAL@961..962 + 0: MD_TEXTUAL_LITERAL@961..962 ">" [] [] + 16: MD_TEXTUAL@962..967 + 0: MD_TEXTUAL_LITERAL@962..967 " end." [] [] + 17: MD_TEXTUAL@967..968 + 0: MD_TEXTUAL_LITERAL@967..968 "\n" [] [] + 1: (empty) + 34: MD_NEWLINE@968..969 + 0: NEWLINE@968..969 "\n" [] [] + 35: MD_HEADER@969..1001 + 0: MD_HASH_LIST@969..971 + 0: MD_HASH@969..971 + 0: HASH@969..971 "##" [] [] + 1: MD_PARAGRAPH@971..1001 + 0: MD_INLINE_ITEM_LIST@971..1001 + 0: MD_TEXTUAL@971..1001 + 0: MD_TEXTUAL_LITERAL@971..1001 " Attributes with Special Chars" [] [] + 1: (empty) + 2: MD_HASH_LIST@1001..1001 + 36: MD_NEWLINE@1001..1002 + 0: NEWLINE@1001..1002 "\n" [] [] + 37: MD_PARAGRAPH@1002..1261 + 0: MD_INLINE_ITEM_LIST@1002..1261 + 0: MD_TEXTUAL@1002..1010 + 0: MD_TEXTUAL_LITERAL@1002..1010 "Spaces: " [] [] + 1: MD_INLINE_HTML@1010..1040 + 0: MD_INLINE_ITEM_LIST@1010..1040 + 0: MD_TEXTUAL@1010..1011 + 0: MD_TEXTUAL_LITERAL@1010..1011 "<" [] [] + 1: MD_TEXTUAL@1011..1019 + 0: MD_TEXTUAL_LITERAL@1011..1019 "div data" [] [] + 2: MD_TEXTUAL@1019..1020 + 0: MD_TEXTUAL_LITERAL@1019..1020 "-" [] [] + 3: MD_TEXTUAL@1020..1039 + 0: MD_TEXTUAL_LITERAL@1020..1039 "value=\"with spaces\"" [] [] + 4: MD_TEXTUAL@1039..1040 + 0: MD_TEXTUAL_LITERAL@1039..1040 ">" [] [] + 2: MD_TEXTUAL@1040..1044 + 0: MD_TEXTUAL_LITERAL@1040..1044 "text" [] [] + 3: MD_INLINE_HTML@1044..1050 + 0: MD_INLINE_ITEM_LIST@1044..1050 + 0: MD_TEXTUAL@1044..1045 + 0: MD_TEXTUAL_LITERAL@1044..1045 "<" [] [] + 1: MD_TEXTUAL@1045..1049 + 0: MD_TEXTUAL_LITERAL@1045..1049 "/div" [] [] + 2: MD_TEXTUAL@1049..1050 + 0: MD_TEXTUAL_LITERAL@1049..1050 ">" [] [] + 4: MD_TEXTUAL@1050..1055 + 0: MD_TEXTUAL_LITERAL@1050..1055 " end." [] [] + 5: MD_TEXTUAL@1055..1056 + 0: MD_TEXTUAL_LITERAL@1055..1056 "\n" [] [] + 6: MD_TEXTUAL@1056..1066 + 0: MD_TEXTUAL_LITERAL@1056..1066 "Multiple: " [] [] + 7: MD_INLINE_HTML@1066..1099 + 0: MD_INLINE_ITEM_LIST@1066..1099 + 0: MD_TEXTUAL@1066..1067 + 0: MD_TEXTUAL_LITERAL@1066..1067 "<" [] [] + 1: MD_TEXTUAL@1067..1092 + 0: MD_TEXTUAL_LITERAL@1067..1092 "div class=\"a\" id=\"b\" data" [] [] + 2: MD_TEXTUAL@1092..1093 + 0: MD_TEXTUAL_LITERAL@1092..1093 "-" [] [] + 3: MD_TEXTUAL@1093..1098 + 0: MD_TEXTUAL_LITERAL@1093..1098 "x=\"c\"" [] [] + 4: MD_TEXTUAL@1098..1099 + 0: MD_TEXTUAL_LITERAL@1098..1099 ">" [] [] + 8: MD_TEXTUAL@1099..1103 + 0: MD_TEXTUAL_LITERAL@1099..1103 "text" [] [] + 9: MD_INLINE_HTML@1103..1109 + 0: MD_INLINE_ITEM_LIST@1103..1109 + 0: MD_TEXTUAL@1103..1104 + 0: MD_TEXTUAL_LITERAL@1103..1104 "<" [] [] + 1: MD_TEXTUAL@1104..1108 + 0: MD_TEXTUAL_LITERAL@1104..1108 "/div" [] [] + 2: MD_TEXTUAL@1108..1109 + 0: MD_TEXTUAL_LITERAL@1108..1109 ">" [] [] + 10: MD_TEXTUAL@1109..1114 + 0: MD_TEXTUAL_LITERAL@1109..1114 " end." [] [] + 11: MD_TEXTUAL@1114..1115 + 0: MD_TEXTUAL_LITERAL@1114..1115 "\n" [] [] + 12: MD_TEXTUAL@1115..1125 + 0: MD_TEXTUAL_LITERAL@1115..1125 "Unquoted: " [] [] + 13: MD_INLINE_HTML@1125..1156 + 0: MD_INLINE_ITEM_LIST@1125..1156 + 0: MD_TEXTUAL@1125..1126 + 0: MD_TEXTUAL_LITERAL@1125..1126 "<" [] [] + 1: MD_TEXTUAL@1126..1134 + 0: MD_TEXTUAL_LITERAL@1126..1134 "div data" [] [] + 2: MD_TEXTUAL@1134..1135 + 0: MD_TEXTUAL_LITERAL@1134..1135 "-" [] [] + 3: MD_TEXTUAL@1135..1145 + 0: MD_TEXTUAL_LITERAL@1135..1145 "x=foo data" [] [] + 4: MD_TEXTUAL@1145..1146 + 0: MD_TEXTUAL_LITERAL@1145..1146 "-" [] [] + 5: MD_TEXTUAL@1146..1151 + 0: MD_TEXTUAL_LITERAL@1146..1151 "y=bar" [] [] + 6: MD_TEXTUAL@1151..1152 + 0: MD_TEXTUAL_LITERAL@1151..1152 "-" [] [] + 7: MD_TEXTUAL@1152..1155 + 0: MD_TEXTUAL_LITERAL@1152..1155 "baz" [] [] + 8: MD_TEXTUAL@1155..1156 + 0: MD_TEXTUAL_LITERAL@1155..1156 ">" [] [] + 14: MD_TEXTUAL@1156..1160 + 0: MD_TEXTUAL_LITERAL@1156..1160 "text" [] [] + 15: MD_INLINE_HTML@1160..1166 + 0: MD_INLINE_ITEM_LIST@1160..1166 + 0: MD_TEXTUAL@1160..1161 + 0: MD_TEXTUAL_LITERAL@1160..1161 "<" [] [] + 1: MD_TEXTUAL@1161..1165 + 0: MD_TEXTUAL_LITERAL@1161..1165 "/div" [] [] + 2: MD_TEXTUAL@1165..1166 + 0: MD_TEXTUAL_LITERAL@1165..1166 ">" [] [] + 16: MD_TEXTUAL@1166..1171 + 0: MD_TEXTUAL_LITERAL@1166..1171 " end." [] [] + 17: MD_TEXTUAL@1171..1172 + 0: MD_TEXTUAL_LITERAL@1171..1172 "\n" [] [] + 18: MD_TEXTUAL@1172..1190 + 0: MD_TEXTUAL_LITERAL@1172..1190 "Underscore/colon: " [] [] + 19: MD_INLINE_HTML@1190..1206 + 0: MD_INLINE_ITEM_LIST@1190..1206 + 0: MD_TEXTUAL@1190..1191 + 0: MD_TEXTUAL_LITERAL@1190..1191 "<" [] [] + 1: MD_TEXTUAL@1191..1195 + 0: MD_TEXTUAL_LITERAL@1191..1195 "div " [] [] + 2: MD_TEXTUAL@1195..1196 + 0: MD_TEXTUAL_LITERAL@1195..1196 "_" [] [] + 3: MD_TEXTUAL@1196..1205 + 0: MD_TEXTUAL_LITERAL@1196..1205 "x=1 x:y=2" [] [] + 4: MD_TEXTUAL@1205..1206 + 0: MD_TEXTUAL_LITERAL@1205..1206 ">" [] [] + 20: MD_TEXTUAL@1206..1210 + 0: MD_TEXTUAL_LITERAL@1206..1210 "text" [] [] + 21: MD_INLINE_HTML@1210..1216 + 0: MD_INLINE_ITEM_LIST@1210..1216 + 0: MD_TEXTUAL@1210..1211 + 0: MD_TEXTUAL_LITERAL@1210..1211 "<" [] [] + 1: MD_TEXTUAL@1211..1215 + 0: MD_TEXTUAL_LITERAL@1211..1215 "/div" [] [] + 2: MD_TEXTUAL@1215..1216 + 0: MD_TEXTUAL_LITERAL@1215..1216 ">" [] [] + 22: MD_TEXTUAL@1216..1221 + 0: MD_TEXTUAL_LITERAL@1216..1221 " end." [] [] + 23: MD_TEXTUAL@1221..1222 + 0: MD_TEXTUAL_LITERAL@1221..1222 "\n" [] [] + 24: MD_TEXTUAL@1222..1231 + 0: MD_TEXTUAL_LITERAL@1222..1231 "Boolean: " [] [] + 25: MD_INLINE_HTML@1231..1245 + 0: MD_INLINE_ITEM_LIST@1231..1245 + 0: MD_TEXTUAL@1231..1232 + 0: MD_TEXTUAL_LITERAL@1231..1232 "<" [] [] + 1: MD_TEXTUAL@1232..1244 + 0: MD_TEXTUAL_LITERAL@1232..1244 "div disabled" [] [] + 2: MD_TEXTUAL@1244..1245 + 0: MD_TEXTUAL_LITERAL@1244..1245 ">" [] [] + 26: MD_TEXTUAL@1245..1249 + 0: MD_TEXTUAL_LITERAL@1245..1249 "text" [] [] + 27: MD_INLINE_HTML@1249..1255 + 0: MD_INLINE_ITEM_LIST@1249..1255 + 0: MD_TEXTUAL@1249..1250 + 0: MD_TEXTUAL_LITERAL@1249..1250 "<" [] [] + 1: MD_TEXTUAL@1250..1254 + 0: MD_TEXTUAL_LITERAL@1250..1254 "/div" [] [] + 2: MD_TEXTUAL@1254..1255 + 0: MD_TEXTUAL_LITERAL@1254..1255 ">" [] [] + 28: MD_TEXTUAL@1255..1260 + 0: MD_TEXTUAL_LITERAL@1255..1260 " end." [] [] + 29: MD_TEXTUAL@1260..1261 + 0: MD_TEXTUAL_LITERAL@1260..1261 "\n" [] [] + 1: (empty) + 38: MD_NEWLINE@1261..1262 + 0: NEWLINE@1261..1262 "\n" [] [] + 39: MD_HEADER@1262..1308 + 0: MD_HASH_LIST@1262..1264 + 0: MD_HASH@1262..1264 + 0: HASH@1262..1264 "##" [] [] + 1: MD_PARAGRAPH@1264..1308 + 0: MD_INLINE_ITEM_LIST@1264..1308 + 0: MD_TEXTUAL@1264..1279 + 0: MD_TEXTUAL_LITERAL@1264..1279 " Newline Cases " [] [] + 1: MD_TEXTUAL@1279..1280 + 0: MD_TEXTUAL_LITERAL@1279..1280 "(" [] [] + 2: MD_TEXTUAL@1280..1307 + 0: MD_TEXTUAL_LITERAL@1280..1307 "should parse as inline HTML" [] [] + 3: MD_TEXTUAL@1307..1308 + 0: MD_TEXTUAL_LITERAL@1307..1308 ")" [] [] + 1: (empty) + 2: MD_HASH_LIST@1308..1308 + 40: MD_NEWLINE@1308..1309 + 0: NEWLINE@1308..1309 "\n" [] [] + 41: MD_PARAGRAPH@1309..1389 + 0: MD_INLINE_ITEM_LIST@1309..1389 + 0: MD_TEXTUAL@1309..1318 + 0: MD_TEXTUAL_LITERAL@1309..1318 "Allowed: " [] [] + 1: MD_INLINE_HTML@1318..1336 + 0: MD_INLINE_ITEM_LIST@1318..1336 + 0: MD_TEXTUAL@1318..1319 + 0: MD_TEXTUAL_LITERAL@1318..1319 "<" [] [] + 1: MD_TEXTUAL@1319..1322 + 0: MD_TEXTUAL_LITERAL@1319..1322 "div" [] [] + 2: MD_TEXTUAL@1322..1323 + 0: MD_TEXTUAL_LITERAL@1322..1323 "\n" [] [] + 3: MD_TEXTUAL@1323..1335 + 0: MD_TEXTUAL_LITERAL@1323..1335 "class=\"test\"" [] [] + 4: MD_TEXTUAL@1335..1336 + 0: MD_TEXTUAL_LITERAL@1335..1336 ">" [] [] + 2: MD_TEXTUAL@1336..1338 + 0: MD_TEXTUAL_LITERAL@1336..1338 "ok" [] [] + 3: MD_INLINE_HTML@1338..1344 + 0: MD_INLINE_ITEM_LIST@1338..1344 + 0: MD_TEXTUAL@1338..1339 + 0: MD_TEXTUAL_LITERAL@1338..1339 "<" [] [] + 1: MD_TEXTUAL@1339..1343 + 0: MD_TEXTUAL_LITERAL@1339..1343 "/div" [] [] + 2: MD_TEXTUAL@1343..1344 + 0: MD_TEXTUAL_LITERAL@1343..1344 ">" [] [] + 4: MD_TEXTUAL@1344..1349 + 0: MD_TEXTUAL_LITERAL@1344..1349 " tag." [] [] + 5: MD_TEXTUAL@1349..1350 + 0: MD_TEXTUAL_LITERAL@1349..1350 "\n" [] [] + 6: MD_TEXTUAL@1350..1359 + 0: MD_TEXTUAL_LITERAL@1350..1359 "Allowed: " [] [] + 7: MD_INLINE_HTML@1359..1375 + 0: MD_INLINE_ITEM_LIST@1359..1375 + 0: MD_TEXTUAL@1359..1360 + 0: MD_TEXTUAL_LITERAL@1359..1360 "<" [] [] + 1: MD_TEXTUAL@1360..1373 + 0: MD_TEXTUAL_LITERAL@1360..1373 "div class=\"a\"" [] [] + 2: MD_TEXTUAL@1373..1374 + 0: MD_TEXTUAL_LITERAL@1373..1374 "\n" [] [] + 3: MD_TEXTUAL@1374..1375 + 0: MD_TEXTUAL_LITERAL@1374..1375 ">" [] [] + 8: MD_TEXTUAL@1375..1377 + 0: MD_TEXTUAL_LITERAL@1375..1377 "ok" [] [] + 9: MD_INLINE_HTML@1377..1383 + 0: MD_INLINE_ITEM_LIST@1377..1383 + 0: MD_TEXTUAL@1377..1378 + 0: MD_TEXTUAL_LITERAL@1377..1378 "<" [] [] + 1: MD_TEXTUAL@1378..1382 + 0: MD_TEXTUAL_LITERAL@1378..1382 "/div" [] [] + 2: MD_TEXTUAL@1382..1383 + 0: MD_TEXTUAL_LITERAL@1382..1383 ">" [] [] + 10: MD_TEXTUAL@1383..1388 + 0: MD_TEXTUAL_LITERAL@1383..1388 " tag." [] [] + 11: MD_TEXTUAL@1388..1389 + 0: MD_TEXTUAL_LITERAL@1388..1389 "\n" [] [] + 1: (empty) + 42: MD_NEWLINE@1389..1390 + 0: NEWLINE@1389..1390 "\n" [] [] + 43: MD_HEADER@1390..1424 + 0: MD_HASH_LIST@1390..1392 + 0: MD_HASH@1390..1392 + 0: HASH@1390..1392 "##" [] [] + 1: MD_PARAGRAPH@1392..1424 + 0: MD_INLINE_ITEM_LIST@1392..1424 + 0: MD_TEXTUAL@1392..1402 + 0: MD_TEXTUAL_LITERAL@1392..1402 " Priority " [] [] + 1: MD_TEXTUAL@1402..1403 + 0: MD_TEXTUAL_LITERAL@1402..1403 "-" [] [] + 2: MD_TEXTUAL@1403..1424 + 0: MD_TEXTUAL_LITERAL@1403..1424 " Autolinks Should Win" [] [] + 1: (empty) + 2: MD_HASH_LIST@1424..1424 + 44: MD_NEWLINE@1424..1425 + 0: NEWLINE@1424..1425 "\n" [] [] + 45: MD_PARAGRAPH@1425..1493 + 0: MD_INLINE_ITEM_LIST@1425..1493 + 0: MD_TEXTUAL@1425..1430 + 0: MD_TEXTUAL_LITERAL@1425..1430 "URL: " [] [] + 1: MD_AUTOLINK@1430..1451 + 0: L_ANGLE@1430..1431 "<" [] [] + 1: MD_INLINE_ITEM_LIST@1431..1450 + 0: MD_TEXTUAL@1431..1450 + 0: MD_TEXTUAL_LITERAL@1431..1450 "https://example.com" [] [] + 2: R_ANGLE@1450..1451 ">" [] [] + 2: MD_TEXTUAL@1451..1457 + 0: MD_TEXTUAL_LITERAL@1451..1457 " link." [] [] + 3: MD_TEXTUAL@1457..1458 + 0: MD_TEXTUAL_LITERAL@1457..1458 "\n" [] [] + 4: MD_TEXTUAL@1458..1465 + 0: MD_TEXTUAL_LITERAL@1458..1465 "Email: " [] [] + 5: MD_AUTOLINK@1465..1483 + 0: L_ANGLE@1465..1466 "<" [] [] + 1: MD_INLINE_ITEM_LIST@1466..1482 + 0: MD_TEXTUAL@1466..1482 + 0: MD_TEXTUAL_LITERAL@1466..1482 "user@example.com" [] [] + 2: R_ANGLE@1482..1483 ">" [] [] + 6: MD_TEXTUAL@1483..1492 + 0: MD_TEXTUAL_LITERAL@1483..1492 " address." [] [] + 7: MD_TEXTUAL@1492..1493 + 0: MD_TEXTUAL_LITERAL@1492..1493 "\n" [] [] + 1: (empty) + 46: MD_NEWLINE@1493..1494 + 0: NEWLINE@1493..1494 "\n" [] [] + 47: MD_HEADER@1494..1519 + 0: MD_HASH_LIST@1494..1496 + 0: MD_HASH@1494..1496 + 0: HASH@1494..1496 "##" [] [] + 1: MD_PARAGRAPH@1496..1519 + 0: MD_INLINE_ITEM_LIST@1496..1519 + 0: MD_TEXTUAL@1496..1519 + 0: MD_TEXTUAL_LITERAL@1496..1519 " Tag Names with Hyphens" [] [] + 1: (empty) + 2: MD_HASH_LIST@1519..1519 + 48: MD_NEWLINE@1519..1520 + 0: NEWLINE@1519..1520 "\n" [] [] + 49: MD_PARAGRAPH@1520..1633 + 0: MD_INLINE_ITEM_LIST@1520..1633 + 0: MD_TEXTUAL@1520..1528 + 0: MD_TEXTUAL_LITERAL@1520..1528 "Custom: " [] [] + 1: MD_INLINE_HTML@1528..1542 + 0: MD_INLINE_ITEM_LIST@1528..1542 + 0: MD_TEXTUAL@1528..1529 + 0: MD_TEXTUAL_LITERAL@1528..1529 "<" [] [] + 1: MD_TEXTUAL@1529..1531 + 0: MD_TEXTUAL_LITERAL@1529..1531 "my" [] [] + 2: MD_TEXTUAL@1531..1532 + 0: MD_TEXTUAL_LITERAL@1531..1532 "-" [] [] + 3: MD_TEXTUAL@1532..1541 + 0: MD_TEXTUAL_LITERAL@1532..1541 "component" [] [] + 4: MD_TEXTUAL@1541..1542 + 0: MD_TEXTUAL_LITERAL@1541..1542 ">" [] [] + 2: MD_TEXTUAL@1542..1549 + 0: MD_TEXTUAL_LITERAL@1542..1549 "content" [] [] + 3: MD_INLINE_HTML@1549..1564 + 0: MD_INLINE_ITEM_LIST@1549..1564 + 0: MD_TEXTUAL@1549..1550 + 0: MD_TEXTUAL_LITERAL@1549..1550 "<" [] [] + 1: MD_TEXTUAL@1550..1553 + 0: MD_TEXTUAL_LITERAL@1550..1553 "/my" [] [] + 2: MD_TEXTUAL@1553..1554 + 0: MD_TEXTUAL_LITERAL@1553..1554 "-" [] [] + 3: MD_TEXTUAL@1554..1563 + 0: MD_TEXTUAL_LITERAL@1554..1563 "component" [] [] + 4: MD_TEXTUAL@1563..1564 + 0: MD_TEXTUAL_LITERAL@1563..1564 ">" [] [] + 4: MD_TEXTUAL@1564..1573 + 0: MD_TEXTUAL_LITERAL@1564..1573 " element." [] [] + 5: MD_TEXTUAL@1573..1574 + 0: MD_TEXTUAL_LITERAL@1573..1574 "\n" [] [] + 6: MD_TEXTUAL@1574..1584 + 0: MD_TEXTUAL_LITERAL@1574..1584 "Multiple: " [] [] + 7: MD_INLINE_HTML@1584..1603 + 0: MD_INLINE_ITEM_LIST@1584..1603 + 0: MD_TEXTUAL@1584..1585 + 0: MD_TEXTUAL_LITERAL@1584..1585 "<" [] [] + 1: MD_TEXTUAL@1585..1587 + 0: MD_TEXTUAL_LITERAL@1585..1587 "my" [] [] + 2: MD_TEXTUAL@1587..1588 + 0: MD_TEXTUAL_LITERAL@1587..1588 "-" [] [] + 3: MD_TEXTUAL@1588..1594 + 0: MD_TEXTUAL_LITERAL@1588..1594 "custom" [] [] + 4: MD_TEXTUAL@1594..1595 + 0: MD_TEXTUAL_LITERAL@1594..1595 "-" [] [] + 5: MD_TEXTUAL@1595..1602 + 0: MD_TEXTUAL_LITERAL@1595..1602 "element" [] [] + 6: MD_TEXTUAL@1602..1603 + 0: MD_TEXTUAL_LITERAL@1602..1603 ">" [] [] + 8: MD_TEXTUAL@1603..1607 + 0: MD_TEXTUAL_LITERAL@1603..1607 "test" [] [] + 9: MD_INLINE_HTML@1607..1627 + 0: MD_INLINE_ITEM_LIST@1607..1627 + 0: MD_TEXTUAL@1607..1608 + 0: MD_TEXTUAL_LITERAL@1607..1608 "<" [] [] + 1: MD_TEXTUAL@1608..1611 + 0: MD_TEXTUAL_LITERAL@1608..1611 "/my" [] [] + 2: MD_TEXTUAL@1611..1612 + 0: MD_TEXTUAL_LITERAL@1611..1612 "-" [] [] + 3: MD_TEXTUAL@1612..1618 + 0: MD_TEXTUAL_LITERAL@1612..1618 "custom" [] [] + 4: MD_TEXTUAL@1618..1619 + 0: MD_TEXTUAL_LITERAL@1618..1619 "-" [] [] + 5: MD_TEXTUAL@1619..1626 + 0: MD_TEXTUAL_LITERAL@1619..1626 "element" [] [] + 6: MD_TEXTUAL@1626..1627 + 0: MD_TEXTUAL_LITERAL@1626..1627 ">" [] [] + 10: MD_TEXTUAL@1627..1632 + 0: MD_TEXTUAL_LITERAL@1627..1632 " tag." [] [] + 11: MD_TEXTUAL@1632..1633 + 0: MD_TEXTUAL_LITERAL@1632..1633 "\n" [] [] + 1: (empty) + 50: MD_NEWLINE@1633..1634 + 0: NEWLINE@1633..1634 "\n" [] [] + 51: MD_HEADER@1634..1647 + 0: MD_HASH_LIST@1634..1636 + 0: MD_HASH@1634..1636 + 0: HASH@1634..1636 "##" [] [] + 1: MD_PARAGRAPH@1636..1647 + 0: MD_INLINE_ITEM_LIST@1636..1647 + 0: MD_TEXTUAL@1636..1647 + 0: MD_TEXTUAL_LITERAL@1636..1647 " Empty Tags" [] [] + 1: (empty) + 2: MD_HASH_LIST@1647..1647 + 52: MD_NEWLINE@1647..1648 + 0: NEWLINE@1647..1648 "\n" [] [] + 53: MD_PARAGRAPH@1648..1703 + 0: MD_INLINE_ITEM_LIST@1648..1703 + 0: MD_TEXTUAL@1648..1660 + 0: MD_TEXTUAL_LITERAL@1648..1660 "Empty open: " [] [] + 1: MD_INLINE_HTML@1660..1665 + 0: MD_INLINE_ITEM_LIST@1660..1665 + 0: MD_TEXTUAL@1660..1661 + 0: MD_TEXTUAL_LITERAL@1660..1661 "<" [] [] + 1: MD_TEXTUAL@1661..1664 + 0: MD_TEXTUAL_LITERAL@1661..1664 "div" [] [] + 2: MD_TEXTUAL@1664..1665 + 0: MD_TEXTUAL_LITERAL@1664..1665 ">" [] [] + 2: MD_INLINE_HTML@1665..1671 + 0: MD_INLINE_ITEM_LIST@1665..1671 + 0: MD_TEXTUAL@1665..1666 + 0: MD_TEXTUAL_LITERAL@1665..1666 "<" [] [] + 1: MD_TEXTUAL@1666..1670 + 0: MD_TEXTUAL_LITERAL@1666..1670 "/div" [] [] + 2: MD_TEXTUAL@1670..1671 + 0: MD_TEXTUAL_LITERAL@1670..1671 ">" [] [] + 3: MD_TEXTUAL@1671..1677 + 0: MD_TEXTUAL_LITERAL@1671..1677 " tags." [] [] + 4: MD_TEXTUAL@1677..1678 + 0: MD_TEXTUAL_LITERAL@1677..1678 "\n" [] [] + 5: MD_TEXTUAL@1678..1690 + 0: MD_TEXTUAL_LITERAL@1678..1690 "Self close: " [] [] + 6: MD_INLINE_HTML@1690..1695 + 0: MD_INLINE_ITEM_LIST@1690..1695 + 0: MD_TEXTUAL@1690..1691 + 0: MD_TEXTUAL_LITERAL@1690..1691 "<" [] [] + 1: MD_TEXTUAL@1691..1694 + 0: MD_TEXTUAL_LITERAL@1691..1694 "br/" [] [] + 2: MD_TEXTUAL@1694..1695 + 0: MD_TEXTUAL_LITERAL@1694..1695 ">" [] [] + 7: MD_TEXTUAL@1695..1702 + 0: MD_TEXTUAL_LITERAL@1695..1702 " break." [] [] + 8: MD_TEXTUAL@1702..1703 + 0: MD_TEXTUAL_LITERAL@1702..1703 "\n" [] [] + 1: (empty) + 2: EOF@1703..1703 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_invalid.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_invalid.md new file mode 100644 index 000000000000..e75a898cc0f0 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_invalid.md @@ -0,0 +1,21 @@ +# Invalid Inline HTML Cases + +These should all be parsed as text, NOT as inline HTML. + +## Period in Tag Name +The URL should remain text. +Domain should remain text. + +## Unclosed Tags +Open bracket < followed by text. +Partial tag
    should be text. +Missing value
    should be text. +Backtick in unquoted
    should be text. +Invalid name
    should be text. + +## Invalid Comments +Invalid start should be text. +Double dash should be text maybe. +Starts with arrow should be text. diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_invalid.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_invalid.md.snap new file mode 100644 index 000000000000..a854a5ad813d --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_html_invalid.md.snap @@ -0,0 +1,687 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +# Invalid Inline HTML Cases + +These should all be parsed as text, NOT as inline HTML. + +## Period in Tag Name +The URL should remain text. +Domain should remain text. + +## Unclosed Tags +Open bracket < followed by text. +Partial tag
    should be text. +Missing value
    should be text. +Backtick in unquoted
    should be text. +Invalid name
    should be text. + +## Invalid Comments +Invalid start should be text. +Double dash should be text maybe. +Starts with arrow should be text. + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@0..1 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..27 " Invalid Inline HTML Cases" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@27..28 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@28..29 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..84 "These should all be parsed as text, NOT as inline HTML." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@84..85 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@85..86 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@86..88 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@88..107 " Period in Tag Name" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@107..108 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@108..116 "The URL " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@116..117 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@117..128 "example.com" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@128..129 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@129..149 " should remain text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@149..150 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@150..157 "Domain " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@157..158 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@158..174 "test.example.com" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@174..175 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@175..195 " should remain text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@195..196 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@196..197 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@197..199 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@199..213 " Unclosed Tags" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@213..214 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@214..227 "Open bracket " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@227..228 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@228..246 " followed by text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@246..247 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@247..259 "Partial tag " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@259..260 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@260..279 "div should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@279..280 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@280..292 "Missing end " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@292..293 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@293..325 "div class=\"test\" should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@325..326 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@326..339 "Missing name " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@339..340 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@340..348 "div =foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@348..349 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@349..365 " should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@365..366 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@366..380 "Missing value " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@380..381 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@381..389 "div data" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@389..390 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@390..392 "x=" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@392..393 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@393..409 " should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@409..410 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@410..431 "Backtick in unquoted " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@431..432 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@432..440 "div data" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@440..441 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@441..443 "x=" [] [], + }, + MdInlineCode { + l_tick_token: BACKTICK@443..444 "`" [] [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@444..445 "a" [] [], + }, + ], + r_tick_token: BACKTICK@445..446 "`" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@446..447 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@447..463 " should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@463..464 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@464..477 "Invalid name " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@477..478 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@478..488 "div 1a=foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@488..489 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@489..505 " should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@505..506 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@506..507 "\n" [] [], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@507..509 "##" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@509..526 " Invalid Comments" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@526..527 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@527..541 "Invalid start " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@541..542 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@542..543 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@543..544 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@544..545 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@545..546 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@546..562 " should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@562..563 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@563..575 "Double dash " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@575..576 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@576..577 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@577..578 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@578..579 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@579..584 " foo " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@584..585 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@585..586 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@586..591 " bar " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@591..592 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@592..593 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@593..594 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@594..616 " should be text maybe." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@616..617 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@617..635 "Starts with arrow " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@635..636 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@636..637 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@637..638 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@638..639 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@639..640 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@640..641 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@641..657 " should be text." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@657..658 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@658..658 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..658 + 0: (empty) + 1: MD_BLOCK_LIST@0..658 + 0: MD_HEADER@0..27 + 0: MD_HASH_LIST@0..1 + 0: MD_HASH@0..1 + 0: HASH@0..1 "#" [] [] + 1: MD_PARAGRAPH@1..27 + 0: MD_INLINE_ITEM_LIST@1..27 + 0: MD_TEXTUAL@1..27 + 0: MD_TEXTUAL_LITERAL@1..27 " Invalid Inline HTML Cases" [] [] + 1: (empty) + 2: MD_HASH_LIST@27..27 + 1: MD_NEWLINE@27..28 + 0: NEWLINE@27..28 "\n" [] [] + 2: MD_NEWLINE@28..29 + 0: NEWLINE@28..29 "\n" [] [] + 3: MD_PARAGRAPH@29..85 + 0: MD_INLINE_ITEM_LIST@29..85 + 0: MD_TEXTUAL@29..84 + 0: MD_TEXTUAL_LITERAL@29..84 "These should all be parsed as text, NOT as inline HTML." [] [] + 1: MD_TEXTUAL@84..85 + 0: MD_TEXTUAL_LITERAL@84..85 "\n" [] [] + 1: (empty) + 4: MD_NEWLINE@85..86 + 0: NEWLINE@85..86 "\n" [] [] + 5: MD_HEADER@86..107 + 0: MD_HASH_LIST@86..88 + 0: MD_HASH@86..88 + 0: HASH@86..88 "##" [] [] + 1: MD_PARAGRAPH@88..107 + 0: MD_INLINE_ITEM_LIST@88..107 + 0: MD_TEXTUAL@88..107 + 0: MD_TEXTUAL_LITERAL@88..107 " Period in Tag Name" [] [] + 1: (empty) + 2: MD_HASH_LIST@107..107 + 6: MD_NEWLINE@107..108 + 0: NEWLINE@107..108 "\n" [] [] + 7: MD_PARAGRAPH@108..196 + 0: MD_INLINE_ITEM_LIST@108..196 + 0: MD_TEXTUAL@108..116 + 0: MD_TEXTUAL_LITERAL@108..116 "The URL " [] [] + 1: MD_TEXTUAL@116..117 + 0: MD_TEXTUAL_LITERAL@116..117 "<" [] [] + 2: MD_TEXTUAL@117..128 + 0: MD_TEXTUAL_LITERAL@117..128 "example.com" [] [] + 3: MD_TEXTUAL@128..129 + 0: MD_TEXTUAL_LITERAL@128..129 ">" [] [] + 4: MD_TEXTUAL@129..149 + 0: MD_TEXTUAL_LITERAL@129..149 " should remain text." [] [] + 5: MD_TEXTUAL@149..150 + 0: MD_TEXTUAL_LITERAL@149..150 "\n" [] [] + 6: MD_TEXTUAL@150..157 + 0: MD_TEXTUAL_LITERAL@150..157 "Domain " [] [] + 7: MD_TEXTUAL@157..158 + 0: MD_TEXTUAL_LITERAL@157..158 "<" [] [] + 8: MD_TEXTUAL@158..174 + 0: MD_TEXTUAL_LITERAL@158..174 "test.example.com" [] [] + 9: MD_TEXTUAL@174..175 + 0: MD_TEXTUAL_LITERAL@174..175 ">" [] [] + 10: MD_TEXTUAL@175..195 + 0: MD_TEXTUAL_LITERAL@175..195 " should remain text." [] [] + 11: MD_TEXTUAL@195..196 + 0: MD_TEXTUAL_LITERAL@195..196 "\n" [] [] + 1: (empty) + 8: MD_NEWLINE@196..197 + 0: NEWLINE@196..197 "\n" [] [] + 9: MD_HEADER@197..213 + 0: MD_HASH_LIST@197..199 + 0: MD_HASH@197..199 + 0: HASH@197..199 "##" [] [] + 1: MD_PARAGRAPH@199..213 + 0: MD_INLINE_ITEM_LIST@199..213 + 0: MD_TEXTUAL@199..213 + 0: MD_TEXTUAL_LITERAL@199..213 " Unclosed Tags" [] [] + 1: (empty) + 2: MD_HASH_LIST@213..213 + 10: MD_NEWLINE@213..214 + 0: NEWLINE@213..214 "\n" [] [] + 11: MD_PARAGRAPH@214..506 + 0: MD_INLINE_ITEM_LIST@214..506 + 0: MD_TEXTUAL@214..227 + 0: MD_TEXTUAL_LITERAL@214..227 "Open bracket " [] [] + 1: MD_TEXTUAL@227..228 + 0: MD_TEXTUAL_LITERAL@227..228 "<" [] [] + 2: MD_TEXTUAL@228..246 + 0: MD_TEXTUAL_LITERAL@228..246 " followed by text." [] [] + 3: MD_TEXTUAL@246..247 + 0: MD_TEXTUAL_LITERAL@246..247 "\n" [] [] + 4: MD_TEXTUAL@247..259 + 0: MD_TEXTUAL_LITERAL@247..259 "Partial tag " [] [] + 5: MD_TEXTUAL@259..260 + 0: MD_TEXTUAL_LITERAL@259..260 "<" [] [] + 6: MD_TEXTUAL@260..279 + 0: MD_TEXTUAL_LITERAL@260..279 "div should be text." [] [] + 7: MD_TEXTUAL@279..280 + 0: MD_TEXTUAL_LITERAL@279..280 "\n" [] [] + 8: MD_TEXTUAL@280..292 + 0: MD_TEXTUAL_LITERAL@280..292 "Missing end " [] [] + 9: MD_TEXTUAL@292..293 + 0: MD_TEXTUAL_LITERAL@292..293 "<" [] [] + 10: MD_TEXTUAL@293..325 + 0: MD_TEXTUAL_LITERAL@293..325 "div class=\"test\" should be text." [] [] + 11: MD_TEXTUAL@325..326 + 0: MD_TEXTUAL_LITERAL@325..326 "\n" [] [] + 12: MD_TEXTUAL@326..339 + 0: MD_TEXTUAL_LITERAL@326..339 "Missing name " [] [] + 13: MD_TEXTUAL@339..340 + 0: MD_TEXTUAL_LITERAL@339..340 "<" [] [] + 14: MD_TEXTUAL@340..348 + 0: MD_TEXTUAL_LITERAL@340..348 "div =foo" [] [] + 15: MD_TEXTUAL@348..349 + 0: MD_TEXTUAL_LITERAL@348..349 ">" [] [] + 16: MD_TEXTUAL@349..365 + 0: MD_TEXTUAL_LITERAL@349..365 " should be text." [] [] + 17: MD_TEXTUAL@365..366 + 0: MD_TEXTUAL_LITERAL@365..366 "\n" [] [] + 18: MD_TEXTUAL@366..380 + 0: MD_TEXTUAL_LITERAL@366..380 "Missing value " [] [] + 19: MD_TEXTUAL@380..381 + 0: MD_TEXTUAL_LITERAL@380..381 "<" [] [] + 20: MD_TEXTUAL@381..389 + 0: MD_TEXTUAL_LITERAL@381..389 "div data" [] [] + 21: MD_TEXTUAL@389..390 + 0: MD_TEXTUAL_LITERAL@389..390 "-" [] [] + 22: MD_TEXTUAL@390..392 + 0: MD_TEXTUAL_LITERAL@390..392 "x=" [] [] + 23: MD_TEXTUAL@392..393 + 0: MD_TEXTUAL_LITERAL@392..393 ">" [] [] + 24: MD_TEXTUAL@393..409 + 0: MD_TEXTUAL_LITERAL@393..409 " should be text." [] [] + 25: MD_TEXTUAL@409..410 + 0: MD_TEXTUAL_LITERAL@409..410 "\n" [] [] + 26: MD_TEXTUAL@410..431 + 0: MD_TEXTUAL_LITERAL@410..431 "Backtick in unquoted " [] [] + 27: MD_TEXTUAL@431..432 + 0: MD_TEXTUAL_LITERAL@431..432 "<" [] [] + 28: MD_TEXTUAL@432..440 + 0: MD_TEXTUAL_LITERAL@432..440 "div data" [] [] + 29: MD_TEXTUAL@440..441 + 0: MD_TEXTUAL_LITERAL@440..441 "-" [] [] + 30: MD_TEXTUAL@441..443 + 0: MD_TEXTUAL_LITERAL@441..443 "x=" [] [] + 31: MD_INLINE_CODE@443..446 + 0: BACKTICK@443..444 "`" [] [] + 1: MD_INLINE_ITEM_LIST@444..445 + 0: MD_TEXTUAL@444..445 + 0: MD_TEXTUAL_LITERAL@444..445 "a" [] [] + 2: BACKTICK@445..446 "`" [] [] + 32: MD_TEXTUAL@446..447 + 0: MD_TEXTUAL_LITERAL@446..447 ">" [] [] + 33: MD_TEXTUAL@447..463 + 0: MD_TEXTUAL_LITERAL@447..463 " should be text." [] [] + 34: MD_TEXTUAL@463..464 + 0: MD_TEXTUAL_LITERAL@463..464 "\n" [] [] + 35: MD_TEXTUAL@464..477 + 0: MD_TEXTUAL_LITERAL@464..477 "Invalid name " [] [] + 36: MD_TEXTUAL@477..478 + 0: MD_TEXTUAL_LITERAL@477..478 "<" [] [] + 37: MD_TEXTUAL@478..488 + 0: MD_TEXTUAL_LITERAL@478..488 "div 1a=foo" [] [] + 38: MD_TEXTUAL@488..489 + 0: MD_TEXTUAL_LITERAL@488..489 ">" [] [] + 39: MD_TEXTUAL@489..505 + 0: MD_TEXTUAL_LITERAL@489..505 " should be text." [] [] + 40: MD_TEXTUAL@505..506 + 0: MD_TEXTUAL_LITERAL@505..506 "\n" [] [] + 1: (empty) + 12: MD_NEWLINE@506..507 + 0: NEWLINE@506..507 "\n" [] [] + 13: MD_HEADER@507..526 + 0: MD_HASH_LIST@507..509 + 0: MD_HASH@507..509 + 0: HASH@507..509 "##" [] [] + 1: MD_PARAGRAPH@509..526 + 0: MD_INLINE_ITEM_LIST@509..526 + 0: MD_TEXTUAL@509..526 + 0: MD_TEXTUAL_LITERAL@509..526 " Invalid Comments" [] [] + 1: (empty) + 2: MD_HASH_LIST@526..526 + 14: MD_NEWLINE@526..527 + 0: NEWLINE@526..527 "\n" [] [] + 15: MD_PARAGRAPH@527..658 + 0: MD_INLINE_ITEM_LIST@527..658 + 0: MD_TEXTUAL@527..541 + 0: MD_TEXTUAL_LITERAL@527..541 "Invalid start " [] [] + 1: MD_INLINE_HTML@541..546 + 0: MD_INLINE_ITEM_LIST@541..546 + 0: MD_TEXTUAL@541..542 + 0: MD_TEXTUAL_LITERAL@541..542 "<" [] [] + 1: MD_TEXTUAL@542..543 + 0: MD_TEXTUAL_LITERAL@542..543 "!" [] [] + 2: MD_TEXTUAL@543..544 + 0: MD_TEXTUAL_LITERAL@543..544 "-" [] [] + 3: MD_TEXTUAL@544..545 + 0: MD_TEXTUAL_LITERAL@544..545 "-" [] [] + 4: MD_TEXTUAL@545..546 + 0: MD_TEXTUAL_LITERAL@545..546 ">" [] [] + 2: MD_TEXTUAL@546..562 + 0: MD_TEXTUAL_LITERAL@546..562 " should be text." [] [] + 3: MD_TEXTUAL@562..563 + 0: MD_TEXTUAL_LITERAL@562..563 "\n" [] [] + 4: MD_TEXTUAL@563..575 + 0: MD_TEXTUAL_LITERAL@563..575 "Double dash " [] [] + 5: MD_INLINE_HTML@575..594 + 0: MD_INLINE_ITEM_LIST@575..594 + 0: MD_TEXTUAL@575..576 + 0: MD_TEXTUAL_LITERAL@575..576 "<" [] [] + 1: MD_TEXTUAL@576..577 + 0: MD_TEXTUAL_LITERAL@576..577 "!" [] [] + 2: MD_TEXTUAL@577..578 + 0: MD_TEXTUAL_LITERAL@577..578 "-" [] [] + 3: MD_TEXTUAL@578..579 + 0: MD_TEXTUAL_LITERAL@578..579 "-" [] [] + 4: MD_TEXTUAL@579..584 + 0: MD_TEXTUAL_LITERAL@579..584 " foo " [] [] + 5: MD_TEXTUAL@584..585 + 0: MD_TEXTUAL_LITERAL@584..585 "-" [] [] + 6: MD_TEXTUAL@585..586 + 0: MD_TEXTUAL_LITERAL@585..586 "-" [] [] + 7: MD_TEXTUAL@586..591 + 0: MD_TEXTUAL_LITERAL@586..591 " bar " [] [] + 8: MD_TEXTUAL@591..592 + 0: MD_TEXTUAL_LITERAL@591..592 "-" [] [] + 9: MD_TEXTUAL@592..593 + 0: MD_TEXTUAL_LITERAL@592..593 "-" [] [] + 10: MD_TEXTUAL@593..594 + 0: MD_TEXTUAL_LITERAL@593..594 ">" [] [] + 6: MD_TEXTUAL@594..616 + 0: MD_TEXTUAL_LITERAL@594..616 " should be text maybe." [] [] + 7: MD_TEXTUAL@616..617 + 0: MD_TEXTUAL_LITERAL@616..617 "\n" [] [] + 8: MD_TEXTUAL@617..635 + 0: MD_TEXTUAL_LITERAL@617..635 "Starts with arrow " [] [] + 9: MD_INLINE_HTML@635..641 + 0: MD_INLINE_ITEM_LIST@635..641 + 0: MD_TEXTUAL@635..636 + 0: MD_TEXTUAL_LITERAL@635..636 "<" [] [] + 1: MD_TEXTUAL@636..637 + 0: MD_TEXTUAL_LITERAL@636..637 "!" [] [] + 2: MD_TEXTUAL@637..638 + 0: MD_TEXTUAL_LITERAL@637..638 "-" [] [] + 3: MD_TEXTUAL@638..639 + 0: MD_TEXTUAL_LITERAL@638..639 "-" [] [] + 4: MD_TEXTUAL@639..640 + 0: MD_TEXTUAL_LITERAL@639..640 "-" [] [] + 5: MD_TEXTUAL@640..641 + 0: MD_TEXTUAL_LITERAL@640..641 ">" [] [] + 10: MD_TEXTUAL@641..657 + 0: MD_TEXTUAL_LITERAL@641..657 " should be text." [] [] + 11: MD_TEXTUAL@657..658 + 0: MD_TEXTUAL_LITERAL@657..658 "\n" [] [] + 1: (empty) + 2: EOF@658..658 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_destination_title.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_destination_title.md new file mode 100644 index 000000000000..0f4293b8ad7c --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_destination_title.md @@ -0,0 +1,11 @@ +Paren destination: [a](b(c)d) + +Angle destination: [a]() + +Title double: [a](u "t") + +Title single: [a](u 't') + +Title paren: [a](u (t)) + +Image title: ![alt](u "t") diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_destination_title.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_destination_title.md.snap new file mode 100644 index 000000000000..f173cc8fd0dc --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_destination_title.md.snap @@ -0,0 +1,468 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Paren destination: [a](b(c)d) + +Angle destination: [a]() + +Title double: [a](u "t") + +Title single: [a](u 't') + +Title paren: [a](u (t)) + +Image title: ![alt](u "t") + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..19 "Paren destination: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@19..20 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@20..21 "a" [] [], + }, + ], + r_brack_token: R_BRACK@21..22 "]" [] [], + l_paren_token: L_PAREN@22..23 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..24 "b" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..25 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@25..26 "c" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@26..27 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@27..28 "d" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@28..29 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..30 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@30..31 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..50 "Angle destination: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@50..51 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@51..52 "a" [] [], + }, + ], + r_brack_token: R_BRACK@52..53 "]" [] [], + l_paren_token: L_PAREN@53..54 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@54..55 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@55..56 "b" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..57 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@57..58 "c" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..59 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@59..60 "d" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@60..61 ">" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@61..62 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@62..63 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@63..64 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@64..78 "Title double: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@78..79 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@79..80 "a" [] [], + }, + ], + r_brack_token: R_BRACK@80..81 "]" [] [], + l_paren_token: L_PAREN@81..82 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@82..83 "u" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@83..84 " " [] [], + }, + ], + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@84..87 "\"t\"" [] [], + }, + ], + }, + r_paren_token: R_PAREN@87..88 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@88..89 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@89..90 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@90..104 "Title single: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@104..105 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@105..106 "a" [] [], + }, + ], + r_brack_token: R_BRACK@106..107 "]" [] [], + l_paren_token: L_PAREN@107..108 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@108..109 "u" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@109..110 " " [] [], + }, + ], + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@110..113 "'t'" [] [], + }, + ], + }, + r_paren_token: R_PAREN@113..114 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@114..115 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@115..116 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@116..129 "Title paren: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@129..130 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@130..131 "a" [] [], + }, + ], + r_brack_token: R_BRACK@131..132 "]" [] [], + l_paren_token: L_PAREN@132..133 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@133..134 "u" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@134..135 " " [] [], + }, + ], + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@135..136 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@136..137 "t" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@137..138 ")" [] [], + }, + ], + }, + r_paren_token: R_PAREN@138..139 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@139..140 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@140..141 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@141..154 "Image title: " [] [], + }, + MdInlineImage { + excl_token: BANG@154..155 "!" [] [], + l_brack_token: L_BRACK@155..156 "[" [] [], + alt: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@156..159 "alt" [] [], + }, + ], + r_brack_token: R_BRACK@159..160 "]" [] [], + l_paren_token: L_PAREN@160..161 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@161..162 "u" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@162..163 " " [] [], + }, + ], + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@163..166 "\"t\"" [] [], + }, + ], + }, + r_paren_token: R_PAREN@166..167 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@167..168 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@168..168 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..168 + 0: (empty) + 1: MD_BLOCK_LIST@0..168 + 0: MD_PARAGRAPH@0..30 + 0: MD_INLINE_ITEM_LIST@0..30 + 0: MD_TEXTUAL@0..19 + 0: MD_TEXTUAL_LITERAL@0..19 "Paren destination: " [] [] + 1: MD_INLINE_LINK@19..29 + 0: L_BRACK@19..20 "[" [] [] + 1: MD_INLINE_ITEM_LIST@20..21 + 0: MD_TEXTUAL@20..21 + 0: MD_TEXTUAL_LITERAL@20..21 "a" [] [] + 2: R_BRACK@21..22 "]" [] [] + 3: L_PAREN@22..23 "(" [] [] + 4: MD_INLINE_ITEM_LIST@23..28 + 0: MD_TEXTUAL@23..24 + 0: MD_TEXTUAL_LITERAL@23..24 "b" [] [] + 1: MD_TEXTUAL@24..25 + 0: MD_TEXTUAL_LITERAL@24..25 "(" [] [] + 2: MD_TEXTUAL@25..26 + 0: MD_TEXTUAL_LITERAL@25..26 "c" [] [] + 3: MD_TEXTUAL@26..27 + 0: MD_TEXTUAL_LITERAL@26..27 ")" [] [] + 4: MD_TEXTUAL@27..28 + 0: MD_TEXTUAL_LITERAL@27..28 "d" [] [] + 5: (empty) + 6: R_PAREN@28..29 ")" [] [] + 2: MD_TEXTUAL@29..30 + 0: MD_TEXTUAL_LITERAL@29..30 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@30..31 + 0: NEWLINE@30..31 "\n" [] [] + 2: MD_PARAGRAPH@31..63 + 0: MD_INLINE_ITEM_LIST@31..63 + 0: MD_TEXTUAL@31..50 + 0: MD_TEXTUAL_LITERAL@31..50 "Angle destination: " [] [] + 1: MD_INLINE_LINK@50..62 + 0: L_BRACK@50..51 "[" [] [] + 1: MD_INLINE_ITEM_LIST@51..52 + 0: MD_TEXTUAL@51..52 + 0: MD_TEXTUAL_LITERAL@51..52 "a" [] [] + 2: R_BRACK@52..53 "]" [] [] + 3: L_PAREN@53..54 "(" [] [] + 4: MD_INLINE_ITEM_LIST@54..61 + 0: MD_TEXTUAL@54..55 + 0: MD_TEXTUAL_LITERAL@54..55 "<" [] [] + 1: MD_TEXTUAL@55..56 + 0: MD_TEXTUAL_LITERAL@55..56 "b" [] [] + 2: MD_TEXTUAL@56..57 + 0: MD_TEXTUAL_LITERAL@56..57 "(" [] [] + 3: MD_TEXTUAL@57..58 + 0: MD_TEXTUAL_LITERAL@57..58 "c" [] [] + 4: MD_TEXTUAL@58..59 + 0: MD_TEXTUAL_LITERAL@58..59 ")" [] [] + 5: MD_TEXTUAL@59..60 + 0: MD_TEXTUAL_LITERAL@59..60 "d" [] [] + 6: MD_TEXTUAL@60..61 + 0: MD_TEXTUAL_LITERAL@60..61 ">" [] [] + 5: (empty) + 6: R_PAREN@61..62 ")" [] [] + 2: MD_TEXTUAL@62..63 + 0: MD_TEXTUAL_LITERAL@62..63 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@63..64 + 0: NEWLINE@63..64 "\n" [] [] + 4: MD_PARAGRAPH@64..89 + 0: MD_INLINE_ITEM_LIST@64..89 + 0: MD_TEXTUAL@64..78 + 0: MD_TEXTUAL_LITERAL@64..78 "Title double: " [] [] + 1: MD_INLINE_LINK@78..88 + 0: L_BRACK@78..79 "[" [] [] + 1: MD_INLINE_ITEM_LIST@79..80 + 0: MD_TEXTUAL@79..80 + 0: MD_TEXTUAL_LITERAL@79..80 "a" [] [] + 2: R_BRACK@80..81 "]" [] [] + 3: L_PAREN@81..82 "(" [] [] + 4: MD_INLINE_ITEM_LIST@82..84 + 0: MD_TEXTUAL@82..83 + 0: MD_TEXTUAL_LITERAL@82..83 "u" [] [] + 1: MD_TEXTUAL@83..84 + 0: MD_TEXTUAL_LITERAL@83..84 " " [] [] + 5: MD_LINK_TITLE@84..87 + 0: MD_INLINE_ITEM_LIST@84..87 + 0: MD_TEXTUAL@84..87 + 0: MD_TEXTUAL_LITERAL@84..87 "\"t\"" [] [] + 6: R_PAREN@87..88 ")" [] [] + 2: MD_TEXTUAL@88..89 + 0: MD_TEXTUAL_LITERAL@88..89 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@89..90 + 0: NEWLINE@89..90 "\n" [] [] + 6: MD_PARAGRAPH@90..115 + 0: MD_INLINE_ITEM_LIST@90..115 + 0: MD_TEXTUAL@90..104 + 0: MD_TEXTUAL_LITERAL@90..104 "Title single: " [] [] + 1: MD_INLINE_LINK@104..114 + 0: L_BRACK@104..105 "[" [] [] + 1: MD_INLINE_ITEM_LIST@105..106 + 0: MD_TEXTUAL@105..106 + 0: MD_TEXTUAL_LITERAL@105..106 "a" [] [] + 2: R_BRACK@106..107 "]" [] [] + 3: L_PAREN@107..108 "(" [] [] + 4: MD_INLINE_ITEM_LIST@108..110 + 0: MD_TEXTUAL@108..109 + 0: MD_TEXTUAL_LITERAL@108..109 "u" [] [] + 1: MD_TEXTUAL@109..110 + 0: MD_TEXTUAL_LITERAL@109..110 " " [] [] + 5: MD_LINK_TITLE@110..113 + 0: MD_INLINE_ITEM_LIST@110..113 + 0: MD_TEXTUAL@110..113 + 0: MD_TEXTUAL_LITERAL@110..113 "'t'" [] [] + 6: R_PAREN@113..114 ")" [] [] + 2: MD_TEXTUAL@114..115 + 0: MD_TEXTUAL_LITERAL@114..115 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@115..116 + 0: NEWLINE@115..116 "\n" [] [] + 8: MD_PARAGRAPH@116..140 + 0: MD_INLINE_ITEM_LIST@116..140 + 0: MD_TEXTUAL@116..129 + 0: MD_TEXTUAL_LITERAL@116..129 "Title paren: " [] [] + 1: MD_INLINE_LINK@129..139 + 0: L_BRACK@129..130 "[" [] [] + 1: MD_INLINE_ITEM_LIST@130..131 + 0: MD_TEXTUAL@130..131 + 0: MD_TEXTUAL_LITERAL@130..131 "a" [] [] + 2: R_BRACK@131..132 "]" [] [] + 3: L_PAREN@132..133 "(" [] [] + 4: MD_INLINE_ITEM_LIST@133..135 + 0: MD_TEXTUAL@133..134 + 0: MD_TEXTUAL_LITERAL@133..134 "u" [] [] + 1: MD_TEXTUAL@134..135 + 0: MD_TEXTUAL_LITERAL@134..135 " " [] [] + 5: MD_LINK_TITLE@135..138 + 0: MD_INLINE_ITEM_LIST@135..138 + 0: MD_TEXTUAL@135..136 + 0: MD_TEXTUAL_LITERAL@135..136 "(" [] [] + 1: MD_TEXTUAL@136..137 + 0: MD_TEXTUAL_LITERAL@136..137 "t" [] [] + 2: MD_TEXTUAL@137..138 + 0: MD_TEXTUAL_LITERAL@137..138 ")" [] [] + 6: R_PAREN@138..139 ")" [] [] + 2: MD_TEXTUAL@139..140 + 0: MD_TEXTUAL_LITERAL@139..140 "\n" [] [] + 1: (empty) + 9: MD_NEWLINE@140..141 + 0: NEWLINE@140..141 "\n" [] [] + 10: MD_PARAGRAPH@141..168 + 0: MD_INLINE_ITEM_LIST@141..168 + 0: MD_TEXTUAL@141..154 + 0: MD_TEXTUAL_LITERAL@141..154 "Image title: " [] [] + 1: MD_INLINE_IMAGE@154..167 + 0: BANG@154..155 "!" [] [] + 1: L_BRACK@155..156 "[" [] [] + 2: MD_INLINE_ITEM_LIST@156..159 + 0: MD_TEXTUAL@156..159 + 0: MD_TEXTUAL_LITERAL@156..159 "alt" [] [] + 3: R_BRACK@159..160 "]" [] [] + 4: L_PAREN@160..161 "(" [] [] + 5: MD_INLINE_ITEM_LIST@161..163 + 0: MD_TEXTUAL@161..162 + 0: MD_TEXTUAL_LITERAL@161..162 "u" [] [] + 1: MD_TEXTUAL@162..163 + 0: MD_TEXTUAL_LITERAL@162..163 " " [] [] + 6: MD_LINK_TITLE@163..166 + 0: MD_INLINE_ITEM_LIST@163..166 + 0: MD_TEXTUAL@163..166 + 0: MD_TEXTUAL_LITERAL@163..166 "\"t\"" [] [] + 7: R_PAREN@166..167 ")" [] [] + 2: MD_TEXTUAL@167..168 + 0: MD_TEXTUAL_LITERAL@167..168 "\n" [] [] + 1: (empty) + 2: EOF@168..168 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_whitespace.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_whitespace.md new file mode 100644 index 000000000000..502a1004733e --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_whitespace.md @@ -0,0 +1,17 @@ +Whitespace around destination and title: +[link]( /uri + "title" ) + +Title spanning lines: +[link](/url "title +continued") + +Line break between destination and title: +[link](/uri +"title") + +Leading whitespace before destination: +[link]( /url) + +Trailing whitespace before close paren: +[link](/url ) diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_whitespace.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_whitespace.md.snap new file mode 100644 index 000000000000..d984449d3f4a --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/inline_link_whitespace.md.snap @@ -0,0 +1,406 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Whitespace around destination and title: +[link]( /uri + "title" ) + +Title spanning lines: +[link](/url "title +continued") + +Line break between destination and title: +[link](/uri +"title") + +Leading whitespace before destination: +[link]( /url) + +Trailing whitespace before close paren: +[link](/url ) + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..40 "Whitespace around destination and title:" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..41 "\n" [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@41..42 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..46 "link" [] [], + }, + ], + r_brack_token: R_BRACK@46..47 "]" [] [], + l_paren_token: L_PAREN@47..48 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@48..51 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@51..55 "/uri" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@55..56 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..57 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@57..58 " " [] [], + }, + ], + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..65 "\"title\"" [] [], + }, + ], + }, + r_paren_token: R_PAREN@65..68 " )" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..69 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@69..70 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@70..91 "Title spanning lines:" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@91..92 "\n" [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@92..93 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@93..97 "link" [] [], + }, + ], + r_brack_token: R_BRACK@97..98 "]" [] [], + l_paren_token: L_PAREN@98..99 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@99..103 "/url" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@103..104 " " [] [], + }, + ], + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@104..110 "\"title" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@110..111 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@111..121 "continued\"" [] [], + }, + ], + }, + r_paren_token: R_PAREN@121..122 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@122..123 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@123..124 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@124..165 "Line break between destination and title:" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@165..166 "\n" [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@166..167 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@167..171 "link" [] [], + }, + ], + r_brack_token: R_BRACK@171..172 "]" [] [], + l_paren_token: L_PAREN@172..173 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@173..177 "/uri" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@177..178 "\n" [] [], + }, + ], + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@178..185 "\"title\"" [] [], + }, + ], + }, + r_paren_token: R_PAREN@185..186 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@186..187 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@187..188 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@188..226 "Leading whitespace before destination:" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@226..227 "\n" [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@227..228 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@228..232 "link" [] [], + }, + ], + r_brack_token: R_BRACK@232..233 "]" [] [], + l_paren_token: L_PAREN@233..234 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@234..237 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@237..241 "/url" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@241..242 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@242..243 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@243..244 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@244..283 "Trailing whitespace before close paren:" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@283..284 "\n" [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@284..285 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@285..289 "link" [] [], + }, + ], + r_brack_token: R_BRACK@289..290 "]" [] [], + l_paren_token: L_PAREN@290..291 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@291..295 "/url" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@295..298 " " [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@298..299 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@299..300 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@300..300 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..300 + 0: (empty) + 1: MD_BLOCK_LIST@0..300 + 0: MD_PARAGRAPH@0..69 + 0: MD_INLINE_ITEM_LIST@0..69 + 0: MD_TEXTUAL@0..40 + 0: MD_TEXTUAL_LITERAL@0..40 "Whitespace around destination and title:" [] [] + 1: MD_TEXTUAL@40..41 + 0: MD_TEXTUAL_LITERAL@40..41 "\n" [] [] + 2: MD_INLINE_LINK@41..68 + 0: L_BRACK@41..42 "[" [] [] + 1: MD_INLINE_ITEM_LIST@42..46 + 0: MD_TEXTUAL@42..46 + 0: MD_TEXTUAL_LITERAL@42..46 "link" [] [] + 2: R_BRACK@46..47 "]" [] [] + 3: L_PAREN@47..48 "(" [] [] + 4: MD_INLINE_ITEM_LIST@48..58 + 0: MD_TEXTUAL@48..51 + 0: MD_TEXTUAL_LITERAL@48..51 " " [] [] + 1: MD_TEXTUAL@51..55 + 0: MD_TEXTUAL_LITERAL@51..55 "/uri" [] [] + 2: MD_TEXTUAL@55..56 + 0: MD_TEXTUAL_LITERAL@55..56 "\n" [] [] + 3: MD_TEXTUAL@56..57 + 0: MD_TEXTUAL_LITERAL@56..57 " " [] [] + 4: MD_TEXTUAL@57..58 + 0: MD_TEXTUAL_LITERAL@57..58 " " [] [] + 5: MD_LINK_TITLE@58..65 + 0: MD_INLINE_ITEM_LIST@58..65 + 0: MD_TEXTUAL@58..65 + 0: MD_TEXTUAL_LITERAL@58..65 "\"title\"" [] [] + 6: R_PAREN@65..68 " )" [] [] + 3: MD_TEXTUAL@68..69 + 0: MD_TEXTUAL_LITERAL@68..69 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@69..70 + 0: NEWLINE@69..70 "\n" [] [] + 2: MD_PARAGRAPH@70..123 + 0: MD_INLINE_ITEM_LIST@70..123 + 0: MD_TEXTUAL@70..91 + 0: MD_TEXTUAL_LITERAL@70..91 "Title spanning lines:" [] [] + 1: MD_TEXTUAL@91..92 + 0: MD_TEXTUAL_LITERAL@91..92 "\n" [] [] + 2: MD_INLINE_LINK@92..122 + 0: L_BRACK@92..93 "[" [] [] + 1: MD_INLINE_ITEM_LIST@93..97 + 0: MD_TEXTUAL@93..97 + 0: MD_TEXTUAL_LITERAL@93..97 "link" [] [] + 2: R_BRACK@97..98 "]" [] [] + 3: L_PAREN@98..99 "(" [] [] + 4: MD_INLINE_ITEM_LIST@99..104 + 0: MD_TEXTUAL@99..103 + 0: MD_TEXTUAL_LITERAL@99..103 "/url" [] [] + 1: MD_TEXTUAL@103..104 + 0: MD_TEXTUAL_LITERAL@103..104 " " [] [] + 5: MD_LINK_TITLE@104..121 + 0: MD_INLINE_ITEM_LIST@104..121 + 0: MD_TEXTUAL@104..110 + 0: MD_TEXTUAL_LITERAL@104..110 "\"title" [] [] + 1: MD_TEXTUAL@110..111 + 0: MD_TEXTUAL_LITERAL@110..111 "\n" [] [] + 2: MD_TEXTUAL@111..121 + 0: MD_TEXTUAL_LITERAL@111..121 "continued\"" [] [] + 6: R_PAREN@121..122 ")" [] [] + 3: MD_TEXTUAL@122..123 + 0: MD_TEXTUAL_LITERAL@122..123 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@123..124 + 0: NEWLINE@123..124 "\n" [] [] + 4: MD_PARAGRAPH@124..187 + 0: MD_INLINE_ITEM_LIST@124..187 + 0: MD_TEXTUAL@124..165 + 0: MD_TEXTUAL_LITERAL@124..165 "Line break between destination and title:" [] [] + 1: MD_TEXTUAL@165..166 + 0: MD_TEXTUAL_LITERAL@165..166 "\n" [] [] + 2: MD_INLINE_LINK@166..186 + 0: L_BRACK@166..167 "[" [] [] + 1: MD_INLINE_ITEM_LIST@167..171 + 0: MD_TEXTUAL@167..171 + 0: MD_TEXTUAL_LITERAL@167..171 "link" [] [] + 2: R_BRACK@171..172 "]" [] [] + 3: L_PAREN@172..173 "(" [] [] + 4: MD_INLINE_ITEM_LIST@173..178 + 0: MD_TEXTUAL@173..177 + 0: MD_TEXTUAL_LITERAL@173..177 "/uri" [] [] + 1: MD_TEXTUAL@177..178 + 0: MD_TEXTUAL_LITERAL@177..178 "\n" [] [] + 5: MD_LINK_TITLE@178..185 + 0: MD_INLINE_ITEM_LIST@178..185 + 0: MD_TEXTUAL@178..185 + 0: MD_TEXTUAL_LITERAL@178..185 "\"title\"" [] [] + 6: R_PAREN@185..186 ")" [] [] + 3: MD_TEXTUAL@186..187 + 0: MD_TEXTUAL_LITERAL@186..187 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@187..188 + 0: NEWLINE@187..188 "\n" [] [] + 6: MD_PARAGRAPH@188..243 + 0: MD_INLINE_ITEM_LIST@188..243 + 0: MD_TEXTUAL@188..226 + 0: MD_TEXTUAL_LITERAL@188..226 "Leading whitespace before destination:" [] [] + 1: MD_TEXTUAL@226..227 + 0: MD_TEXTUAL_LITERAL@226..227 "\n" [] [] + 2: MD_INLINE_LINK@227..242 + 0: L_BRACK@227..228 "[" [] [] + 1: MD_INLINE_ITEM_LIST@228..232 + 0: MD_TEXTUAL@228..232 + 0: MD_TEXTUAL_LITERAL@228..232 "link" [] [] + 2: R_BRACK@232..233 "]" [] [] + 3: L_PAREN@233..234 "(" [] [] + 4: MD_INLINE_ITEM_LIST@234..241 + 0: MD_TEXTUAL@234..237 + 0: MD_TEXTUAL_LITERAL@234..237 " " [] [] + 1: MD_TEXTUAL@237..241 + 0: MD_TEXTUAL_LITERAL@237..241 "/url" [] [] + 5: (empty) + 6: R_PAREN@241..242 ")" [] [] + 3: MD_TEXTUAL@242..243 + 0: MD_TEXTUAL_LITERAL@242..243 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@243..244 + 0: NEWLINE@243..244 "\n" [] [] + 8: MD_PARAGRAPH@244..300 + 0: MD_INLINE_ITEM_LIST@244..300 + 0: MD_TEXTUAL@244..283 + 0: MD_TEXTUAL_LITERAL@244..283 "Trailing whitespace before close paren:" [] [] + 1: MD_TEXTUAL@283..284 + 0: MD_TEXTUAL_LITERAL@283..284 "\n" [] [] + 2: MD_INLINE_LINK@284..299 + 0: L_BRACK@284..285 "[" [] [] + 1: MD_INLINE_ITEM_LIST@285..289 + 0: MD_TEXTUAL@285..289 + 0: MD_TEXTUAL_LITERAL@285..289 "link" [] [] + 2: R_BRACK@289..290 "]" [] [] + 3: L_PAREN@290..291 "(" [] [] + 4: MD_INLINE_ITEM_LIST@291..298 + 0: MD_TEXTUAL@291..295 + 0: MD_TEXTUAL_LITERAL@291..295 "/url" [] [] + 1: MD_TEXTUAL@295..298 + 0: MD_TEXTUAL_LITERAL@295..298 " " [] [] + 5: (empty) + 6: R_PAREN@298..299 ")" [] [] + 3: MD_TEXTUAL@299..300 + 0: MD_TEXTUAL_LITERAL@299..300 "\n" [] [] + 1: (empty) + 2: EOF@300..300 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/lazy_continuation.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/lazy_continuation.md new file mode 100644 index 000000000000..33298e8d5795 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/lazy_continuation.md @@ -0,0 +1,31 @@ +> This is a quote +that continues lazily + +> Another quote +with lazy continuation +> and explicit continuation + +> Multi-line quote +with more lazy content +and even more content + +> Quote interrupted by heading +# This heading ends the lazy continuation + +> Quote interrupted by setext heading +Setext heading +--- + +> Quote interrupted by list +- This list item ends lazy continuation + +> Quote interrupted by fenced code +``` +code block +``` + +> Quote interrupted by thematic break +--- + +> Quote interrupted by indented code + This is an indented code block diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/lazy_continuation.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/lazy_continuation.md.snap new file mode 100644 index 000000000000..1b6ba21153b2 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/lazy_continuation.md.snap @@ -0,0 +1,532 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +> This is a quote +that continues lazily + +> Another quote +with lazy continuation +> and explicit continuation + +> Multi-line quote +with more lazy content +and even more content + +> Quote interrupted by heading +# This heading ends the lazy continuation + +> Quote interrupted by setext heading +Setext heading +--- + +> Quote interrupted by list +- This list item ends lazy continuation + +> Quote interrupted by fenced code +``` +code block +``` + +> Quote interrupted by thematic break +--- + +> Quote interrupted by indented code + This is an indented code block + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@0..1 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..17 "This is a quote" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@17..18 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@18..39 "that continues lazily" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@39..40 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@40..41 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@41..42 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..56 "Another quote" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..57 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@57..79 "with lazy continuation" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@79..80 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..107 "and explicit continuation" [Skipped(">"), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@107..108 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@108..109 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@109..110 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@110..116 "Multi" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@116..117 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@117..127 "line quote" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@127..128 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@128..150 "with more lazy content" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@150..151 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@151..172 "and even more content" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@172..173 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@173..174 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@174..175 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@175..204 "Quote interrupted by heading" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@204..205 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@205..206 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@206..246 " This heading ends the lazy continuation" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@246..247 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@247..248 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@248..249 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@249..285 "Quote interrupted by setext heading" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@285..286 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@286..300 "Setext heading" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@300..301 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdThematicBreakBlock { + value_token: MD_THEMATIC_BREAK_LITERAL@301..304 "---" [] [], + }, + MdNewline { + value_token: NEWLINE@304..305 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@305..306 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@306..307 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@307..333 "Quote interrupted by list" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@333..334 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBulletListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: MINUS@334..335 "-" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@335..373 " This list item ends lazy continuation" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@373..374 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + }, + MdNewline { + value_token: NEWLINE@374..375 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@375..376 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@376..409 "Quote interrupted by fenced code" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@409..410 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@410..413 "```" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@413..414 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@414..424 "code block" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@424..425 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@425..428 "```" [] [], + }, + MdNewline { + value_token: NEWLINE@428..429 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@429..430 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@430..431 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@431..467 "Quote interrupted by thematic break" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@467..468 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdThematicBreakBlock { + value_token: MD_THEMATIC_BREAK_LITERAL@468..471 "---" [] [], + }, + MdNewline { + value_token: NEWLINE@471..472 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@472..473 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@473..474 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@474..509 "Quote interrupted by indented code" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@509..510 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@510..544 "This is an indented code block" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@544..545 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + eof_token: EOF@545..545 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..545 + 0: (empty) + 1: MD_BLOCK_LIST@0..545 + 0: MD_QUOTE@0..40 + 0: R_ANGLE@0..1 ">" [] [] + 1: MD_BLOCK_LIST@1..40 + 0: MD_PARAGRAPH@1..40 + 0: MD_INLINE_ITEM_LIST@1..40 + 0: MD_TEXTUAL@1..17 + 0: MD_TEXTUAL_LITERAL@1..17 "This is a quote" [Skipped(" ")] [] + 1: MD_TEXTUAL@17..18 + 0: MD_TEXTUAL_LITERAL@17..18 "\n" [] [] + 2: MD_TEXTUAL@18..39 + 0: MD_TEXTUAL_LITERAL@18..39 "that continues lazily" [] [] + 3: MD_TEXTUAL@39..40 + 0: MD_TEXTUAL_LITERAL@39..40 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@40..41 + 0: NEWLINE@40..41 "\n" [] [] + 2: MD_QUOTE@41..108 + 0: R_ANGLE@41..42 ">" [] [] + 1: MD_BLOCK_LIST@42..108 + 0: MD_PARAGRAPH@42..108 + 0: MD_INLINE_ITEM_LIST@42..108 + 0: MD_TEXTUAL@42..56 + 0: MD_TEXTUAL_LITERAL@42..56 "Another quote" [Skipped(" ")] [] + 1: MD_TEXTUAL@56..57 + 0: MD_TEXTUAL_LITERAL@56..57 "\n" [] [] + 2: MD_TEXTUAL@57..79 + 0: MD_TEXTUAL_LITERAL@57..79 "with lazy continuation" [] [] + 3: MD_TEXTUAL@79..80 + 0: MD_TEXTUAL_LITERAL@79..80 "\n" [] [] + 4: MD_TEXTUAL@80..107 + 0: MD_TEXTUAL_LITERAL@80..107 "and explicit continuation" [Skipped(">"), Skipped(" ")] [] + 5: MD_TEXTUAL@107..108 + 0: MD_TEXTUAL_LITERAL@107..108 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@108..109 + 0: NEWLINE@108..109 "\n" [] [] + 4: MD_QUOTE@109..173 + 0: R_ANGLE@109..110 ">" [] [] + 1: MD_BLOCK_LIST@110..173 + 0: MD_PARAGRAPH@110..173 + 0: MD_INLINE_ITEM_LIST@110..173 + 0: MD_TEXTUAL@110..116 + 0: MD_TEXTUAL_LITERAL@110..116 "Multi" [Skipped(" ")] [] + 1: MD_TEXTUAL@116..117 + 0: MD_TEXTUAL_LITERAL@116..117 "-" [] [] + 2: MD_TEXTUAL@117..127 + 0: MD_TEXTUAL_LITERAL@117..127 "line quote" [] [] + 3: MD_TEXTUAL@127..128 + 0: MD_TEXTUAL_LITERAL@127..128 "\n" [] [] + 4: MD_TEXTUAL@128..150 + 0: MD_TEXTUAL_LITERAL@128..150 "with more lazy content" [] [] + 5: MD_TEXTUAL@150..151 + 0: MD_TEXTUAL_LITERAL@150..151 "\n" [] [] + 6: MD_TEXTUAL@151..172 + 0: MD_TEXTUAL_LITERAL@151..172 "and even more content" [] [] + 7: MD_TEXTUAL@172..173 + 0: MD_TEXTUAL_LITERAL@172..173 "\n" [] [] + 1: (empty) + 5: MD_NEWLINE@173..174 + 0: NEWLINE@173..174 "\n" [] [] + 6: MD_QUOTE@174..205 + 0: R_ANGLE@174..175 ">" [] [] + 1: MD_BLOCK_LIST@175..205 + 0: MD_PARAGRAPH@175..205 + 0: MD_INLINE_ITEM_LIST@175..205 + 0: MD_TEXTUAL@175..204 + 0: MD_TEXTUAL_LITERAL@175..204 "Quote interrupted by heading" [Skipped(" ")] [] + 1: MD_TEXTUAL@204..205 + 0: MD_TEXTUAL_LITERAL@204..205 "\n" [] [] + 1: (empty) + 7: MD_HEADER@205..246 + 0: MD_HASH_LIST@205..206 + 0: MD_HASH@205..206 + 0: HASH@205..206 "#" [] [] + 1: MD_PARAGRAPH@206..246 + 0: MD_INLINE_ITEM_LIST@206..246 + 0: MD_TEXTUAL@206..246 + 0: MD_TEXTUAL_LITERAL@206..246 " This heading ends the lazy continuation" [] [] + 1: (empty) + 2: MD_HASH_LIST@246..246 + 8: MD_NEWLINE@246..247 + 0: NEWLINE@246..247 "\n" [] [] + 9: MD_NEWLINE@247..248 + 0: NEWLINE@247..248 "\n" [] [] + 10: MD_QUOTE@248..301 + 0: R_ANGLE@248..249 ">" [] [] + 1: MD_BLOCK_LIST@249..301 + 0: MD_PARAGRAPH@249..301 + 0: MD_INLINE_ITEM_LIST@249..301 + 0: MD_TEXTUAL@249..285 + 0: MD_TEXTUAL_LITERAL@249..285 "Quote interrupted by setext heading" [Skipped(" ")] [] + 1: MD_TEXTUAL@285..286 + 0: MD_TEXTUAL_LITERAL@285..286 "\n" [] [] + 2: MD_TEXTUAL@286..300 + 0: MD_TEXTUAL_LITERAL@286..300 "Setext heading" [] [] + 3: MD_TEXTUAL@300..301 + 0: MD_TEXTUAL_LITERAL@300..301 "\n" [] [] + 1: (empty) + 11: MD_THEMATIC_BREAK_BLOCK@301..304 + 0: MD_THEMATIC_BREAK_LITERAL@301..304 "---" [] [] + 12: MD_NEWLINE@304..305 + 0: NEWLINE@304..305 "\n" [] [] + 13: MD_NEWLINE@305..306 + 0: NEWLINE@305..306 "\n" [] [] + 14: MD_QUOTE@306..334 + 0: R_ANGLE@306..307 ">" [] [] + 1: MD_BLOCK_LIST@307..334 + 0: MD_PARAGRAPH@307..334 + 0: MD_INLINE_ITEM_LIST@307..334 + 0: MD_TEXTUAL@307..333 + 0: MD_TEXTUAL_LITERAL@307..333 "Quote interrupted by list" [Skipped(" ")] [] + 1: MD_TEXTUAL@333..334 + 0: MD_TEXTUAL_LITERAL@333..334 "\n" [] [] + 1: (empty) + 15: MD_BULLET_LIST_ITEM@334..374 + 0: MD_BULLET_LIST@334..374 + 0: MD_BULLET@334..374 + 0: MINUS@334..335 "-" [] [] + 1: MD_BLOCK_LIST@335..374 + 0: MD_PARAGRAPH@335..374 + 0: MD_INLINE_ITEM_LIST@335..374 + 0: MD_TEXTUAL@335..373 + 0: MD_TEXTUAL_LITERAL@335..373 " This list item ends lazy continuation" [] [] + 1: MD_TEXTUAL@373..374 + 0: MD_TEXTUAL_LITERAL@373..374 "\n" [] [] + 1: (empty) + 16: MD_NEWLINE@374..375 + 0: NEWLINE@374..375 "\n" [] [] + 17: MD_QUOTE@375..410 + 0: R_ANGLE@375..376 ">" [] [] + 1: MD_BLOCK_LIST@376..410 + 0: MD_PARAGRAPH@376..410 + 0: MD_INLINE_ITEM_LIST@376..410 + 0: MD_TEXTUAL@376..409 + 0: MD_TEXTUAL_LITERAL@376..409 "Quote interrupted by fenced code" [Skipped(" ")] [] + 1: MD_TEXTUAL@409..410 + 0: MD_TEXTUAL_LITERAL@409..410 "\n" [] [] + 1: (empty) + 18: MD_FENCED_CODE_BLOCK@410..428 + 0: TRIPLE_BACKTICK@410..413 "```" [] [] + 1: MD_CODE_NAME_LIST@413..413 + 2: MD_INLINE_ITEM_LIST@413..425 + 0: MD_TEXTUAL@413..414 + 0: MD_TEXTUAL_LITERAL@413..414 "\n" [] [] + 1: MD_TEXTUAL@414..424 + 0: MD_TEXTUAL_LITERAL@414..424 "code block" [] [] + 2: MD_TEXTUAL@424..425 + 0: MD_TEXTUAL_LITERAL@424..425 "\n" [] [] + 3: TRIPLE_BACKTICK@425..428 "```" [] [] + 19: MD_NEWLINE@428..429 + 0: NEWLINE@428..429 "\n" [] [] + 20: MD_NEWLINE@429..430 + 0: NEWLINE@429..430 "\n" [] [] + 21: MD_QUOTE@430..468 + 0: R_ANGLE@430..431 ">" [] [] + 1: MD_BLOCK_LIST@431..468 + 0: MD_PARAGRAPH@431..468 + 0: MD_INLINE_ITEM_LIST@431..468 + 0: MD_TEXTUAL@431..467 + 0: MD_TEXTUAL_LITERAL@431..467 "Quote interrupted by thematic break" [Skipped(" ")] [] + 1: MD_TEXTUAL@467..468 + 0: MD_TEXTUAL_LITERAL@467..468 "\n" [] [] + 1: (empty) + 22: MD_THEMATIC_BREAK_BLOCK@468..471 + 0: MD_THEMATIC_BREAK_LITERAL@468..471 "---" [] [] + 23: MD_NEWLINE@471..472 + 0: NEWLINE@471..472 "\n" [] [] + 24: MD_NEWLINE@472..473 + 0: NEWLINE@472..473 "\n" [] [] + 25: MD_QUOTE@473..545 + 0: R_ANGLE@473..474 ">" [] [] + 1: MD_BLOCK_LIST@474..545 + 0: MD_PARAGRAPH@474..545 + 0: MD_INLINE_ITEM_LIST@474..545 + 0: MD_TEXTUAL@474..509 + 0: MD_TEXTUAL_LITERAL@474..509 "Quote interrupted by indented code" [Skipped(" ")] [] + 1: MD_TEXTUAL@509..510 + 0: MD_TEXTUAL_LITERAL@509..510 "\n" [] [] + 2: MD_TEXTUAL@510..544 + 0: MD_TEXTUAL_LITERAL@510..544 "This is an indented code block" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 3: MD_TEXTUAL@544..545 + 0: MD_TEXTUAL_LITERAL@544..545 "\n" [] [] + 1: (empty) + 2: EOF@545..545 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition.md new file mode 100644 index 000000000000..49f729678fdb --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition.md @@ -0,0 +1,9 @@ +[example]: https://example.com + +[foo]: /url + + [one-space]: /url + + [two-spaces]: /url + + [three-spaces]: /url diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition.md.snap new file mode 100644 index 000000000000..2cc6a441b2a8 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition.md.snap @@ -0,0 +1,306 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +[example]: https://example.com + +[foo]: /url + + [one-space]: /url + + [two-spaces]: /url + + [three-spaces]: /url + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@0..1 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..8 "example" [] [], + }, + ], + }, + r_brack_token: R_BRACK@8..9 "]" [] [], + colon_token: COLON@9..10 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..11 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@11..30 "https://example.com" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@30..31 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@31..32 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@32..33 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@33..36 "foo" [] [], + }, + ], + }, + r_brack_token: R_BRACK@36..37 "]" [] [], + colon_token: COLON@37..38 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@38..39 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@39..43 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@43..44 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@44..45 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@45..47 "[" [Skipped(" ")] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@47..50 "one" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@50..51 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@51..56 "space" [] [], + }, + ], + }, + r_brack_token: R_BRACK@56..57 "]" [] [], + colon_token: COLON@57..58 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..59 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@59..63 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@63..64 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@64..65 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@65..68 "[" [Skipped(" "), Skipped(" ")] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..71 "two" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@71..72 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@72..78 "spaces" [] [], + }, + ], + }, + r_brack_token: R_BRACK@78..79 "]" [] [], + colon_token: COLON@79..80 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..81 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@81..85 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@85..86 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@86..87 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@87..91 "[" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@91..96 "three" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@96..97 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@97..103 "spaces" [] [], + }, + ], + }, + r_brack_token: R_BRACK@103..104 "]" [] [], + colon_token: COLON@104..105 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@105..106 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@106..110 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@110..111 "\n" [] [], + }, + ], + eof_token: EOF@111..111 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..111 + 0: (empty) + 1: MD_BLOCK_LIST@0..111 + 0: MD_LINK_REFERENCE_DEFINITION@0..30 + 0: L_BRACK@0..1 "[" [] [] + 1: MD_LINK_LABEL@1..8 + 0: MD_INLINE_ITEM_LIST@1..8 + 0: MD_TEXTUAL@1..8 + 0: MD_TEXTUAL_LITERAL@1..8 "example" [] [] + 2: R_BRACK@8..9 "]" [] [] + 3: COLON@9..10 ":" [] [] + 4: MD_LINK_DESTINATION@10..30 + 0: MD_INLINE_ITEM_LIST@10..30 + 0: MD_TEXTUAL@10..11 + 0: MD_TEXTUAL_LITERAL@10..11 " " [] [] + 1: MD_TEXTUAL@11..30 + 0: MD_TEXTUAL_LITERAL@11..30 "https://example.com" [] [] + 5: (empty) + 1: MD_NEWLINE@30..31 + 0: NEWLINE@30..31 "\n" [] [] + 2: MD_NEWLINE@31..32 + 0: NEWLINE@31..32 "\n" [] [] + 3: MD_LINK_REFERENCE_DEFINITION@32..43 + 0: L_BRACK@32..33 "[" [] [] + 1: MD_LINK_LABEL@33..36 + 0: MD_INLINE_ITEM_LIST@33..36 + 0: MD_TEXTUAL@33..36 + 0: MD_TEXTUAL_LITERAL@33..36 "foo" [] [] + 2: R_BRACK@36..37 "]" [] [] + 3: COLON@37..38 ":" [] [] + 4: MD_LINK_DESTINATION@38..43 + 0: MD_INLINE_ITEM_LIST@38..43 + 0: MD_TEXTUAL@38..39 + 0: MD_TEXTUAL_LITERAL@38..39 " " [] [] + 1: MD_TEXTUAL@39..43 + 0: MD_TEXTUAL_LITERAL@39..43 "/url" [] [] + 5: (empty) + 4: MD_NEWLINE@43..44 + 0: NEWLINE@43..44 "\n" [] [] + 5: MD_NEWLINE@44..45 + 0: NEWLINE@44..45 "\n" [] [] + 6: MD_LINK_REFERENCE_DEFINITION@45..63 + 0: L_BRACK@45..47 "[" [Skipped(" ")] [] + 1: MD_LINK_LABEL@47..56 + 0: MD_INLINE_ITEM_LIST@47..56 + 0: MD_TEXTUAL@47..50 + 0: MD_TEXTUAL_LITERAL@47..50 "one" [] [] + 1: MD_TEXTUAL@50..51 + 0: MD_TEXTUAL_LITERAL@50..51 "-" [] [] + 2: MD_TEXTUAL@51..56 + 0: MD_TEXTUAL_LITERAL@51..56 "space" [] [] + 2: R_BRACK@56..57 "]" [] [] + 3: COLON@57..58 ":" [] [] + 4: MD_LINK_DESTINATION@58..63 + 0: MD_INLINE_ITEM_LIST@58..63 + 0: MD_TEXTUAL@58..59 + 0: MD_TEXTUAL_LITERAL@58..59 " " [] [] + 1: MD_TEXTUAL@59..63 + 0: MD_TEXTUAL_LITERAL@59..63 "/url" [] [] + 5: (empty) + 7: MD_NEWLINE@63..64 + 0: NEWLINE@63..64 "\n" [] [] + 8: MD_NEWLINE@64..65 + 0: NEWLINE@64..65 "\n" [] [] + 9: MD_LINK_REFERENCE_DEFINITION@65..85 + 0: L_BRACK@65..68 "[" [Skipped(" "), Skipped(" ")] [] + 1: MD_LINK_LABEL@68..78 + 0: MD_INLINE_ITEM_LIST@68..78 + 0: MD_TEXTUAL@68..71 + 0: MD_TEXTUAL_LITERAL@68..71 "two" [] [] + 1: MD_TEXTUAL@71..72 + 0: MD_TEXTUAL_LITERAL@71..72 "-" [] [] + 2: MD_TEXTUAL@72..78 + 0: MD_TEXTUAL_LITERAL@72..78 "spaces" [] [] + 2: R_BRACK@78..79 "]" [] [] + 3: COLON@79..80 ":" [] [] + 4: MD_LINK_DESTINATION@80..85 + 0: MD_INLINE_ITEM_LIST@80..85 + 0: MD_TEXTUAL@80..81 + 0: MD_TEXTUAL_LITERAL@80..81 " " [] [] + 1: MD_TEXTUAL@81..85 + 0: MD_TEXTUAL_LITERAL@81..85 "/url" [] [] + 5: (empty) + 10: MD_NEWLINE@85..86 + 0: NEWLINE@85..86 "\n" [] [] + 11: MD_NEWLINE@86..87 + 0: NEWLINE@86..87 "\n" [] [] + 12: MD_LINK_REFERENCE_DEFINITION@87..110 + 0: L_BRACK@87..91 "[" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_LINK_LABEL@91..103 + 0: MD_INLINE_ITEM_LIST@91..103 + 0: MD_TEXTUAL@91..96 + 0: MD_TEXTUAL_LITERAL@91..96 "three" [] [] + 1: MD_TEXTUAL@96..97 + 0: MD_TEXTUAL_LITERAL@96..97 "-" [] [] + 2: MD_TEXTUAL@97..103 + 0: MD_TEXTUAL_LITERAL@97..103 "spaces" [] [] + 2: R_BRACK@103..104 "]" [] [] + 3: COLON@104..105 ":" [] [] + 4: MD_LINK_DESTINATION@105..110 + 0: MD_INLINE_ITEM_LIST@105..110 + 0: MD_TEXTUAL@105..106 + 0: MD_TEXTUAL_LITERAL@105..106 " " [] [] + 1: MD_TEXTUAL@106..110 + 0: MD_TEXTUAL_LITERAL@106..110 "/url" [] [] + 5: (empty) + 13: MD_NEWLINE@110..111 + 0: NEWLINE@110..111 "\n" [] [] + 2: EOF@111..111 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_edge_cases.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_edge_cases.md new file mode 100644 index 000000000000..b8c72626c468 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_edge_cases.md @@ -0,0 +1,32 @@ +Some text with trailing spaces + + [after-trailing]: /url + +Another paragraph + + [normal-indent]: /url + +[lambda]: /url + +[nihongo]: /url "Japanese label" + +[title-next-line]: /url + "title on next line" + +[single-quote-next]: /url + 'single quoted' + +[paren-next]: /url + (parenthesized) + +[balanced-parens]: http://example.com/path(with)parens + +[nested-parens]: http://example.com/a(b(c)d)e + +[escaped\]bracket]: /url + +[trailing-spaces]: /url + +[invalid-trailing]: /url invalid + +[angle-trailing]: invalid diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_edge_cases.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_edge_cases.md.snap new file mode 100644 index 000000000000..c1d0f90e6c95 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_edge_cases.md.snap @@ -0,0 +1,1052 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Some text with trailing spaces + + [after-trailing]: /url + +Another paragraph + + [normal-indent]: /url + +[lambda]: /url + +[nihongo]: /url "Japanese label" + +[title-next-line]: /url + "title on next line" + +[single-quote-next]: /url + 'single quoted' + +[paren-next]: /url + (parenthesized) + +[balanced-parens]: http://example.com/path(with)parens + +[nested-parens]: http://example.com/a(b(c)d)e + +[escaped\]bracket]: /url + +[trailing-spaces]: /url + +[invalid-trailing]: /url invalid + +[angle-trailing]: invalid + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..30 "Some text with trailing spaces" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@31..32 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@32..35 "[" [Skipped(" "), Skipped(" ")] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@35..40 "after" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..41 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@41..49 "trailing" [] [], + }, + ], + }, + r_brack_token: R_BRACK@49..50 "]" [] [], + colon_token: COLON@50..51 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@51..52 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@52..56 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@56..57 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@57..58 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..75 "Another paragraph" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@75..76 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@76..77 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@77..80 "[" [Skipped(" "), Skipped(" ")] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..86 "normal" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@86..87 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@87..93 "indent" [] [], + }, + ], + }, + r_brack_token: R_BRACK@93..94 "]" [] [], + colon_token: COLON@94..95 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@95..96 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@96..100 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@100..101 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@101..102 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@102..103 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@103..109 "lambda" [] [], + }, + ], + }, + r_brack_token: R_BRACK@109..110 "]" [] [], + colon_token: COLON@110..111 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@111..112 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@112..116 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@116..117 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@117..118 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@118..119 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@119..126 "nihongo" [] [], + }, + ], + }, + r_brack_token: R_BRACK@126..127 "]" [] [], + colon_token: COLON@127..128 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@128..129 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@129..133 "/url" [] [], + }, + ], + }, + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@133..134 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@134..150 "\"Japanese label\"" [] [], + }, + ], + }, + }, + MdNewline { + value_token: NEWLINE@150..151 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@151..152 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@152..153 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@153..158 "title" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@158..159 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@159..163 "next" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@163..164 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@164..168 "line" [] [], + }, + ], + }, + r_brack_token: R_BRACK@168..169 "]" [] [], + colon_token: COLON@169..170 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@170..171 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@171..175 "/url" [] [], + }, + ], + }, + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@175..176 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@176..178 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@178..198 "\"title on next line\"" [] [], + }, + ], + }, + }, + MdNewline { + value_token: NEWLINE@198..199 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@199..200 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@200..201 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@201..207 "single" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@207..208 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@208..213 "quote" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@213..214 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@214..218 "next" [] [], + }, + ], + }, + r_brack_token: R_BRACK@218..219 "]" [] [], + colon_token: COLON@219..220 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@220..221 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@221..225 "/url" [] [], + }, + ], + }, + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@225..226 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@226..228 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@228..243 "'single quoted'" [] [], + }, + ], + }, + }, + MdNewline { + value_token: NEWLINE@243..244 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@244..245 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@245..246 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@246..251 "paren" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@251..252 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@252..256 "next" [] [], + }, + ], + }, + r_brack_token: R_BRACK@256..257 "]" [] [], + colon_token: COLON@257..258 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@258..259 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@259..263 "/url" [] [], + }, + ], + }, + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@263..264 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@264..266 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@266..267 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@267..280 "parenthesized" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@280..281 ")" [] [], + }, + ], + }, + }, + MdNewline { + value_token: NEWLINE@281..282 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@282..283 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@283..284 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@284..292 "balanced" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@292..293 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@293..299 "parens" [] [], + }, + ], + }, + r_brack_token: R_BRACK@299..300 "]" [] [], + colon_token: COLON@300..301 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@301..302 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@302..325 "http://example.com/path" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@325..326 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@326..330 "with" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@330..331 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@331..337 "parens" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@337..338 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@338..339 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@339..340 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@340..346 "nested" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@346..347 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@347..353 "parens" [] [], + }, + ], + }, + r_brack_token: R_BRACK@353..354 "]" [] [], + colon_token: COLON@354..355 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@355..356 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@356..376 "http://example.com/a" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@376..377 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@377..378 "b" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@378..379 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@379..380 "c" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@380..381 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@381..382 "d" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@382..383 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@383..384 "e" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@384..385 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@385..386 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@386..387 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@387..394 "escaped" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@394..396 "\\]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@396..403 "bracket" [] [], + }, + ], + }, + r_brack_token: R_BRACK@403..404 "]" [] [], + colon_token: COLON@404..405 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@405..406 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@406..410 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@410..411 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@411..412 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@412..413 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@413..421 "trailing" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@421..422 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@422..428 "spaces" [] [], + }, + ], + }, + r_brack_token: R_BRACK@428..429 "]" [] [], + colon_token: COLON@429..430 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@430..431 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@431..435 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@435..438 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@438..439 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@439..440 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@440..441 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@441..448 "invalid" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@448..449 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@449..457 "trailing" [] [], + }, + ], + }, + r_brack_token: R_BRACK@457..458 "]" [] [], + colon_token: COLON@458..459 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@459..460 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@460..464 "/url" [] [], + }, + ], + }, + title: missing (optional), + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@464..465 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@465..472 "invalid" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@472..473 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@473..474 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@474..475 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@475..480 "angle" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@480..481 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@481..489 "trailing" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@489..490 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@490..491 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@491..492 " " [] [], + }, + MdInlineHtml { + value: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@492..493 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@493..497 "/url" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@497..498 ">" [] [], + }, + ], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@498..506 " invalid" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@506..507 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@507..507 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..507 + 0: (empty) + 1: MD_BLOCK_LIST@0..507 + 0: MD_PARAGRAPH@0..31 + 0: MD_INLINE_ITEM_LIST@0..31 + 0: MD_TEXTUAL@0..30 + 0: MD_TEXTUAL_LITERAL@0..30 "Some text with trailing spaces" [] [] + 1: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@31..32 + 0: NEWLINE@31..32 "\n" [] [] + 2: MD_LINK_REFERENCE_DEFINITION@32..56 + 0: L_BRACK@32..35 "[" [Skipped(" "), Skipped(" ")] [] + 1: MD_LINK_LABEL@35..49 + 0: MD_INLINE_ITEM_LIST@35..49 + 0: MD_TEXTUAL@35..40 + 0: MD_TEXTUAL_LITERAL@35..40 "after" [] [] + 1: MD_TEXTUAL@40..41 + 0: MD_TEXTUAL_LITERAL@40..41 "-" [] [] + 2: MD_TEXTUAL@41..49 + 0: MD_TEXTUAL_LITERAL@41..49 "trailing" [] [] + 2: R_BRACK@49..50 "]" [] [] + 3: COLON@50..51 ":" [] [] + 4: MD_LINK_DESTINATION@51..56 + 0: MD_INLINE_ITEM_LIST@51..56 + 0: MD_TEXTUAL@51..52 + 0: MD_TEXTUAL_LITERAL@51..52 " " [] [] + 1: MD_TEXTUAL@52..56 + 0: MD_TEXTUAL_LITERAL@52..56 "/url" [] [] + 5: (empty) + 3: MD_NEWLINE@56..57 + 0: NEWLINE@56..57 "\n" [] [] + 4: MD_NEWLINE@57..58 + 0: NEWLINE@57..58 "\n" [] [] + 5: MD_PARAGRAPH@58..76 + 0: MD_INLINE_ITEM_LIST@58..76 + 0: MD_TEXTUAL@58..75 + 0: MD_TEXTUAL_LITERAL@58..75 "Another paragraph" [] [] + 1: MD_TEXTUAL@75..76 + 0: MD_TEXTUAL_LITERAL@75..76 "\n" [] [] + 1: (empty) + 6: MD_NEWLINE@76..77 + 0: NEWLINE@76..77 "\n" [] [] + 7: MD_LINK_REFERENCE_DEFINITION@77..100 + 0: L_BRACK@77..80 "[" [Skipped(" "), Skipped(" ")] [] + 1: MD_LINK_LABEL@80..93 + 0: MD_INLINE_ITEM_LIST@80..93 + 0: MD_TEXTUAL@80..86 + 0: MD_TEXTUAL_LITERAL@80..86 "normal" [] [] + 1: MD_TEXTUAL@86..87 + 0: MD_TEXTUAL_LITERAL@86..87 "-" [] [] + 2: MD_TEXTUAL@87..93 + 0: MD_TEXTUAL_LITERAL@87..93 "indent" [] [] + 2: R_BRACK@93..94 "]" [] [] + 3: COLON@94..95 ":" [] [] + 4: MD_LINK_DESTINATION@95..100 + 0: MD_INLINE_ITEM_LIST@95..100 + 0: MD_TEXTUAL@95..96 + 0: MD_TEXTUAL_LITERAL@95..96 " " [] [] + 1: MD_TEXTUAL@96..100 + 0: MD_TEXTUAL_LITERAL@96..100 "/url" [] [] + 5: (empty) + 8: MD_NEWLINE@100..101 + 0: NEWLINE@100..101 "\n" [] [] + 9: MD_NEWLINE@101..102 + 0: NEWLINE@101..102 "\n" [] [] + 10: MD_LINK_REFERENCE_DEFINITION@102..116 + 0: L_BRACK@102..103 "[" [] [] + 1: MD_LINK_LABEL@103..109 + 0: MD_INLINE_ITEM_LIST@103..109 + 0: MD_TEXTUAL@103..109 + 0: MD_TEXTUAL_LITERAL@103..109 "lambda" [] [] + 2: R_BRACK@109..110 "]" [] [] + 3: COLON@110..111 ":" [] [] + 4: MD_LINK_DESTINATION@111..116 + 0: MD_INLINE_ITEM_LIST@111..116 + 0: MD_TEXTUAL@111..112 + 0: MD_TEXTUAL_LITERAL@111..112 " " [] [] + 1: MD_TEXTUAL@112..116 + 0: MD_TEXTUAL_LITERAL@112..116 "/url" [] [] + 5: (empty) + 11: MD_NEWLINE@116..117 + 0: NEWLINE@116..117 "\n" [] [] + 12: MD_NEWLINE@117..118 + 0: NEWLINE@117..118 "\n" [] [] + 13: MD_LINK_REFERENCE_DEFINITION@118..150 + 0: L_BRACK@118..119 "[" [] [] + 1: MD_LINK_LABEL@119..126 + 0: MD_INLINE_ITEM_LIST@119..126 + 0: MD_TEXTUAL@119..126 + 0: MD_TEXTUAL_LITERAL@119..126 "nihongo" [] [] + 2: R_BRACK@126..127 "]" [] [] + 3: COLON@127..128 ":" [] [] + 4: MD_LINK_DESTINATION@128..133 + 0: MD_INLINE_ITEM_LIST@128..133 + 0: MD_TEXTUAL@128..129 + 0: MD_TEXTUAL_LITERAL@128..129 " " [] [] + 1: MD_TEXTUAL@129..133 + 0: MD_TEXTUAL_LITERAL@129..133 "/url" [] [] + 5: MD_LINK_TITLE@133..150 + 0: MD_INLINE_ITEM_LIST@133..150 + 0: MD_TEXTUAL@133..134 + 0: MD_TEXTUAL_LITERAL@133..134 " " [] [] + 1: MD_TEXTUAL@134..150 + 0: MD_TEXTUAL_LITERAL@134..150 "\"Japanese label\"" [] [] + 14: MD_NEWLINE@150..151 + 0: NEWLINE@150..151 "\n" [] [] + 15: MD_NEWLINE@151..152 + 0: NEWLINE@151..152 "\n" [] [] + 16: MD_LINK_REFERENCE_DEFINITION@152..198 + 0: L_BRACK@152..153 "[" [] [] + 1: MD_LINK_LABEL@153..168 + 0: MD_INLINE_ITEM_LIST@153..168 + 0: MD_TEXTUAL@153..158 + 0: MD_TEXTUAL_LITERAL@153..158 "title" [] [] + 1: MD_TEXTUAL@158..159 + 0: MD_TEXTUAL_LITERAL@158..159 "-" [] [] + 2: MD_TEXTUAL@159..163 + 0: MD_TEXTUAL_LITERAL@159..163 "next" [] [] + 3: MD_TEXTUAL@163..164 + 0: MD_TEXTUAL_LITERAL@163..164 "-" [] [] + 4: MD_TEXTUAL@164..168 + 0: MD_TEXTUAL_LITERAL@164..168 "line" [] [] + 2: R_BRACK@168..169 "]" [] [] + 3: COLON@169..170 ":" [] [] + 4: MD_LINK_DESTINATION@170..175 + 0: MD_INLINE_ITEM_LIST@170..175 + 0: MD_TEXTUAL@170..171 + 0: MD_TEXTUAL_LITERAL@170..171 " " [] [] + 1: MD_TEXTUAL@171..175 + 0: MD_TEXTUAL_LITERAL@171..175 "/url" [] [] + 5: MD_LINK_TITLE@175..198 + 0: MD_INLINE_ITEM_LIST@175..198 + 0: MD_TEXTUAL@175..176 + 0: MD_TEXTUAL_LITERAL@175..176 "\n" [] [] + 1: MD_TEXTUAL@176..178 + 0: MD_TEXTUAL_LITERAL@176..178 " " [] [] + 2: MD_TEXTUAL@178..198 + 0: MD_TEXTUAL_LITERAL@178..198 "\"title on next line\"" [] [] + 17: MD_NEWLINE@198..199 + 0: NEWLINE@198..199 "\n" [] [] + 18: MD_NEWLINE@199..200 + 0: NEWLINE@199..200 "\n" [] [] + 19: MD_LINK_REFERENCE_DEFINITION@200..243 + 0: L_BRACK@200..201 "[" [] [] + 1: MD_LINK_LABEL@201..218 + 0: MD_INLINE_ITEM_LIST@201..218 + 0: MD_TEXTUAL@201..207 + 0: MD_TEXTUAL_LITERAL@201..207 "single" [] [] + 1: MD_TEXTUAL@207..208 + 0: MD_TEXTUAL_LITERAL@207..208 "-" [] [] + 2: MD_TEXTUAL@208..213 + 0: MD_TEXTUAL_LITERAL@208..213 "quote" [] [] + 3: MD_TEXTUAL@213..214 + 0: MD_TEXTUAL_LITERAL@213..214 "-" [] [] + 4: MD_TEXTUAL@214..218 + 0: MD_TEXTUAL_LITERAL@214..218 "next" [] [] + 2: R_BRACK@218..219 "]" [] [] + 3: COLON@219..220 ":" [] [] + 4: MD_LINK_DESTINATION@220..225 + 0: MD_INLINE_ITEM_LIST@220..225 + 0: MD_TEXTUAL@220..221 + 0: MD_TEXTUAL_LITERAL@220..221 " " [] [] + 1: MD_TEXTUAL@221..225 + 0: MD_TEXTUAL_LITERAL@221..225 "/url" [] [] + 5: MD_LINK_TITLE@225..243 + 0: MD_INLINE_ITEM_LIST@225..243 + 0: MD_TEXTUAL@225..226 + 0: MD_TEXTUAL_LITERAL@225..226 "\n" [] [] + 1: MD_TEXTUAL@226..228 + 0: MD_TEXTUAL_LITERAL@226..228 " " [] [] + 2: MD_TEXTUAL@228..243 + 0: MD_TEXTUAL_LITERAL@228..243 "'single quoted'" [] [] + 20: MD_NEWLINE@243..244 + 0: NEWLINE@243..244 "\n" [] [] + 21: MD_NEWLINE@244..245 + 0: NEWLINE@244..245 "\n" [] [] + 22: MD_LINK_REFERENCE_DEFINITION@245..281 + 0: L_BRACK@245..246 "[" [] [] + 1: MD_LINK_LABEL@246..256 + 0: MD_INLINE_ITEM_LIST@246..256 + 0: MD_TEXTUAL@246..251 + 0: MD_TEXTUAL_LITERAL@246..251 "paren" [] [] + 1: MD_TEXTUAL@251..252 + 0: MD_TEXTUAL_LITERAL@251..252 "-" [] [] + 2: MD_TEXTUAL@252..256 + 0: MD_TEXTUAL_LITERAL@252..256 "next" [] [] + 2: R_BRACK@256..257 "]" [] [] + 3: COLON@257..258 ":" [] [] + 4: MD_LINK_DESTINATION@258..263 + 0: MD_INLINE_ITEM_LIST@258..263 + 0: MD_TEXTUAL@258..259 + 0: MD_TEXTUAL_LITERAL@258..259 " " [] [] + 1: MD_TEXTUAL@259..263 + 0: MD_TEXTUAL_LITERAL@259..263 "/url" [] [] + 5: MD_LINK_TITLE@263..281 + 0: MD_INLINE_ITEM_LIST@263..281 + 0: MD_TEXTUAL@263..264 + 0: MD_TEXTUAL_LITERAL@263..264 "\n" [] [] + 1: MD_TEXTUAL@264..266 + 0: MD_TEXTUAL_LITERAL@264..266 " " [] [] + 2: MD_TEXTUAL@266..267 + 0: MD_TEXTUAL_LITERAL@266..267 "(" [] [] + 3: MD_TEXTUAL@267..280 + 0: MD_TEXTUAL_LITERAL@267..280 "parenthesized" [] [] + 4: MD_TEXTUAL@280..281 + 0: MD_TEXTUAL_LITERAL@280..281 ")" [] [] + 23: MD_NEWLINE@281..282 + 0: NEWLINE@281..282 "\n" [] [] + 24: MD_NEWLINE@282..283 + 0: NEWLINE@282..283 "\n" [] [] + 25: MD_LINK_REFERENCE_DEFINITION@283..337 + 0: L_BRACK@283..284 "[" [] [] + 1: MD_LINK_LABEL@284..299 + 0: MD_INLINE_ITEM_LIST@284..299 + 0: MD_TEXTUAL@284..292 + 0: MD_TEXTUAL_LITERAL@284..292 "balanced" [] [] + 1: MD_TEXTUAL@292..293 + 0: MD_TEXTUAL_LITERAL@292..293 "-" [] [] + 2: MD_TEXTUAL@293..299 + 0: MD_TEXTUAL_LITERAL@293..299 "parens" [] [] + 2: R_BRACK@299..300 "]" [] [] + 3: COLON@300..301 ":" [] [] + 4: MD_LINK_DESTINATION@301..337 + 0: MD_INLINE_ITEM_LIST@301..337 + 0: MD_TEXTUAL@301..302 + 0: MD_TEXTUAL_LITERAL@301..302 " " [] [] + 1: MD_TEXTUAL@302..325 + 0: MD_TEXTUAL_LITERAL@302..325 "http://example.com/path" [] [] + 2: MD_TEXTUAL@325..326 + 0: MD_TEXTUAL_LITERAL@325..326 "(" [] [] + 3: MD_TEXTUAL@326..330 + 0: MD_TEXTUAL_LITERAL@326..330 "with" [] [] + 4: MD_TEXTUAL@330..331 + 0: MD_TEXTUAL_LITERAL@330..331 ")" [] [] + 5: MD_TEXTUAL@331..337 + 0: MD_TEXTUAL_LITERAL@331..337 "parens" [] [] + 5: (empty) + 26: MD_NEWLINE@337..338 + 0: NEWLINE@337..338 "\n" [] [] + 27: MD_NEWLINE@338..339 + 0: NEWLINE@338..339 "\n" [] [] + 28: MD_LINK_REFERENCE_DEFINITION@339..384 + 0: L_BRACK@339..340 "[" [] [] + 1: MD_LINK_LABEL@340..353 + 0: MD_INLINE_ITEM_LIST@340..353 + 0: MD_TEXTUAL@340..346 + 0: MD_TEXTUAL_LITERAL@340..346 "nested" [] [] + 1: MD_TEXTUAL@346..347 + 0: MD_TEXTUAL_LITERAL@346..347 "-" [] [] + 2: MD_TEXTUAL@347..353 + 0: MD_TEXTUAL_LITERAL@347..353 "parens" [] [] + 2: R_BRACK@353..354 "]" [] [] + 3: COLON@354..355 ":" [] [] + 4: MD_LINK_DESTINATION@355..384 + 0: MD_INLINE_ITEM_LIST@355..384 + 0: MD_TEXTUAL@355..356 + 0: MD_TEXTUAL_LITERAL@355..356 " " [] [] + 1: MD_TEXTUAL@356..376 + 0: MD_TEXTUAL_LITERAL@356..376 "http://example.com/a" [] [] + 2: MD_TEXTUAL@376..377 + 0: MD_TEXTUAL_LITERAL@376..377 "(" [] [] + 3: MD_TEXTUAL@377..378 + 0: MD_TEXTUAL_LITERAL@377..378 "b" [] [] + 4: MD_TEXTUAL@378..379 + 0: MD_TEXTUAL_LITERAL@378..379 "(" [] [] + 5: MD_TEXTUAL@379..380 + 0: MD_TEXTUAL_LITERAL@379..380 "c" [] [] + 6: MD_TEXTUAL@380..381 + 0: MD_TEXTUAL_LITERAL@380..381 ")" [] [] + 7: MD_TEXTUAL@381..382 + 0: MD_TEXTUAL_LITERAL@381..382 "d" [] [] + 8: MD_TEXTUAL@382..383 + 0: MD_TEXTUAL_LITERAL@382..383 ")" [] [] + 9: MD_TEXTUAL@383..384 + 0: MD_TEXTUAL_LITERAL@383..384 "e" [] [] + 5: (empty) + 29: MD_NEWLINE@384..385 + 0: NEWLINE@384..385 "\n" [] [] + 30: MD_NEWLINE@385..386 + 0: NEWLINE@385..386 "\n" [] [] + 31: MD_LINK_REFERENCE_DEFINITION@386..410 + 0: L_BRACK@386..387 "[" [] [] + 1: MD_LINK_LABEL@387..403 + 0: MD_INLINE_ITEM_LIST@387..403 + 0: MD_TEXTUAL@387..394 + 0: MD_TEXTUAL_LITERAL@387..394 "escaped" [] [] + 1: MD_TEXTUAL@394..396 + 0: MD_TEXTUAL_LITERAL@394..396 "\\]" [] [] + 2: MD_TEXTUAL@396..403 + 0: MD_TEXTUAL_LITERAL@396..403 "bracket" [] [] + 2: R_BRACK@403..404 "]" [] [] + 3: COLON@404..405 ":" [] [] + 4: MD_LINK_DESTINATION@405..410 + 0: MD_INLINE_ITEM_LIST@405..410 + 0: MD_TEXTUAL@405..406 + 0: MD_TEXTUAL_LITERAL@405..406 " " [] [] + 1: MD_TEXTUAL@406..410 + 0: MD_TEXTUAL_LITERAL@406..410 "/url" [] [] + 5: (empty) + 32: MD_NEWLINE@410..411 + 0: NEWLINE@410..411 "\n" [] [] + 33: MD_NEWLINE@411..412 + 0: NEWLINE@411..412 "\n" [] [] + 34: MD_LINK_REFERENCE_DEFINITION@412..435 + 0: L_BRACK@412..413 "[" [] [] + 1: MD_LINK_LABEL@413..428 + 0: MD_INLINE_ITEM_LIST@413..428 + 0: MD_TEXTUAL@413..421 + 0: MD_TEXTUAL_LITERAL@413..421 "trailing" [] [] + 1: MD_TEXTUAL@421..422 + 0: MD_TEXTUAL_LITERAL@421..422 "-" [] [] + 2: MD_TEXTUAL@422..428 + 0: MD_TEXTUAL_LITERAL@422..428 "spaces" [] [] + 2: R_BRACK@428..429 "]" [] [] + 3: COLON@429..430 ":" [] [] + 4: MD_LINK_DESTINATION@430..435 + 0: MD_INLINE_ITEM_LIST@430..435 + 0: MD_TEXTUAL@430..431 + 0: MD_TEXTUAL_LITERAL@430..431 " " [] [] + 1: MD_TEXTUAL@431..435 + 0: MD_TEXTUAL_LITERAL@431..435 "/url" [] [] + 5: (empty) + 35: MD_PARAGRAPH@435..439 + 0: MD_INLINE_ITEM_LIST@435..439 + 0: MD_TEXTUAL@435..438 + 0: MD_TEXTUAL_LITERAL@435..438 " " [] [] + 1: MD_TEXTUAL@438..439 + 0: MD_TEXTUAL_LITERAL@438..439 "\n" [] [] + 1: (empty) + 36: MD_NEWLINE@439..440 + 0: NEWLINE@439..440 "\n" [] [] + 37: MD_LINK_REFERENCE_DEFINITION@440..464 + 0: L_BRACK@440..441 "[" [] [] + 1: MD_LINK_LABEL@441..457 + 0: MD_INLINE_ITEM_LIST@441..457 + 0: MD_TEXTUAL@441..448 + 0: MD_TEXTUAL_LITERAL@441..448 "invalid" [] [] + 1: MD_TEXTUAL@448..449 + 0: MD_TEXTUAL_LITERAL@448..449 "-" [] [] + 2: MD_TEXTUAL@449..457 + 0: MD_TEXTUAL_LITERAL@449..457 "trailing" [] [] + 2: R_BRACK@457..458 "]" [] [] + 3: COLON@458..459 ":" [] [] + 4: MD_LINK_DESTINATION@459..464 + 0: MD_INLINE_ITEM_LIST@459..464 + 0: MD_TEXTUAL@459..460 + 0: MD_TEXTUAL_LITERAL@459..460 " " [] [] + 1: MD_TEXTUAL@460..464 + 0: MD_TEXTUAL_LITERAL@460..464 "/url" [] [] + 5: (empty) + 38: MD_PARAGRAPH@464..473 + 0: MD_INLINE_ITEM_LIST@464..473 + 0: MD_TEXTUAL@464..465 + 0: MD_TEXTUAL_LITERAL@464..465 " " [] [] + 1: MD_TEXTUAL@465..472 + 0: MD_TEXTUAL_LITERAL@465..472 "invalid" [] [] + 2: MD_TEXTUAL@472..473 + 0: MD_TEXTUAL_LITERAL@472..473 "\n" [] [] + 1: (empty) + 39: MD_NEWLINE@473..474 + 0: NEWLINE@473..474 "\n" [] [] + 40: MD_PARAGRAPH@474..507 + 0: MD_INLINE_ITEM_LIST@474..507 + 0: MD_TEXTUAL@474..475 + 0: MD_TEXTUAL_LITERAL@474..475 "[" [] [] + 1: MD_TEXTUAL@475..480 + 0: MD_TEXTUAL_LITERAL@475..480 "angle" [] [] + 2: MD_TEXTUAL@480..481 + 0: MD_TEXTUAL_LITERAL@480..481 "-" [] [] + 3: MD_TEXTUAL@481..489 + 0: MD_TEXTUAL_LITERAL@481..489 "trailing" [] [] + 4: MD_TEXTUAL@489..490 + 0: MD_TEXTUAL_LITERAL@489..490 "]" [] [] + 5: MD_TEXTUAL@490..491 + 0: MD_TEXTUAL_LITERAL@490..491 ":" [] [] + 6: MD_TEXTUAL@491..492 + 0: MD_TEXTUAL_LITERAL@491..492 " " [] [] + 7: MD_INLINE_HTML@492..498 + 0: MD_INLINE_ITEM_LIST@492..498 + 0: MD_TEXTUAL@492..493 + 0: MD_TEXTUAL_LITERAL@492..493 "<" [] [] + 1: MD_TEXTUAL@493..497 + 0: MD_TEXTUAL_LITERAL@493..497 "/url" [] [] + 2: MD_TEXTUAL@497..498 + 0: MD_TEXTUAL_LITERAL@497..498 ">" [] [] + 8: MD_TEXTUAL@498..506 + 0: MD_TEXTUAL_LITERAL@498..506 " invalid" [] [] + 9: MD_TEXTUAL@506..507 + 0: MD_TEXTUAL_LITERAL@506..507 "\n" [] [] + 1: (empty) + 2: EOF@507..507 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_invalid.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_invalid.md new file mode 100644 index 000000000000..e62c67e57955 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/link_definition_invalid.md @@ -0,0 +1,14 @@ +These should NOT be parsed as link reference definitions. +They should fall back to paragraph parsing. + +Unterminated angle bracket destination: +[unterminated-angle]: Outer quote +>> Nested quote +>>> Deeply nested + +> Back to outer diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/nested_quote.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/nested_quote.md.snap new file mode 100644 index 000000000000..02e336c24db9 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/nested_quote.md.snap @@ -0,0 +1,146 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +> Outer quote +>> Nested quote +>>> Deeply nested + +> Back to outer + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdQuote { + marker_token: R_ANGLE@0..1 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..13 "Outer quote" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..14 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdQuote { + marker_token: R_ANGLE@14..16 ">" [Skipped(">")] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..29 "Nested quote" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..30 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdQuote { + marker_token: R_ANGLE@30..33 ">" [Skipped(">"), Skipped(">")] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@33..47 "Deeply nested" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@47..48 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + }, + ], + }, + MdNewline { + value_token: NEWLINE@48..49 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@49..50 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@50..64 "Back to outer" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@64..65 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + eof_token: EOF@65..65 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..65 + 0: (empty) + 1: MD_BLOCK_LIST@0..65 + 0: MD_QUOTE@0..48 + 0: R_ANGLE@0..1 ">" [] [] + 1: MD_BLOCK_LIST@1..48 + 0: MD_PARAGRAPH@1..14 + 0: MD_INLINE_ITEM_LIST@1..14 + 0: MD_TEXTUAL@1..13 + 0: MD_TEXTUAL_LITERAL@1..13 "Outer quote" [Skipped(" ")] [] + 1: MD_TEXTUAL@13..14 + 0: MD_TEXTUAL_LITERAL@13..14 "\n" [] [] + 1: (empty) + 1: MD_QUOTE@14..48 + 0: R_ANGLE@14..16 ">" [Skipped(">")] [] + 1: MD_BLOCK_LIST@16..48 + 0: MD_PARAGRAPH@16..30 + 0: MD_INLINE_ITEM_LIST@16..30 + 0: MD_TEXTUAL@16..29 + 0: MD_TEXTUAL_LITERAL@16..29 "Nested quote" [Skipped(" ")] [] + 1: MD_TEXTUAL@29..30 + 0: MD_TEXTUAL_LITERAL@29..30 "\n" [] [] + 1: (empty) + 1: MD_QUOTE@30..48 + 0: R_ANGLE@30..33 ">" [Skipped(">"), Skipped(">")] [] + 1: MD_BLOCK_LIST@33..48 + 0: MD_PARAGRAPH@33..48 + 0: MD_INLINE_ITEM_LIST@33..48 + 0: MD_TEXTUAL@33..47 + 0: MD_TEXTUAL_LITERAL@33..47 "Deeply nested" [Skipped(" ")] [] + 1: MD_TEXTUAL@47..48 + 0: MD_TEXTUAL_LITERAL@47..48 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@48..49 + 0: NEWLINE@48..49 "\n" [] [] + 2: MD_QUOTE@49..65 + 0: R_ANGLE@49..50 ">" [] [] + 1: MD_BLOCK_LIST@50..65 + 0: MD_PARAGRAPH@50..65 + 0: MD_INLINE_ITEM_LIST@50..65 + 0: MD_TEXTUAL@50..64 + 0: MD_TEXTUAL_LITERAL@50..64 "Back to outer" [Skipped(" ")] [] + 1: MD_TEXTUAL@64..65 + 0: MD_TEXTUAL_LITERAL@64..65 "\n" [] [] + 1: (empty) + 2: EOF@65..65 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/ordered_list.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/ordered_list.md new file mode 100644 index 000000000000..b250e4984978 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/ordered_list.md @@ -0,0 +1,6 @@ +1. First item +2. Second item +3. Third item + +1) Using parenthesis +2) Another item diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/ordered_list.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/ordered_list.md.snap new file mode 100644 index 000000000000..f1c98f087c14 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/ordered_list.md.snap @@ -0,0 +1,185 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +1. First item +2. Second item +3. Third item + +1) Using parenthesis +2) Another item + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdOrderedListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: MD_ORDERED_LIST_MARKER@0..2 "1." [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@2..13 " First item" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..14 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBullet { + bullet: MD_ORDERED_LIST_MARKER@14..16 "2." [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..28 " Second item" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..29 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBullet { + bullet: MD_ORDERED_LIST_MARKER@29..31 "3." [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..42 " Third item" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..43 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@43..44 "\n" [] [], + }, + ], + }, + ], + }, + MdOrderedListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: MD_ORDERED_LIST_MARKER@44..46 "1)" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@46..64 " Using parenthesis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@64..65 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdBullet { + bullet: MD_ORDERED_LIST_MARKER@65..67 "2)" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@67..80 " Another item" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..81 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + }, + ], + eof_token: EOF@81..81 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..81 + 0: (empty) + 1: MD_BLOCK_LIST@0..81 + 0: MD_ORDERED_LIST_ITEM@0..44 + 0: MD_BULLET_LIST@0..44 + 0: MD_BULLET@0..14 + 0: MD_ORDERED_LIST_MARKER@0..2 "1." [] [] + 1: MD_BLOCK_LIST@2..14 + 0: MD_PARAGRAPH@2..14 + 0: MD_INLINE_ITEM_LIST@2..14 + 0: MD_TEXTUAL@2..13 + 0: MD_TEXTUAL_LITERAL@2..13 " First item" [] [] + 1: MD_TEXTUAL@13..14 + 0: MD_TEXTUAL_LITERAL@13..14 "\n" [] [] + 1: (empty) + 1: MD_BULLET@14..29 + 0: MD_ORDERED_LIST_MARKER@14..16 "2." [] [] + 1: MD_BLOCK_LIST@16..29 + 0: MD_PARAGRAPH@16..29 + 0: MD_INLINE_ITEM_LIST@16..29 + 0: MD_TEXTUAL@16..28 + 0: MD_TEXTUAL_LITERAL@16..28 " Second item" [] [] + 1: MD_TEXTUAL@28..29 + 0: MD_TEXTUAL_LITERAL@28..29 "\n" [] [] + 1: (empty) + 2: MD_BULLET@29..44 + 0: MD_ORDERED_LIST_MARKER@29..31 "3." [] [] + 1: MD_BLOCK_LIST@31..44 + 0: MD_PARAGRAPH@31..43 + 0: MD_INLINE_ITEM_LIST@31..43 + 0: MD_TEXTUAL@31..42 + 0: MD_TEXTUAL_LITERAL@31..42 " Third item" [] [] + 1: MD_TEXTUAL@42..43 + 0: MD_TEXTUAL_LITERAL@42..43 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@43..44 + 0: NEWLINE@43..44 "\n" [] [] + 1: MD_ORDERED_LIST_ITEM@44..81 + 0: MD_BULLET_LIST@44..81 + 0: MD_BULLET@44..65 + 0: MD_ORDERED_LIST_MARKER@44..46 "1)" [] [] + 1: MD_BLOCK_LIST@46..65 + 0: MD_PARAGRAPH@46..65 + 0: MD_INLINE_ITEM_LIST@46..65 + 0: MD_TEXTUAL@46..64 + 0: MD_TEXTUAL_LITERAL@46..64 " Using parenthesis" [] [] + 1: MD_TEXTUAL@64..65 + 0: MD_TEXTUAL_LITERAL@64..65 "\n" [] [] + 1: (empty) + 1: MD_BULLET@65..81 + 0: MD_ORDERED_LIST_MARKER@65..67 "2)" [] [] + 1: MD_BLOCK_LIST@67..81 + 0: MD_PARAGRAPH@67..81 + 0: MD_INLINE_ITEM_LIST@67..81 + 0: MD_TEXTUAL@67..80 + 0: MD_TEXTUAL_LITERAL@67..80 " Another item" [] [] + 1: MD_TEXTUAL@80..81 + 0: MD_TEXTUAL_LITERAL@80..81 "\n" [] [] + 1: (empty) + 2: EOF@81..81 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph.md new file mode 100644 index 000000000000..b3f891111b93 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph.md @@ -0,0 +1,3 @@ +This is a simple paragraph. + +This is another paragraph. \ No newline at end of file diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph.md.snap new file mode 100644 index 000000000000..a6ca100f767f --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph.md.snap @@ -0,0 +1,70 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This is a simple paragraph. + +This is another paragraph. +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..27 "This is a simple paragraph." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@27..28 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@28..29 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..55 "This is another paragraph." [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@55..55 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..55 + 0: (empty) + 1: MD_BLOCK_LIST@0..55 + 0: MD_PARAGRAPH@0..28 + 0: MD_INLINE_ITEM_LIST@0..28 + 0: MD_TEXTUAL@0..27 + 0: MD_TEXTUAL_LITERAL@0..27 "This is a simple paragraph." [] [] + 1: MD_TEXTUAL@27..28 + 0: MD_TEXTUAL_LITERAL@27..28 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@28..29 + 0: NEWLINE@28..29 "\n" [] [] + 2: MD_PARAGRAPH@29..55 + 0: MD_INLINE_ITEM_LIST@29..55 + 0: MD_TEXTUAL@29..55 + 0: MD_TEXTUAL_LITERAL@29..55 "This is another paragraph." [] [] + 1: (empty) + 2: EOF@55..55 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph_interruption.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph_interruption.md new file mode 100644 index 000000000000..48c087618be2 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph_interruption.md @@ -0,0 +1,16 @@ +Paragraph text +# Heading interrupts + +More text here +- List interrupts + +Another para +> Quote interrupts + +Some text +``` +Fence interrupts +``` + +Final text +*** diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph_interruption.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph_interruption.md.snap new file mode 100644 index 000000000000..361c97814589 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/paragraph_interruption.md.snap @@ -0,0 +1,289 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Paragraph text +# Heading interrupts + +More text here +- List interrupts + +Another para +> Quote interrupts + +Some text +``` +Fence interrupts +``` + +Final text +*** + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..14 "Paragraph text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..15 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdHeader { + before: MdHashList [ + MdHash { + hash_token: HASH@15..16 "#" [] [], + }, + ], + content: MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..35 " Heading interrupts" [] [], + }, + ], + hard_line: missing (optional), + }, + after: MdHashList [], + }, + MdNewline { + value_token: NEWLINE@35..36 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@36..37 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..51 "More text here" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@51..52 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdBulletListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: MINUS@52..53 "-" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@53..69 " List interrupts" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@69..70 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + }, + MdNewline { + value_token: NEWLINE@70..71 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@71..83 "Another para" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@83..84 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdQuote { + marker_token: R_ANGLE@84..85 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@85..102 "Quote interrupts" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@102..103 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@103..104 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@104..113 "Some text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@113..114 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdFencedCodeBlock { + l_fence: TRIPLE_BACKTICK@114..117 "```" [] [], + code_list: MdCodeNameList [], + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@117..118 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@118..134 "Fence interrupts" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@134..135 "\n" [] [], + }, + ], + r_fence: TRIPLE_BACKTICK@135..138 "```" [] [], + }, + MdNewline { + value_token: NEWLINE@138..139 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@139..140 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@140..150 "Final text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@150..151 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdThematicBreakBlock { + value_token: MD_THEMATIC_BREAK_LITERAL@151..154 "***" [] [], + }, + MdNewline { + value_token: NEWLINE@154..155 "\n" [] [], + }, + ], + eof_token: EOF@155..155 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..155 + 0: (empty) + 1: MD_BLOCK_LIST@0..155 + 0: MD_PARAGRAPH@0..15 + 0: MD_INLINE_ITEM_LIST@0..15 + 0: MD_TEXTUAL@0..14 + 0: MD_TEXTUAL_LITERAL@0..14 "Paragraph text" [] [] + 1: MD_TEXTUAL@14..15 + 0: MD_TEXTUAL_LITERAL@14..15 "\n" [] [] + 1: (empty) + 1: MD_HEADER@15..35 + 0: MD_HASH_LIST@15..16 + 0: MD_HASH@15..16 + 0: HASH@15..16 "#" [] [] + 1: MD_PARAGRAPH@16..35 + 0: MD_INLINE_ITEM_LIST@16..35 + 0: MD_TEXTUAL@16..35 + 0: MD_TEXTUAL_LITERAL@16..35 " Heading interrupts" [] [] + 1: (empty) + 2: MD_HASH_LIST@35..35 + 2: MD_NEWLINE@35..36 + 0: NEWLINE@35..36 "\n" [] [] + 3: MD_NEWLINE@36..37 + 0: NEWLINE@36..37 "\n" [] [] + 4: MD_PARAGRAPH@37..52 + 0: MD_INLINE_ITEM_LIST@37..52 + 0: MD_TEXTUAL@37..51 + 0: MD_TEXTUAL_LITERAL@37..51 "More text here" [] [] + 1: MD_TEXTUAL@51..52 + 0: MD_TEXTUAL_LITERAL@51..52 "\n" [] [] + 1: (empty) + 5: MD_BULLET_LIST_ITEM@52..70 + 0: MD_BULLET_LIST@52..70 + 0: MD_BULLET@52..70 + 0: MINUS@52..53 "-" [] [] + 1: MD_BLOCK_LIST@53..70 + 0: MD_PARAGRAPH@53..70 + 0: MD_INLINE_ITEM_LIST@53..70 + 0: MD_TEXTUAL@53..69 + 0: MD_TEXTUAL_LITERAL@53..69 " List interrupts" [] [] + 1: MD_TEXTUAL@69..70 + 0: MD_TEXTUAL_LITERAL@69..70 "\n" [] [] + 1: (empty) + 6: MD_NEWLINE@70..71 + 0: NEWLINE@70..71 "\n" [] [] + 7: MD_PARAGRAPH@71..84 + 0: MD_INLINE_ITEM_LIST@71..84 + 0: MD_TEXTUAL@71..83 + 0: MD_TEXTUAL_LITERAL@71..83 "Another para" [] [] + 1: MD_TEXTUAL@83..84 + 0: MD_TEXTUAL_LITERAL@83..84 "\n" [] [] + 1: (empty) + 8: MD_QUOTE@84..103 + 0: R_ANGLE@84..85 ">" [] [] + 1: MD_BLOCK_LIST@85..103 + 0: MD_PARAGRAPH@85..103 + 0: MD_INLINE_ITEM_LIST@85..103 + 0: MD_TEXTUAL@85..102 + 0: MD_TEXTUAL_LITERAL@85..102 "Quote interrupts" [Skipped(" ")] [] + 1: MD_TEXTUAL@102..103 + 0: MD_TEXTUAL_LITERAL@102..103 "\n" [] [] + 1: (empty) + 9: MD_NEWLINE@103..104 + 0: NEWLINE@103..104 "\n" [] [] + 10: MD_PARAGRAPH@104..114 + 0: MD_INLINE_ITEM_LIST@104..114 + 0: MD_TEXTUAL@104..113 + 0: MD_TEXTUAL_LITERAL@104..113 "Some text" [] [] + 1: MD_TEXTUAL@113..114 + 0: MD_TEXTUAL_LITERAL@113..114 "\n" [] [] + 1: (empty) + 11: MD_FENCED_CODE_BLOCK@114..138 + 0: TRIPLE_BACKTICK@114..117 "```" [] [] + 1: MD_CODE_NAME_LIST@117..117 + 2: MD_INLINE_ITEM_LIST@117..135 + 0: MD_TEXTUAL@117..118 + 0: MD_TEXTUAL_LITERAL@117..118 "\n" [] [] + 1: MD_TEXTUAL@118..134 + 0: MD_TEXTUAL_LITERAL@118..134 "Fence interrupts" [] [] + 2: MD_TEXTUAL@134..135 + 0: MD_TEXTUAL_LITERAL@134..135 "\n" [] [] + 3: TRIPLE_BACKTICK@135..138 "```" [] [] + 12: MD_NEWLINE@138..139 + 0: NEWLINE@138..139 "\n" [] [] + 13: MD_NEWLINE@139..140 + 0: NEWLINE@139..140 "\n" [] [] + 14: MD_PARAGRAPH@140..151 + 0: MD_INLINE_ITEM_LIST@140..151 + 0: MD_TEXTUAL@140..150 + 0: MD_TEXTUAL_LITERAL@140..150 "Final text" [] [] + 1: MD_TEXTUAL@150..151 + 0: MD_TEXTUAL_LITERAL@150..151 "\n" [] [] + 1: (empty) + 15: MD_THEMATIC_BREAK_BLOCK@151..154 + 0: MD_THEMATIC_BREAK_LITERAL@151..154 "***" [] [] + 16: MD_NEWLINE@154..155 + 0: NEWLINE@154..155 "\n" [] [] + 2: EOF@155..155 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/paren_depth_limit.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/paren_depth_limit.md new file mode 100644 index 000000000000..3cbf1f91d3e1 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/paren_depth_limit.md @@ -0,0 +1 @@ +[a](x((((((((((((((((((((((((((((((((y))))))))))))))))))))))))))))))))) diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/paren_depth_limit.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/paren_depth_limit.md.snap new file mode 100644 index 000000000000..d51a9ea0e1e8 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/paren_depth_limit.md.snap @@ -0,0 +1,400 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +[a](x((((((((((((((((((((((((((((((((y))))))))))))))))))))))))))))))))) + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdInlineLink { + l_brack_token: L_BRACK@0..1 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..2 "a" [] [], + }, + ], + r_brack_token: R_BRACK@2..3 "]" [] [], + l_paren_token: L_PAREN@3..4 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@4..5 "x" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@5..6 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@6..7 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@7..8 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@8..9 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..11 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@11..12 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@12..13 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..14 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..15 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..16 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..17 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@17..18 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@18..19 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@19..20 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@20..21 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..22 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@22..23 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..24 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..25 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@25..26 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@26..27 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@27..28 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@28..29 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@29..30 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..32 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..33 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@33..34 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@34..35 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@35..36 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@36..37 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@37..38 "y" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@38..39 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@39..40 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..41 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@41..42 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..43 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@43..44 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@44..45 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@45..46 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@46..47 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@47..48 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@48..49 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@49..50 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@50..51 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@51..52 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@52..53 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@53..54 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@54..55 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@55..56 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@56..57 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@57..58 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..59 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@59..60 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@60..61 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@61..62 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@62..63 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@63..64 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@64..65 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@65..66 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@66..67 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@67..68 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..69 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@69..70 ")" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@70..71 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@71..72 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@72..72 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..72 + 0: (empty) + 1: MD_BLOCK_LIST@0..72 + 0: MD_PARAGRAPH@0..72 + 0: MD_INLINE_ITEM_LIST@0..72 + 0: MD_INLINE_LINK@0..71 + 0: L_BRACK@0..1 "[" [] [] + 1: MD_INLINE_ITEM_LIST@1..2 + 0: MD_TEXTUAL@1..2 + 0: MD_TEXTUAL_LITERAL@1..2 "a" [] [] + 2: R_BRACK@2..3 "]" [] [] + 3: L_PAREN@3..4 "(" [] [] + 4: MD_INLINE_ITEM_LIST@4..70 + 0: MD_TEXTUAL@4..5 + 0: MD_TEXTUAL_LITERAL@4..5 "x" [] [] + 1: MD_TEXTUAL@5..6 + 0: MD_TEXTUAL_LITERAL@5..6 "(" [] [] + 2: MD_TEXTUAL@6..7 + 0: MD_TEXTUAL_LITERAL@6..7 "(" [] [] + 3: MD_TEXTUAL@7..8 + 0: MD_TEXTUAL_LITERAL@7..8 "(" [] [] + 4: MD_TEXTUAL@8..9 + 0: MD_TEXTUAL_LITERAL@8..9 "(" [] [] + 5: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "(" [] [] + 6: MD_TEXTUAL@10..11 + 0: MD_TEXTUAL_LITERAL@10..11 "(" [] [] + 7: MD_TEXTUAL@11..12 + 0: MD_TEXTUAL_LITERAL@11..12 "(" [] [] + 8: MD_TEXTUAL@12..13 + 0: MD_TEXTUAL_LITERAL@12..13 "(" [] [] + 9: MD_TEXTUAL@13..14 + 0: MD_TEXTUAL_LITERAL@13..14 "(" [] [] + 10: MD_TEXTUAL@14..15 + 0: MD_TEXTUAL_LITERAL@14..15 "(" [] [] + 11: MD_TEXTUAL@15..16 + 0: MD_TEXTUAL_LITERAL@15..16 "(" [] [] + 12: MD_TEXTUAL@16..17 + 0: MD_TEXTUAL_LITERAL@16..17 "(" [] [] + 13: MD_TEXTUAL@17..18 + 0: MD_TEXTUAL_LITERAL@17..18 "(" [] [] + 14: MD_TEXTUAL@18..19 + 0: MD_TEXTUAL_LITERAL@18..19 "(" [] [] + 15: MD_TEXTUAL@19..20 + 0: MD_TEXTUAL_LITERAL@19..20 "(" [] [] + 16: MD_TEXTUAL@20..21 + 0: MD_TEXTUAL_LITERAL@20..21 "(" [] [] + 17: MD_TEXTUAL@21..22 + 0: MD_TEXTUAL_LITERAL@21..22 "(" [] [] + 18: MD_TEXTUAL@22..23 + 0: MD_TEXTUAL_LITERAL@22..23 "(" [] [] + 19: MD_TEXTUAL@23..24 + 0: MD_TEXTUAL_LITERAL@23..24 "(" [] [] + 20: MD_TEXTUAL@24..25 + 0: MD_TEXTUAL_LITERAL@24..25 "(" [] [] + 21: MD_TEXTUAL@25..26 + 0: MD_TEXTUAL_LITERAL@25..26 "(" [] [] + 22: MD_TEXTUAL@26..27 + 0: MD_TEXTUAL_LITERAL@26..27 "(" [] [] + 23: MD_TEXTUAL@27..28 + 0: MD_TEXTUAL_LITERAL@27..28 "(" [] [] + 24: MD_TEXTUAL@28..29 + 0: MD_TEXTUAL_LITERAL@28..29 "(" [] [] + 25: MD_TEXTUAL@29..30 + 0: MD_TEXTUAL_LITERAL@29..30 "(" [] [] + 26: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 "(" [] [] + 27: MD_TEXTUAL@31..32 + 0: MD_TEXTUAL_LITERAL@31..32 "(" [] [] + 28: MD_TEXTUAL@32..33 + 0: MD_TEXTUAL_LITERAL@32..33 "(" [] [] + 29: MD_TEXTUAL@33..34 + 0: MD_TEXTUAL_LITERAL@33..34 "(" [] [] + 30: MD_TEXTUAL@34..35 + 0: MD_TEXTUAL_LITERAL@34..35 "(" [] [] + 31: MD_TEXTUAL@35..36 + 0: MD_TEXTUAL_LITERAL@35..36 "(" [] [] + 32: MD_TEXTUAL@36..37 + 0: MD_TEXTUAL_LITERAL@36..37 "(" [] [] + 33: MD_TEXTUAL@37..38 + 0: MD_TEXTUAL_LITERAL@37..38 "y" [] [] + 34: MD_TEXTUAL@38..39 + 0: MD_TEXTUAL_LITERAL@38..39 ")" [] [] + 35: MD_TEXTUAL@39..40 + 0: MD_TEXTUAL_LITERAL@39..40 ")" [] [] + 36: MD_TEXTUAL@40..41 + 0: MD_TEXTUAL_LITERAL@40..41 ")" [] [] + 37: MD_TEXTUAL@41..42 + 0: MD_TEXTUAL_LITERAL@41..42 ")" [] [] + 38: MD_TEXTUAL@42..43 + 0: MD_TEXTUAL_LITERAL@42..43 ")" [] [] + 39: MD_TEXTUAL@43..44 + 0: MD_TEXTUAL_LITERAL@43..44 ")" [] [] + 40: MD_TEXTUAL@44..45 + 0: MD_TEXTUAL_LITERAL@44..45 ")" [] [] + 41: MD_TEXTUAL@45..46 + 0: MD_TEXTUAL_LITERAL@45..46 ")" [] [] + 42: MD_TEXTUAL@46..47 + 0: MD_TEXTUAL_LITERAL@46..47 ")" [] [] + 43: MD_TEXTUAL@47..48 + 0: MD_TEXTUAL_LITERAL@47..48 ")" [] [] + 44: MD_TEXTUAL@48..49 + 0: MD_TEXTUAL_LITERAL@48..49 ")" [] [] + 45: MD_TEXTUAL@49..50 + 0: MD_TEXTUAL_LITERAL@49..50 ")" [] [] + 46: MD_TEXTUAL@50..51 + 0: MD_TEXTUAL_LITERAL@50..51 ")" [] [] + 47: MD_TEXTUAL@51..52 + 0: MD_TEXTUAL_LITERAL@51..52 ")" [] [] + 48: MD_TEXTUAL@52..53 + 0: MD_TEXTUAL_LITERAL@52..53 ")" [] [] + 49: MD_TEXTUAL@53..54 + 0: MD_TEXTUAL_LITERAL@53..54 ")" [] [] + 50: MD_TEXTUAL@54..55 + 0: MD_TEXTUAL_LITERAL@54..55 ")" [] [] + 51: MD_TEXTUAL@55..56 + 0: MD_TEXTUAL_LITERAL@55..56 ")" [] [] + 52: MD_TEXTUAL@56..57 + 0: MD_TEXTUAL_LITERAL@56..57 ")" [] [] + 53: MD_TEXTUAL@57..58 + 0: MD_TEXTUAL_LITERAL@57..58 ")" [] [] + 54: MD_TEXTUAL@58..59 + 0: MD_TEXTUAL_LITERAL@58..59 ")" [] [] + 55: MD_TEXTUAL@59..60 + 0: MD_TEXTUAL_LITERAL@59..60 ")" [] [] + 56: MD_TEXTUAL@60..61 + 0: MD_TEXTUAL_LITERAL@60..61 ")" [] [] + 57: MD_TEXTUAL@61..62 + 0: MD_TEXTUAL_LITERAL@61..62 ")" [] [] + 58: MD_TEXTUAL@62..63 + 0: MD_TEXTUAL_LITERAL@62..63 ")" [] [] + 59: MD_TEXTUAL@63..64 + 0: MD_TEXTUAL_LITERAL@63..64 ")" [] [] + 60: MD_TEXTUAL@64..65 + 0: MD_TEXTUAL_LITERAL@64..65 ")" [] [] + 61: MD_TEXTUAL@65..66 + 0: MD_TEXTUAL_LITERAL@65..66 ")" [] [] + 62: MD_TEXTUAL@66..67 + 0: MD_TEXTUAL_LITERAL@66..67 ")" [] [] + 63: MD_TEXTUAL@67..68 + 0: MD_TEXTUAL_LITERAL@67..68 ")" [] [] + 64: MD_TEXTUAL@68..69 + 0: MD_TEXTUAL_LITERAL@68..69 ")" [] [] + 65: MD_TEXTUAL@69..70 + 0: MD_TEXTUAL_LITERAL@69..70 ")" [] [] + 5: (empty) + 6: R_PAREN@70..71 ")" [] [] + 1: MD_TEXTUAL@71..72 + 0: MD_TEXTUAL_LITERAL@71..72 "\n" [] [] + 1: (empty) + 2: EOF@72..72 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_link_not_implemented.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_link_not_implemented.md new file mode 100644 index 000000000000..0c7df8482598 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_link_not_implemented.md @@ -0,0 +1 @@ +Reference links without definitions should be parsed as text: [text][label]. diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_link_not_implemented.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_link_not_implemented.md.snap new file mode 100644 index 000000000000..70745a7d274c --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_link_not_implemented.md.snap @@ -0,0 +1,86 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Reference links without definitions should be parsed as text: [text][label]. + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..62 "Reference links without definitions should be parsed as text: " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@62..63 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@63..67 "text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@67..68 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..69 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@69..74 "label" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@74..75 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@75..76 "." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@76..77 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@77..77 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..77 + 0: (empty) + 1: MD_BLOCK_LIST@0..77 + 0: MD_PARAGRAPH@0..77 + 0: MD_INLINE_ITEM_LIST@0..77 + 0: MD_TEXTUAL@0..62 + 0: MD_TEXTUAL_LITERAL@0..62 "Reference links without definitions should be parsed as text: " [] [] + 1: MD_TEXTUAL@62..63 + 0: MD_TEXTUAL_LITERAL@62..63 "[" [] [] + 2: MD_TEXTUAL@63..67 + 0: MD_TEXTUAL_LITERAL@63..67 "text" [] [] + 3: MD_TEXTUAL@67..68 + 0: MD_TEXTUAL_LITERAL@67..68 "]" [] [] + 4: MD_TEXTUAL@68..69 + 0: MD_TEXTUAL_LITERAL@68..69 "[" [] [] + 5: MD_TEXTUAL@69..74 + 0: MD_TEXTUAL_LITERAL@69..74 "label" [] [] + 6: MD_TEXTUAL@74..75 + 0: MD_TEXTUAL_LITERAL@74..75 "]" [] [] + 7: MD_TEXTUAL@75..76 + 0: MD_TEXTUAL_LITERAL@75..76 "." [] [] + 8: MD_TEXTUAL@76..77 + 0: MD_TEXTUAL_LITERAL@76..77 "\n" [] [] + 1: (empty) + 2: EOF@77..77 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_links.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_links.md new file mode 100644 index 000000000000..1fd899443983 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_links.md @@ -0,0 +1,37 @@ +[example]: https://example.com "Example Title" + +Full reference: [click here][example] + +Collapsed reference: [example][] + +Shortcut reference: [example] + +[foo]: https://foo.com + +Image full: ![alt text][foo] + +Image collapsed: ![foo][] + +Image shortcut: ![foo] + +Multiple words in text: [click here for more info][example] + +Empty label (collapsed): [test][] + +[test]: https://test.com + +Shortcut that looks like text: [undefined] + +Mixed with inline: [inline](https://inline.com) and [ref][example] + +Nested in paragraph: This is a paragraph with [a reference][foo] in the middle. + +[Case Label]: https://case.example + +Case-insensitive: [case label] + +Whitespace normalized: [case label] + +[label\]]: https://escaped.example + +Escaped bracket in label: [text][label\]] diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_links.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_links.md.snap new file mode 100644 index 000000000000..fce5c5466dec --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/reference_links.md.snap @@ -0,0 +1,1065 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +[example]: https://example.com "Example Title" + +Full reference: [click here][example] + +Collapsed reference: [example][] + +Shortcut reference: [example] + +[foo]: https://foo.com + +Image full: ![alt text][foo] + +Image collapsed: ![foo][] + +Image shortcut: ![foo] + +Multiple words in text: [click here for more info][example] + +Empty label (collapsed): [test][] + +[test]: https://test.com + +Shortcut that looks like text: [undefined] + +Mixed with inline: [inline](https://inline.com) and [ref][example] + +Nested in paragraph: This is a paragraph with [a reference][foo] in the middle. + +[Case Label]: https://case.example + +Case-insensitive: [case label] + +Whitespace normalized: [case label] + +[label\]]: https://escaped.example + +Escaped bracket in label: [text][label\]] + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@0..1 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@1..8 "example" [] [], + }, + ], + }, + r_brack_token: R_BRACK@8..9 "]" [] [], + colon_token: COLON@9..10 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..11 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@11..30 "https://example.com" [] [], + }, + ], + }, + title: MdLinkTitle { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@31..46 "\"Example Title\"" [] [], + }, + ], + }, + }, + MdNewline { + value_token: NEWLINE@46..47 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@47..48 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@48..64 "Full reference: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@64..65 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@65..75 "click here" [] [], + }, + ], + r_brack_token: R_BRACK@75..76 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@76..77 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@77..84 "example" [] [], + }, + ], + r_brack_token: R_BRACK@84..85 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@85..86 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@86..87 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@87..108 "Collapsed reference: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@108..109 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@109..116 "example" [] [], + }, + ], + r_brack_token: R_BRACK@116..117 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@117..118 "[" [] [], + label: MdInlineItemList [], + r_brack_token: R_BRACK@118..119 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@119..120 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@120..121 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@121..141 "Shortcut reference: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@141..142 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@142..149 "example" [] [], + }, + ], + r_brack_token: R_BRACK@149..150 "]" [] [], + label: missing (optional), + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@150..151 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@151..152 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@152..153 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@153..156 "foo" [] [], + }, + ], + }, + r_brack_token: R_BRACK@156..157 "]" [] [], + colon_token: COLON@157..158 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@158..159 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@159..174 "https://foo.com" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@174..175 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@175..176 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@176..188 "Image full: " [] [], + }, + MdReferenceImage { + excl_token: BANG@188..189 "!" [] [], + l_brack_token: L_BRACK@189..190 "[" [] [], + alt: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@190..198 "alt text" [] [], + }, + ], + r_brack_token: R_BRACK@198..199 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@199..200 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@200..203 "foo" [] [], + }, + ], + r_brack_token: R_BRACK@203..204 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@204..205 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@205..206 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@206..223 "Image collapsed: " [] [], + }, + MdReferenceImage { + excl_token: BANG@223..224 "!" [] [], + l_brack_token: L_BRACK@224..225 "[" [] [], + alt: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@225..228 "foo" [] [], + }, + ], + r_brack_token: R_BRACK@228..229 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@229..230 "[" [] [], + label: MdInlineItemList [], + r_brack_token: R_BRACK@230..231 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@231..232 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@232..233 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@233..249 "Image shortcut: " [] [], + }, + MdReferenceImage { + excl_token: BANG@249..250 "!" [] [], + l_brack_token: L_BRACK@250..251 "[" [] [], + alt: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@251..254 "foo" [] [], + }, + ], + r_brack_token: R_BRACK@254..255 "]" [] [], + label: missing (optional), + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@255..256 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@256..257 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@257..281 "Multiple words in text: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@281..282 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@282..306 "click here for more info" [] [], + }, + ], + r_brack_token: R_BRACK@306..307 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@307..308 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@308..315 "example" [] [], + }, + ], + r_brack_token: R_BRACK@315..316 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@316..317 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@317..318 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@318..330 "Empty label " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@330..331 "(" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@331..340 "collapsed" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@340..341 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@341..342 ":" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@342..343 " " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@343..344 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@344..348 "test" [] [], + }, + ], + r_brack_token: R_BRACK@348..349 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@349..350 "[" [] [], + label: MdInlineItemList [], + r_brack_token: R_BRACK@350..351 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@351..352 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@352..353 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@353..354 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@354..358 "test" [] [], + }, + ], + }, + r_brack_token: R_BRACK@358..359 "]" [] [], + colon_token: COLON@359..360 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@360..361 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@361..377 "https://test.com" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@377..378 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@378..379 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@379..410 "Shortcut that looks like text: " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@410..411 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@411..420 "undefined" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@420..421 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@421..422 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@422..423 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@423..442 "Mixed with inline: " [] [], + }, + MdInlineLink { + l_brack_token: L_BRACK@442..443 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@443..449 "inline" [] [], + }, + ], + r_brack_token: R_BRACK@449..450 "]" [] [], + l_paren_token: L_PAREN@450..451 "(" [] [], + destination: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@451..469 "https://inline.com" [] [], + }, + ], + title: missing (optional), + r_paren_token: R_PAREN@469..470 ")" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@470..475 " and " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@475..476 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@476..479 "ref" [] [], + }, + ], + r_brack_token: R_BRACK@479..480 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@480..481 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@481..488 "example" [] [], + }, + ], + r_brack_token: R_BRACK@488..489 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@489..490 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@490..491 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@491..537 "Nested in paragraph: This is a paragraph with " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@537..538 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@538..549 "a reference" [] [], + }, + ], + r_brack_token: R_BRACK@549..550 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@550..551 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@551..554 "foo" [] [], + }, + ], + r_brack_token: R_BRACK@554..555 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@555..570 " in the middle." [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@570..571 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@571..572 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@572..573 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@573..583 "Case Label" [] [], + }, + ], + }, + r_brack_token: R_BRACK@583..584 "]" [] [], + colon_token: COLON@584..585 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@585..586 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@586..606 "https://case.example" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@606..607 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@607..608 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@608..612 "Case" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@612..613 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@613..626 "insensitive: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@626..627 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@627..637 "case label" [] [], + }, + ], + r_brack_token: R_BRACK@637..638 "]" [] [], + label: missing (optional), + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@638..639 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@639..640 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@640..663 "Whitespace normalized: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@663..664 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@664..676 "case label" [] [], + }, + ], + r_brack_token: R_BRACK@676..677 "]" [] [], + label: missing (optional), + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@677..678 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@678..679 "\n" [] [], + }, + MdLinkReferenceDefinition { + l_brack_token: L_BRACK@679..680 "[" [] [], + label: MdLinkLabel { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@680..685 "label" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@685..687 "\\]" [] [], + }, + ], + }, + r_brack_token: R_BRACK@687..688 "]" [] [], + colon_token: COLON@688..689 ":" [] [], + destination: MdLinkDestination { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@689..690 " " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@690..713 "https://escaped.example" [] [], + }, + ], + }, + title: missing (optional), + }, + MdNewline { + value_token: NEWLINE@713..714 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@714..715 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@715..741 "Escaped bracket in label: " [] [], + }, + MdReferenceLink { + l_brack_token: L_BRACK@741..742 "[" [] [], + text: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@742..746 "text" [] [], + }, + ], + r_brack_token: R_BRACK@746..747 "]" [] [], + label: MdReferenceLinkLabel { + l_brack_token: L_BRACK@747..748 "[" [] [], + label: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@748..753 "label" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@753..755 "\\]" [] [], + }, + ], + r_brack_token: R_BRACK@755..756 "]" [] [], + }, + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@756..757 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@757..757 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..757 + 0: (empty) + 1: MD_BLOCK_LIST@0..757 + 0: MD_LINK_REFERENCE_DEFINITION@0..46 + 0: L_BRACK@0..1 "[" [] [] + 1: MD_LINK_LABEL@1..8 + 0: MD_INLINE_ITEM_LIST@1..8 + 0: MD_TEXTUAL@1..8 + 0: MD_TEXTUAL_LITERAL@1..8 "example" [] [] + 2: R_BRACK@8..9 "]" [] [] + 3: COLON@9..10 ":" [] [] + 4: MD_LINK_DESTINATION@10..30 + 0: MD_INLINE_ITEM_LIST@10..30 + 0: MD_TEXTUAL@10..11 + 0: MD_TEXTUAL_LITERAL@10..11 " " [] [] + 1: MD_TEXTUAL@11..30 + 0: MD_TEXTUAL_LITERAL@11..30 "https://example.com" [] [] + 5: MD_LINK_TITLE@30..46 + 0: MD_INLINE_ITEM_LIST@30..46 + 0: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 " " [] [] + 1: MD_TEXTUAL@31..46 + 0: MD_TEXTUAL_LITERAL@31..46 "\"Example Title\"" [] [] + 1: MD_NEWLINE@46..47 + 0: NEWLINE@46..47 "\n" [] [] + 2: MD_NEWLINE@47..48 + 0: NEWLINE@47..48 "\n" [] [] + 3: MD_PARAGRAPH@48..86 + 0: MD_INLINE_ITEM_LIST@48..86 + 0: MD_TEXTUAL@48..64 + 0: MD_TEXTUAL_LITERAL@48..64 "Full reference: " [] [] + 1: MD_REFERENCE_LINK@64..85 + 0: L_BRACK@64..65 "[" [] [] + 1: MD_INLINE_ITEM_LIST@65..75 + 0: MD_TEXTUAL@65..75 + 0: MD_TEXTUAL_LITERAL@65..75 "click here" [] [] + 2: R_BRACK@75..76 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@76..85 + 0: L_BRACK@76..77 "[" [] [] + 1: MD_INLINE_ITEM_LIST@77..84 + 0: MD_TEXTUAL@77..84 + 0: MD_TEXTUAL_LITERAL@77..84 "example" [] [] + 2: R_BRACK@84..85 "]" [] [] + 2: MD_TEXTUAL@85..86 + 0: MD_TEXTUAL_LITERAL@85..86 "\n" [] [] + 1: (empty) + 4: MD_NEWLINE@86..87 + 0: NEWLINE@86..87 "\n" [] [] + 5: MD_PARAGRAPH@87..120 + 0: MD_INLINE_ITEM_LIST@87..120 + 0: MD_TEXTUAL@87..108 + 0: MD_TEXTUAL_LITERAL@87..108 "Collapsed reference: " [] [] + 1: MD_REFERENCE_LINK@108..119 + 0: L_BRACK@108..109 "[" [] [] + 1: MD_INLINE_ITEM_LIST@109..116 + 0: MD_TEXTUAL@109..116 + 0: MD_TEXTUAL_LITERAL@109..116 "example" [] [] + 2: R_BRACK@116..117 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@117..119 + 0: L_BRACK@117..118 "[" [] [] + 1: MD_INLINE_ITEM_LIST@118..118 + 2: R_BRACK@118..119 "]" [] [] + 2: MD_TEXTUAL@119..120 + 0: MD_TEXTUAL_LITERAL@119..120 "\n" [] [] + 1: (empty) + 6: MD_NEWLINE@120..121 + 0: NEWLINE@120..121 "\n" [] [] + 7: MD_PARAGRAPH@121..151 + 0: MD_INLINE_ITEM_LIST@121..151 + 0: MD_TEXTUAL@121..141 + 0: MD_TEXTUAL_LITERAL@121..141 "Shortcut reference: " [] [] + 1: MD_REFERENCE_LINK@141..150 + 0: L_BRACK@141..142 "[" [] [] + 1: MD_INLINE_ITEM_LIST@142..149 + 0: MD_TEXTUAL@142..149 + 0: MD_TEXTUAL_LITERAL@142..149 "example" [] [] + 2: R_BRACK@149..150 "]" [] [] + 3: (empty) + 2: MD_TEXTUAL@150..151 + 0: MD_TEXTUAL_LITERAL@150..151 "\n" [] [] + 1: (empty) + 8: MD_NEWLINE@151..152 + 0: NEWLINE@151..152 "\n" [] [] + 9: MD_LINK_REFERENCE_DEFINITION@152..174 + 0: L_BRACK@152..153 "[" [] [] + 1: MD_LINK_LABEL@153..156 + 0: MD_INLINE_ITEM_LIST@153..156 + 0: MD_TEXTUAL@153..156 + 0: MD_TEXTUAL_LITERAL@153..156 "foo" [] [] + 2: R_BRACK@156..157 "]" [] [] + 3: COLON@157..158 ":" [] [] + 4: MD_LINK_DESTINATION@158..174 + 0: MD_INLINE_ITEM_LIST@158..174 + 0: MD_TEXTUAL@158..159 + 0: MD_TEXTUAL_LITERAL@158..159 " " [] [] + 1: MD_TEXTUAL@159..174 + 0: MD_TEXTUAL_LITERAL@159..174 "https://foo.com" [] [] + 5: (empty) + 10: MD_NEWLINE@174..175 + 0: NEWLINE@174..175 "\n" [] [] + 11: MD_NEWLINE@175..176 + 0: NEWLINE@175..176 "\n" [] [] + 12: MD_PARAGRAPH@176..205 + 0: MD_INLINE_ITEM_LIST@176..205 + 0: MD_TEXTUAL@176..188 + 0: MD_TEXTUAL_LITERAL@176..188 "Image full: " [] [] + 1: MD_REFERENCE_IMAGE@188..204 + 0: BANG@188..189 "!" [] [] + 1: L_BRACK@189..190 "[" [] [] + 2: MD_INLINE_ITEM_LIST@190..198 + 0: MD_TEXTUAL@190..198 + 0: MD_TEXTUAL_LITERAL@190..198 "alt text" [] [] + 3: R_BRACK@198..199 "]" [] [] + 4: MD_REFERENCE_LINK_LABEL@199..204 + 0: L_BRACK@199..200 "[" [] [] + 1: MD_INLINE_ITEM_LIST@200..203 + 0: MD_TEXTUAL@200..203 + 0: MD_TEXTUAL_LITERAL@200..203 "foo" [] [] + 2: R_BRACK@203..204 "]" [] [] + 2: MD_TEXTUAL@204..205 + 0: MD_TEXTUAL_LITERAL@204..205 "\n" [] [] + 1: (empty) + 13: MD_NEWLINE@205..206 + 0: NEWLINE@205..206 "\n" [] [] + 14: MD_PARAGRAPH@206..232 + 0: MD_INLINE_ITEM_LIST@206..232 + 0: MD_TEXTUAL@206..223 + 0: MD_TEXTUAL_LITERAL@206..223 "Image collapsed: " [] [] + 1: MD_REFERENCE_IMAGE@223..231 + 0: BANG@223..224 "!" [] [] + 1: L_BRACK@224..225 "[" [] [] + 2: MD_INLINE_ITEM_LIST@225..228 + 0: MD_TEXTUAL@225..228 + 0: MD_TEXTUAL_LITERAL@225..228 "foo" [] [] + 3: R_BRACK@228..229 "]" [] [] + 4: MD_REFERENCE_LINK_LABEL@229..231 + 0: L_BRACK@229..230 "[" [] [] + 1: MD_INLINE_ITEM_LIST@230..230 + 2: R_BRACK@230..231 "]" [] [] + 2: MD_TEXTUAL@231..232 + 0: MD_TEXTUAL_LITERAL@231..232 "\n" [] [] + 1: (empty) + 15: MD_NEWLINE@232..233 + 0: NEWLINE@232..233 "\n" [] [] + 16: MD_PARAGRAPH@233..256 + 0: MD_INLINE_ITEM_LIST@233..256 + 0: MD_TEXTUAL@233..249 + 0: MD_TEXTUAL_LITERAL@233..249 "Image shortcut: " [] [] + 1: MD_REFERENCE_IMAGE@249..255 + 0: BANG@249..250 "!" [] [] + 1: L_BRACK@250..251 "[" [] [] + 2: MD_INLINE_ITEM_LIST@251..254 + 0: MD_TEXTUAL@251..254 + 0: MD_TEXTUAL_LITERAL@251..254 "foo" [] [] + 3: R_BRACK@254..255 "]" [] [] + 4: (empty) + 2: MD_TEXTUAL@255..256 + 0: MD_TEXTUAL_LITERAL@255..256 "\n" [] [] + 1: (empty) + 17: MD_NEWLINE@256..257 + 0: NEWLINE@256..257 "\n" [] [] + 18: MD_PARAGRAPH@257..317 + 0: MD_INLINE_ITEM_LIST@257..317 + 0: MD_TEXTUAL@257..281 + 0: MD_TEXTUAL_LITERAL@257..281 "Multiple words in text: " [] [] + 1: MD_REFERENCE_LINK@281..316 + 0: L_BRACK@281..282 "[" [] [] + 1: MD_INLINE_ITEM_LIST@282..306 + 0: MD_TEXTUAL@282..306 + 0: MD_TEXTUAL_LITERAL@282..306 "click here for more info" [] [] + 2: R_BRACK@306..307 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@307..316 + 0: L_BRACK@307..308 "[" [] [] + 1: MD_INLINE_ITEM_LIST@308..315 + 0: MD_TEXTUAL@308..315 + 0: MD_TEXTUAL_LITERAL@308..315 "example" [] [] + 2: R_BRACK@315..316 "]" [] [] + 2: MD_TEXTUAL@316..317 + 0: MD_TEXTUAL_LITERAL@316..317 "\n" [] [] + 1: (empty) + 19: MD_NEWLINE@317..318 + 0: NEWLINE@317..318 "\n" [] [] + 20: MD_PARAGRAPH@318..352 + 0: MD_INLINE_ITEM_LIST@318..352 + 0: MD_TEXTUAL@318..330 + 0: MD_TEXTUAL_LITERAL@318..330 "Empty label " [] [] + 1: MD_TEXTUAL@330..331 + 0: MD_TEXTUAL_LITERAL@330..331 "(" [] [] + 2: MD_TEXTUAL@331..340 + 0: MD_TEXTUAL_LITERAL@331..340 "collapsed" [] [] + 3: MD_TEXTUAL@340..341 + 0: MD_TEXTUAL_LITERAL@340..341 ")" [] [] + 4: MD_TEXTUAL@341..342 + 0: MD_TEXTUAL_LITERAL@341..342 ":" [] [] + 5: MD_TEXTUAL@342..343 + 0: MD_TEXTUAL_LITERAL@342..343 " " [] [] + 6: MD_REFERENCE_LINK@343..351 + 0: L_BRACK@343..344 "[" [] [] + 1: MD_INLINE_ITEM_LIST@344..348 + 0: MD_TEXTUAL@344..348 + 0: MD_TEXTUAL_LITERAL@344..348 "test" [] [] + 2: R_BRACK@348..349 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@349..351 + 0: L_BRACK@349..350 "[" [] [] + 1: MD_INLINE_ITEM_LIST@350..350 + 2: R_BRACK@350..351 "]" [] [] + 7: MD_TEXTUAL@351..352 + 0: MD_TEXTUAL_LITERAL@351..352 "\n" [] [] + 1: (empty) + 21: MD_NEWLINE@352..353 + 0: NEWLINE@352..353 "\n" [] [] + 22: MD_LINK_REFERENCE_DEFINITION@353..377 + 0: L_BRACK@353..354 "[" [] [] + 1: MD_LINK_LABEL@354..358 + 0: MD_INLINE_ITEM_LIST@354..358 + 0: MD_TEXTUAL@354..358 + 0: MD_TEXTUAL_LITERAL@354..358 "test" [] [] + 2: R_BRACK@358..359 "]" [] [] + 3: COLON@359..360 ":" [] [] + 4: MD_LINK_DESTINATION@360..377 + 0: MD_INLINE_ITEM_LIST@360..377 + 0: MD_TEXTUAL@360..361 + 0: MD_TEXTUAL_LITERAL@360..361 " " [] [] + 1: MD_TEXTUAL@361..377 + 0: MD_TEXTUAL_LITERAL@361..377 "https://test.com" [] [] + 5: (empty) + 23: MD_NEWLINE@377..378 + 0: NEWLINE@377..378 "\n" [] [] + 24: MD_NEWLINE@378..379 + 0: NEWLINE@378..379 "\n" [] [] + 25: MD_PARAGRAPH@379..422 + 0: MD_INLINE_ITEM_LIST@379..422 + 0: MD_TEXTUAL@379..410 + 0: MD_TEXTUAL_LITERAL@379..410 "Shortcut that looks like text: " [] [] + 1: MD_TEXTUAL@410..411 + 0: MD_TEXTUAL_LITERAL@410..411 "[" [] [] + 2: MD_TEXTUAL@411..420 + 0: MD_TEXTUAL_LITERAL@411..420 "undefined" [] [] + 3: MD_TEXTUAL@420..421 + 0: MD_TEXTUAL_LITERAL@420..421 "]" [] [] + 4: MD_TEXTUAL@421..422 + 0: MD_TEXTUAL_LITERAL@421..422 "\n" [] [] + 1: (empty) + 26: MD_NEWLINE@422..423 + 0: NEWLINE@422..423 "\n" [] [] + 27: MD_PARAGRAPH@423..490 + 0: MD_INLINE_ITEM_LIST@423..490 + 0: MD_TEXTUAL@423..442 + 0: MD_TEXTUAL_LITERAL@423..442 "Mixed with inline: " [] [] + 1: MD_INLINE_LINK@442..470 + 0: L_BRACK@442..443 "[" [] [] + 1: MD_INLINE_ITEM_LIST@443..449 + 0: MD_TEXTUAL@443..449 + 0: MD_TEXTUAL_LITERAL@443..449 "inline" [] [] + 2: R_BRACK@449..450 "]" [] [] + 3: L_PAREN@450..451 "(" [] [] + 4: MD_INLINE_ITEM_LIST@451..469 + 0: MD_TEXTUAL@451..469 + 0: MD_TEXTUAL_LITERAL@451..469 "https://inline.com" [] [] + 5: (empty) + 6: R_PAREN@469..470 ")" [] [] + 2: MD_TEXTUAL@470..475 + 0: MD_TEXTUAL_LITERAL@470..475 " and " [] [] + 3: MD_REFERENCE_LINK@475..489 + 0: L_BRACK@475..476 "[" [] [] + 1: MD_INLINE_ITEM_LIST@476..479 + 0: MD_TEXTUAL@476..479 + 0: MD_TEXTUAL_LITERAL@476..479 "ref" [] [] + 2: R_BRACK@479..480 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@480..489 + 0: L_BRACK@480..481 "[" [] [] + 1: MD_INLINE_ITEM_LIST@481..488 + 0: MD_TEXTUAL@481..488 + 0: MD_TEXTUAL_LITERAL@481..488 "example" [] [] + 2: R_BRACK@488..489 "]" [] [] + 4: MD_TEXTUAL@489..490 + 0: MD_TEXTUAL_LITERAL@489..490 "\n" [] [] + 1: (empty) + 28: MD_NEWLINE@490..491 + 0: NEWLINE@490..491 "\n" [] [] + 29: MD_PARAGRAPH@491..571 + 0: MD_INLINE_ITEM_LIST@491..571 + 0: MD_TEXTUAL@491..537 + 0: MD_TEXTUAL_LITERAL@491..537 "Nested in paragraph: This is a paragraph with " [] [] + 1: MD_REFERENCE_LINK@537..555 + 0: L_BRACK@537..538 "[" [] [] + 1: MD_INLINE_ITEM_LIST@538..549 + 0: MD_TEXTUAL@538..549 + 0: MD_TEXTUAL_LITERAL@538..549 "a reference" [] [] + 2: R_BRACK@549..550 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@550..555 + 0: L_BRACK@550..551 "[" [] [] + 1: MD_INLINE_ITEM_LIST@551..554 + 0: MD_TEXTUAL@551..554 + 0: MD_TEXTUAL_LITERAL@551..554 "foo" [] [] + 2: R_BRACK@554..555 "]" [] [] + 2: MD_TEXTUAL@555..570 + 0: MD_TEXTUAL_LITERAL@555..570 " in the middle." [] [] + 3: MD_TEXTUAL@570..571 + 0: MD_TEXTUAL_LITERAL@570..571 "\n" [] [] + 1: (empty) + 30: MD_NEWLINE@571..572 + 0: NEWLINE@571..572 "\n" [] [] + 31: MD_LINK_REFERENCE_DEFINITION@572..606 + 0: L_BRACK@572..573 "[" [] [] + 1: MD_LINK_LABEL@573..583 + 0: MD_INLINE_ITEM_LIST@573..583 + 0: MD_TEXTUAL@573..583 + 0: MD_TEXTUAL_LITERAL@573..583 "Case Label" [] [] + 2: R_BRACK@583..584 "]" [] [] + 3: COLON@584..585 ":" [] [] + 4: MD_LINK_DESTINATION@585..606 + 0: MD_INLINE_ITEM_LIST@585..606 + 0: MD_TEXTUAL@585..586 + 0: MD_TEXTUAL_LITERAL@585..586 " " [] [] + 1: MD_TEXTUAL@586..606 + 0: MD_TEXTUAL_LITERAL@586..606 "https://case.example" [] [] + 5: (empty) + 32: MD_NEWLINE@606..607 + 0: NEWLINE@606..607 "\n" [] [] + 33: MD_NEWLINE@607..608 + 0: NEWLINE@607..608 "\n" [] [] + 34: MD_PARAGRAPH@608..639 + 0: MD_INLINE_ITEM_LIST@608..639 + 0: MD_TEXTUAL@608..612 + 0: MD_TEXTUAL_LITERAL@608..612 "Case" [] [] + 1: MD_TEXTUAL@612..613 + 0: MD_TEXTUAL_LITERAL@612..613 "-" [] [] + 2: MD_TEXTUAL@613..626 + 0: MD_TEXTUAL_LITERAL@613..626 "insensitive: " [] [] + 3: MD_REFERENCE_LINK@626..638 + 0: L_BRACK@626..627 "[" [] [] + 1: MD_INLINE_ITEM_LIST@627..637 + 0: MD_TEXTUAL@627..637 + 0: MD_TEXTUAL_LITERAL@627..637 "case label" [] [] + 2: R_BRACK@637..638 "]" [] [] + 3: (empty) + 4: MD_TEXTUAL@638..639 + 0: MD_TEXTUAL_LITERAL@638..639 "\n" [] [] + 1: (empty) + 35: MD_NEWLINE@639..640 + 0: NEWLINE@639..640 "\n" [] [] + 36: MD_PARAGRAPH@640..678 + 0: MD_INLINE_ITEM_LIST@640..678 + 0: MD_TEXTUAL@640..663 + 0: MD_TEXTUAL_LITERAL@640..663 "Whitespace normalized: " [] [] + 1: MD_REFERENCE_LINK@663..677 + 0: L_BRACK@663..664 "[" [] [] + 1: MD_INLINE_ITEM_LIST@664..676 + 0: MD_TEXTUAL@664..676 + 0: MD_TEXTUAL_LITERAL@664..676 "case label" [] [] + 2: R_BRACK@676..677 "]" [] [] + 3: (empty) + 2: MD_TEXTUAL@677..678 + 0: MD_TEXTUAL_LITERAL@677..678 "\n" [] [] + 1: (empty) + 37: MD_NEWLINE@678..679 + 0: NEWLINE@678..679 "\n" [] [] + 38: MD_LINK_REFERENCE_DEFINITION@679..713 + 0: L_BRACK@679..680 "[" [] [] + 1: MD_LINK_LABEL@680..687 + 0: MD_INLINE_ITEM_LIST@680..687 + 0: MD_TEXTUAL@680..685 + 0: MD_TEXTUAL_LITERAL@680..685 "label" [] [] + 1: MD_TEXTUAL@685..687 + 0: MD_TEXTUAL_LITERAL@685..687 "\\]" [] [] + 2: R_BRACK@687..688 "]" [] [] + 3: COLON@688..689 ":" [] [] + 4: MD_LINK_DESTINATION@689..713 + 0: MD_INLINE_ITEM_LIST@689..713 + 0: MD_TEXTUAL@689..690 + 0: MD_TEXTUAL_LITERAL@689..690 " " [] [] + 1: MD_TEXTUAL@690..713 + 0: MD_TEXTUAL_LITERAL@690..713 "https://escaped.example" [] [] + 5: (empty) + 39: MD_NEWLINE@713..714 + 0: NEWLINE@713..714 "\n" [] [] + 40: MD_NEWLINE@714..715 + 0: NEWLINE@714..715 "\n" [] [] + 41: MD_PARAGRAPH@715..757 + 0: MD_INLINE_ITEM_LIST@715..757 + 0: MD_TEXTUAL@715..741 + 0: MD_TEXTUAL_LITERAL@715..741 "Escaped bracket in label: " [] [] + 1: MD_REFERENCE_LINK@741..756 + 0: L_BRACK@741..742 "[" [] [] + 1: MD_INLINE_ITEM_LIST@742..746 + 0: MD_TEXTUAL@742..746 + 0: MD_TEXTUAL_LITERAL@742..746 "text" [] [] + 2: R_BRACK@746..747 "]" [] [] + 3: MD_REFERENCE_LINK_LABEL@747..756 + 0: L_BRACK@747..748 "[" [] [] + 1: MD_INLINE_ITEM_LIST@748..755 + 0: MD_TEXTUAL@748..753 + 0: MD_TEXTUAL_LITERAL@748..753 "label" [] [] + 1: MD_TEXTUAL@753..755 + 0: MD_TEXTUAL_LITERAL@753..755 "\\]" [] [] + 2: R_BRACK@755..756 "]" [] [] + 2: MD_TEXTUAL@756..757 + 0: MD_TEXTUAL_LITERAL@756..757 "\n" [] [] + 1: (empty) + 2: EOF@757..757 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading.md new file mode 100644 index 000000000000..db3f7fcd82ed --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading.md @@ -0,0 +1,15 @@ +Heading 1 +========= + +Heading 2 +--------- + +Another H1 +=== + +Another H2 +--- + +Multi-line content +that spans lines +================ diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading.md.snap new file mode 100644 index 000000000000..c287fd716d41 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading.md.snap @@ -0,0 +1,202 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Heading 1 +========= + +Heading 2 +--------- + +Another H1 +=== + +Another H2 +--- + +Multi-line content +that spans lines +================ + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "Heading 1" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@10..19 "=========" [] [], + }, + MdNewline { + value_token: NEWLINE@19..20 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@20..21 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@21..30 "Heading 2" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@31..40 "---------" [] [], + }, + MdNewline { + value_token: NEWLINE@40..41 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@41..42 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@42..52 "Another H1" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@52..53 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@53..56 "===" [] [], + }, + MdNewline { + value_token: NEWLINE@56..57 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@57..58 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@58..68 "Another H2" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@68..69 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@69..72 "---" [] [], + }, + MdNewline { + value_token: NEWLINE@72..73 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@73..74 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@74..79 "Multi" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@79..80 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@80..92 "line content" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@92..93 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@93..109 "that spans lines" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@109..110 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@110..126 "================" [] [], + }, + MdNewline { + value_token: NEWLINE@126..127 "\n" [] [], + }, + ], + eof_token: EOF@127..127 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..127 + 0: (empty) + 1: MD_BLOCK_LIST@0..127 + 0: MD_SETEXT_HEADER@0..19 + 0: MD_INLINE_ITEM_LIST@0..10 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "Heading 1" [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@10..19 "=========" [] [] + 1: MD_NEWLINE@19..20 + 0: NEWLINE@19..20 "\n" [] [] + 2: MD_NEWLINE@20..21 + 0: NEWLINE@20..21 "\n" [] [] + 3: MD_SETEXT_HEADER@21..40 + 0: MD_INLINE_ITEM_LIST@21..31 + 0: MD_TEXTUAL@21..30 + 0: MD_TEXTUAL_LITERAL@21..30 "Heading 2" [] [] + 1: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@31..40 "---------" [] [] + 4: MD_NEWLINE@40..41 + 0: NEWLINE@40..41 "\n" [] [] + 5: MD_NEWLINE@41..42 + 0: NEWLINE@41..42 "\n" [] [] + 6: MD_SETEXT_HEADER@42..56 + 0: MD_INLINE_ITEM_LIST@42..53 + 0: MD_TEXTUAL@42..52 + 0: MD_TEXTUAL_LITERAL@42..52 "Another H1" [] [] + 1: MD_TEXTUAL@52..53 + 0: MD_TEXTUAL_LITERAL@52..53 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@53..56 "===" [] [] + 7: MD_NEWLINE@56..57 + 0: NEWLINE@56..57 "\n" [] [] + 8: MD_NEWLINE@57..58 + 0: NEWLINE@57..58 "\n" [] [] + 9: MD_SETEXT_HEADER@58..72 + 0: MD_INLINE_ITEM_LIST@58..69 + 0: MD_TEXTUAL@58..68 + 0: MD_TEXTUAL_LITERAL@58..68 "Another H2" [] [] + 1: MD_TEXTUAL@68..69 + 0: MD_TEXTUAL_LITERAL@68..69 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@69..72 "---" [] [] + 10: MD_NEWLINE@72..73 + 0: NEWLINE@72..73 "\n" [] [] + 11: MD_NEWLINE@73..74 + 0: NEWLINE@73..74 "\n" [] [] + 12: MD_SETEXT_HEADER@74..126 + 0: MD_INLINE_ITEM_LIST@74..110 + 0: MD_TEXTUAL@74..79 + 0: MD_TEXTUAL_LITERAL@74..79 "Multi" [] [] + 1: MD_TEXTUAL@79..80 + 0: MD_TEXTUAL_LITERAL@79..80 "-" [] [] + 2: MD_TEXTUAL@80..92 + 0: MD_TEXTUAL_LITERAL@80..92 "line content" [] [] + 3: MD_TEXTUAL@92..93 + 0: MD_TEXTUAL_LITERAL@92..93 "\n" [] [] + 4: MD_TEXTUAL@93..109 + 0: MD_TEXTUAL_LITERAL@93..109 "that spans lines" [] [] + 5: MD_TEXTUAL@109..110 + 0: MD_TEXTUAL_LITERAL@109..110 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@110..126 "================" [] [] + 13: MD_NEWLINE@126..127 + 0: NEWLINE@126..127 "\n" [] [] + 2: EOF@127..127 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_edge_cases.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_edge_cases.md new file mode 100644 index 000000000000..65ad27b5b991 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_edge_cases.md @@ -0,0 +1,15 @@ +Foo + ---- + +Foo +----- + +Foo\ +---- + +> Foo +> --- + +- Foo + --- + baz diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_edge_cases.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_edge_cases.md.snap new file mode 100644 index 000000000000..f5ddc589205c --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_edge_cases.md.snap @@ -0,0 +1,237 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Foo + ---- + +Foo +----- + +Foo\ +---- + +> Foo +> --- + +- Foo + --- + baz + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..3 "Foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@3..4 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@4..11 "----" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@11..12 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@12..13 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..16 "Foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..17 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@17..22 "-----" [] [], + }, + MdNewline { + value_token: NEWLINE@22..23 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@23..24 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..27 "Foo" [] [], + }, + MdHardLine { + value_token: MD_HARD_LINE_LITERAL@27..29 "\\\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@29..33 "----" [] [], + }, + MdNewline { + value_token: NEWLINE@33..34 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@34..35 "\n" [] [], + }, + MdQuote { + marker_token: R_ANGLE@35..36 ">" [] [], + content: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@36..40 "Foo" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@40..41 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@41..44 "-" [Skipped(">"), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@44..45 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@45..46 "-" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@46..47 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + MdNewline { + value_token: NEWLINE@47..48 "\n" [] [], + }, + MdBulletListItem { + md_bullet_list: MdBulletList [ + MdBullet { + bullet: MINUS@48..49 "-" [] [], + content: MdBlockList [ + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@49..53 "Foo" [Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@53..54 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@54..59 "---" [Skipped(" "), Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@59..60 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@60..65 "baz" [Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@65..66 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + }, + ], + }, + ], + eof_token: EOF@66..66 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..66 + 0: (empty) + 1: MD_BLOCK_LIST@0..66 + 0: MD_SETEXT_HEADER@0..11 + 0: MD_INLINE_ITEM_LIST@0..4 + 0: MD_TEXTUAL@0..3 + 0: MD_TEXTUAL_LITERAL@0..3 "Foo" [] [] + 1: MD_TEXTUAL@3..4 + 0: MD_TEXTUAL_LITERAL@3..4 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@4..11 "----" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_NEWLINE@11..12 + 0: NEWLINE@11..12 "\n" [] [] + 2: MD_NEWLINE@12..13 + 0: NEWLINE@12..13 "\n" [] [] + 3: MD_SETEXT_HEADER@13..22 + 0: MD_INLINE_ITEM_LIST@13..17 + 0: MD_TEXTUAL@13..16 + 0: MD_TEXTUAL_LITERAL@13..16 "Foo" [] [] + 1: MD_TEXTUAL@16..17 + 0: MD_TEXTUAL_LITERAL@16..17 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@17..22 "-----" [] [] + 4: MD_NEWLINE@22..23 + 0: NEWLINE@22..23 "\n" [] [] + 5: MD_NEWLINE@23..24 + 0: NEWLINE@23..24 "\n" [] [] + 6: MD_SETEXT_HEADER@24..33 + 0: MD_INLINE_ITEM_LIST@24..29 + 0: MD_TEXTUAL@24..27 + 0: MD_TEXTUAL_LITERAL@24..27 "Foo" [] [] + 1: MD_HARD_LINE@27..29 + 0: MD_HARD_LINE_LITERAL@27..29 "\\\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@29..33 "----" [] [] + 7: MD_NEWLINE@33..34 + 0: NEWLINE@33..34 "\n" [] [] + 8: MD_NEWLINE@34..35 + 0: NEWLINE@34..35 "\n" [] [] + 9: MD_QUOTE@35..47 + 0: R_ANGLE@35..36 ">" [] [] + 1: MD_BLOCK_LIST@36..47 + 0: MD_PARAGRAPH@36..47 + 0: MD_INLINE_ITEM_LIST@36..47 + 0: MD_TEXTUAL@36..40 + 0: MD_TEXTUAL_LITERAL@36..40 "Foo" [Skipped(" ")] [] + 1: MD_TEXTUAL@40..41 + 0: MD_TEXTUAL_LITERAL@40..41 "\n" [] [] + 2: MD_TEXTUAL@41..44 + 0: MD_TEXTUAL_LITERAL@41..44 "-" [Skipped(">"), Skipped(" ")] [] + 3: MD_TEXTUAL@44..45 + 0: MD_TEXTUAL_LITERAL@44..45 "-" [] [] + 4: MD_TEXTUAL@45..46 + 0: MD_TEXTUAL_LITERAL@45..46 "-" [] [] + 5: MD_TEXTUAL@46..47 + 0: MD_TEXTUAL_LITERAL@46..47 "\n" [] [] + 1: (empty) + 10: MD_NEWLINE@47..48 + 0: NEWLINE@47..48 "\n" [] [] + 11: MD_BULLET_LIST_ITEM@48..66 + 0: MD_BULLET_LIST@48..66 + 0: MD_BULLET@48..66 + 0: MINUS@48..49 "-" [] [] + 1: MD_BLOCK_LIST@49..66 + 0: MD_SETEXT_HEADER@49..59 + 0: MD_INLINE_ITEM_LIST@49..54 + 0: MD_TEXTUAL@49..53 + 0: MD_TEXTUAL_LITERAL@49..53 "Foo" [Skipped(" ")] [] + 1: MD_TEXTUAL@53..54 + 0: MD_TEXTUAL_LITERAL@53..54 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@54..59 "---" [Skipped(" "), Skipped(" ")] [] + 1: MD_NEWLINE@59..60 + 0: NEWLINE@59..60 "\n" [] [] + 2: MD_PARAGRAPH@60..66 + 0: MD_INLINE_ITEM_LIST@60..66 + 0: MD_TEXTUAL@60..65 + 0: MD_TEXTUAL_LITERAL@60..65 "baz" [Skipped(" "), Skipped(" ")] [] + 1: MD_TEXTUAL@65..66 + 0: MD_TEXTUAL_LITERAL@65..66 "\n" [] [] + 1: (empty) + 2: EOF@66..66 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_negative.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_negative.md new file mode 100644 index 000000000000..2c1bcf55a4bc --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_negative.md @@ -0,0 +1,13 @@ +Foo += = + +Foo + --- + +`Foo +---- +` + + diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_negative.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_negative.md.snap new file mode 100644 index 000000000000..5d0a9c53db49 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/setext_heading_negative.md.snap @@ -0,0 +1,213 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +Foo += = + +Foo + --- + +`Foo +---- +` + + + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..3 "Foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@3..4 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@4..7 "= =" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@7..8 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@8..9 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..12 "Foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@12..13 "\n" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@13..20 "---" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@20..21 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@21..22 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@22..23 "`" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..26 "Foo" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@26..27 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@27..31 "----" [] [], + }, + MdNewline { + value_token: NEWLINE@31..32 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@32..33 "`" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@33..34 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + MdNewline { + value_token: NEWLINE@34..35 "\n" [] [], + }, + MdSetextHeader { + content: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@35..36 "<" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@36..50 "a title=\"a lot" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@50..51 "\n" [] [], + }, + ], + underline_token: MD_SETEXT_UNDERLINE_LITERAL@51..54 "---" [] [], + }, + MdNewline { + value_token: NEWLINE@54..55 "\n" [] [], + }, + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@55..66 "of dashes\"/" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@66..67 ">" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@67..68 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@68..68 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..68 + 0: (empty) + 1: MD_BLOCK_LIST@0..68 + 0: MD_PARAGRAPH@0..8 + 0: MD_INLINE_ITEM_LIST@0..8 + 0: MD_TEXTUAL@0..3 + 0: MD_TEXTUAL_LITERAL@0..3 "Foo" [] [] + 1: MD_TEXTUAL@3..4 + 0: MD_TEXTUAL_LITERAL@3..4 "\n" [] [] + 2: MD_TEXTUAL@4..7 + 0: MD_TEXTUAL_LITERAL@4..7 "= =" [] [] + 3: MD_TEXTUAL@7..8 + 0: MD_TEXTUAL_LITERAL@7..8 "\n" [] [] + 1: (empty) + 1: MD_NEWLINE@8..9 + 0: NEWLINE@8..9 "\n" [] [] + 2: MD_PARAGRAPH@9..21 + 0: MD_INLINE_ITEM_LIST@9..21 + 0: MD_TEXTUAL@9..12 + 0: MD_TEXTUAL_LITERAL@9..12 "Foo" [] [] + 1: MD_TEXTUAL@12..13 + 0: MD_TEXTUAL_LITERAL@12..13 "\n" [] [] + 2: MD_TEXTUAL@13..20 + 0: MD_TEXTUAL_LITERAL@13..20 "---" [Skipped(" "), Skipped(" "), Skipped(" "), Skipped(" ")] [] + 3: MD_TEXTUAL@20..21 + 0: MD_TEXTUAL_LITERAL@20..21 "\n" [] [] + 1: (empty) + 3: MD_NEWLINE@21..22 + 0: NEWLINE@21..22 "\n" [] [] + 4: MD_SETEXT_HEADER@22..31 + 0: MD_INLINE_ITEM_LIST@22..27 + 0: MD_TEXTUAL@22..23 + 0: MD_TEXTUAL_LITERAL@22..23 "`" [] [] + 1: MD_TEXTUAL@23..26 + 0: MD_TEXTUAL_LITERAL@23..26 "Foo" [] [] + 2: MD_TEXTUAL@26..27 + 0: MD_TEXTUAL_LITERAL@26..27 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@27..31 "----" [] [] + 5: MD_NEWLINE@31..32 + 0: NEWLINE@31..32 "\n" [] [] + 6: MD_PARAGRAPH@32..34 + 0: MD_INLINE_ITEM_LIST@32..34 + 0: MD_TEXTUAL@32..33 + 0: MD_TEXTUAL_LITERAL@32..33 "`" [] [] + 1: MD_TEXTUAL@33..34 + 0: MD_TEXTUAL_LITERAL@33..34 "\n" [] [] + 1: (empty) + 7: MD_NEWLINE@34..35 + 0: NEWLINE@34..35 "\n" [] [] + 8: MD_SETEXT_HEADER@35..54 + 0: MD_INLINE_ITEM_LIST@35..51 + 0: MD_TEXTUAL@35..36 + 0: MD_TEXTUAL_LITERAL@35..36 "<" [] [] + 1: MD_TEXTUAL@36..50 + 0: MD_TEXTUAL_LITERAL@36..50 "a title=\"a lot" [] [] + 2: MD_TEXTUAL@50..51 + 0: MD_TEXTUAL_LITERAL@50..51 "\n" [] [] + 1: MD_SETEXT_UNDERLINE_LITERAL@51..54 "---" [] [] + 9: MD_NEWLINE@54..55 + 0: NEWLINE@54..55 "\n" [] [] + 10: MD_PARAGRAPH@55..68 + 0: MD_INLINE_ITEM_LIST@55..68 + 0: MD_TEXTUAL@55..66 + 0: MD_TEXTUAL_LITERAL@55..66 "of dashes\"/" [] [] + 1: MD_TEXTUAL@66..67 + 0: MD_TEXTUAL_LITERAL@66..67 ">" [] [] + 2: MD_TEXTUAL@67..68 + 0: MD_TEXTUAL_LITERAL@67..68 "\n" [] [] + 1: (empty) + 2: EOF@68..68 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/thematic_break_block.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/thematic_break_block.md.snap index ed839fc78381..f83e7aa6746c 100644 --- a/crates/biome_markdown_parser/tests/md_test_suite/ok/thematic_break_block.md.snap +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/thematic_break_block.md.snap @@ -2,6 +2,7 @@ source: crates/biome_markdown_parser/tests/spec_test.rs expression: snapshot --- + ## Input ``` @@ -23,22 +24,43 @@ MdDocument { bom_token: missing (optional), value: MdBlockList [ MdThematicBreakBlock { - value_token: MD_THEMATIC_BREAK_LITERAL@0..6 "***" [Whitespace(" ")] [], + value_token: MD_THEMATIC_BREAK_LITERAL@0..6 "***" [Skipped(" "), Skipped(" "), Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@6..7 "\n" [] [], }, MdThematicBreakBlock { - value_token: MD_THEMATIC_BREAK_LITERAL@6..11 "***" [Newline("\n"), Whitespace(" ")] [], + value_token: MD_THEMATIC_BREAK_LITERAL@7..11 "***" [Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@11..12 "\n" [] [], }, MdThematicBreakBlock { - value_token: MD_THEMATIC_BREAK_LITERAL@11..18 "- - -" [Newline("\n"), Whitespace(" ")] [], + value_token: MD_THEMATIC_BREAK_LITERAL@12..18 "- - -" [Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@18..19 "\n" [] [], }, MdThematicBreakBlock { - value_token: MD_THEMATIC_BREAK_LITERAL@18..22 "___" [Newline("\n")] [], + value_token: MD_THEMATIC_BREAK_LITERAL@19..22 "___" [] [], + }, + MdNewline { + value_token: NEWLINE@22..23 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@23..24 "\n" [] [], }, MdThematicBreakBlock { - value_token: MD_THEMATIC_BREAK_LITERAL@22..30 "_ _ _" [Newline("\n"), Newline("\n"), Whitespace(" ")] [], + value_token: MD_THEMATIC_BREAK_LITERAL@24..30 "_ _ _" [Skipped(" ")] [], + }, + MdNewline { + value_token: NEWLINE@30..31 "\n" [] [], + }, + MdNewline { + value_token: NEWLINE@31..32 "\n" [] [], }, MdThematicBreakBlock { - value_token: MD_THEMATIC_BREAK_LITERAL@30..37 "* * *" [Newline("\n"), Newline("\n")] [], + value_token: MD_THEMATIC_BREAK_LITERAL@32..37 "* * *" [] [], }, ], eof_token: EOF@37..37 "" [] [], @@ -52,17 +74,31 @@ MdDocument { 0: (empty) 1: MD_BLOCK_LIST@0..37 0: MD_THEMATIC_BREAK_BLOCK@0..6 - 0: MD_THEMATIC_BREAK_LITERAL@0..6 "***" [Whitespace(" ")] [] - 1: MD_THEMATIC_BREAK_BLOCK@6..11 - 0: MD_THEMATIC_BREAK_LITERAL@6..11 "***" [Newline("\n"), Whitespace(" ")] [] - 2: MD_THEMATIC_BREAK_BLOCK@11..18 - 0: MD_THEMATIC_BREAK_LITERAL@11..18 "- - -" [Newline("\n"), Whitespace(" ")] [] - 3: MD_THEMATIC_BREAK_BLOCK@18..22 - 0: MD_THEMATIC_BREAK_LITERAL@18..22 "___" [Newline("\n")] [] - 4: MD_THEMATIC_BREAK_BLOCK@22..30 - 0: MD_THEMATIC_BREAK_LITERAL@22..30 "_ _ _" [Newline("\n"), Newline("\n"), Whitespace(" ")] [] - 5: MD_THEMATIC_BREAK_BLOCK@30..37 - 0: MD_THEMATIC_BREAK_LITERAL@30..37 "* * *" [Newline("\n"), Newline("\n")] [] + 0: MD_THEMATIC_BREAK_LITERAL@0..6 "***" [Skipped(" "), Skipped(" "), Skipped(" ")] [] + 1: MD_NEWLINE@6..7 + 0: NEWLINE@6..7 "\n" [] [] + 2: MD_THEMATIC_BREAK_BLOCK@7..11 + 0: MD_THEMATIC_BREAK_LITERAL@7..11 "***" [Skipped(" ")] [] + 3: MD_NEWLINE@11..12 + 0: NEWLINE@11..12 "\n" [] [] + 4: MD_THEMATIC_BREAK_BLOCK@12..18 + 0: MD_THEMATIC_BREAK_LITERAL@12..18 "- - -" [Skipped(" ")] [] + 5: MD_NEWLINE@18..19 + 0: NEWLINE@18..19 "\n" [] [] + 6: MD_THEMATIC_BREAK_BLOCK@19..22 + 0: MD_THEMATIC_BREAK_LITERAL@19..22 "___" [] [] + 7: MD_NEWLINE@22..23 + 0: NEWLINE@22..23 "\n" [] [] + 8: MD_NEWLINE@23..24 + 0: NEWLINE@23..24 "\n" [] [] + 9: MD_THEMATIC_BREAK_BLOCK@24..30 + 0: MD_THEMATIC_BREAK_LITERAL@24..30 "_ _ _" [Skipped(" ")] [] + 10: MD_NEWLINE@30..31 + 0: NEWLINE@30..31 "\n" [] [] + 11: MD_NEWLINE@31..32 + 0: NEWLINE@31..32 "\n" [] [] + 12: MD_THEMATIC_BREAK_BLOCK@32..37 + 0: MD_THEMATIC_BREAK_LITERAL@32..37 "* * *" [] [] 2: EOF@37..37 "" [] [] ``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_bold.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_bold.md new file mode 100644 index 000000000000..89ec5e79be43 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_bold.md @@ -0,0 +1 @@ +This has **unclosed bold diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_bold.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_bold.md.snap new file mode 100644 index 000000000000..73b7f6775931 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_bold.md.snap @@ -0,0 +1,66 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has **unclosed bold + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..11 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@11..24 "unclosed bold" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@24..25 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@25..25 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..25 + 0: (empty) + 1: MD_BLOCK_LIST@0..25 + 0: MD_PARAGRAPH@0..25 + 0: MD_INLINE_ITEM_LIST@0..25 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "*" [] [] + 2: MD_TEXTUAL@10..11 + 0: MD_TEXTUAL_LITERAL@10..11 "*" [] [] + 3: MD_TEXTUAL@11..24 + 0: MD_TEXTUAL_LITERAL@11..24 "unclosed bold" [] [] + 4: MD_TEXTUAL@24..25 + 0: MD_TEXTUAL_LITERAL@24..25 "\n" [] [] + 1: (empty) + 2: EOF@25..25 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_code_span.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_code_span.md new file mode 100644 index 000000000000..ec06cc66f72d --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_code_span.md @@ -0,0 +1 @@ +This has `unclosed code diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_code_span.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_code_span.md.snap new file mode 100644 index 000000000000..c3dba3556804 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_code_span.md.snap @@ -0,0 +1,61 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has `unclosed code + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "`" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..23 "unclosed code" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..24 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@24..24 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..24 + 0: (empty) + 1: MD_BLOCK_LIST@0..24 + 0: MD_PARAGRAPH@0..24 + 0: MD_INLINE_ITEM_LIST@0..24 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "`" [] [] + 2: MD_TEXTUAL@10..23 + 0: MD_TEXTUAL_LITERAL@10..23 "unclosed code" [] [] + 3: MD_TEXTUAL@23..24 + 0: MD_TEXTUAL_LITERAL@23..24 "\n" [] [] + 1: (empty) + 2: EOF@24..24 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_emphasis.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_emphasis.md new file mode 100644 index 000000000000..91297cb3bd0d --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_emphasis.md @@ -0,0 +1 @@ +This has *unclosed emphasis diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_emphasis.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_emphasis.md.snap new file mode 100644 index 000000000000..774784707675 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_emphasis.md.snap @@ -0,0 +1,61 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has *unclosed emphasis + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "*" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..27 "unclosed emphasis" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@27..28 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@28..28 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..28 + 0: (empty) + 1: MD_BLOCK_LIST@0..28 + 0: MD_PARAGRAPH@0..28 + 0: MD_INLINE_ITEM_LIST@0..28 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "*" [] [] + 2: MD_TEXTUAL@10..27 + 0: MD_TEXTUAL_LITERAL@10..27 "unclosed emphasis" [] [] + 3: MD_TEXTUAL@27..28 + 0: MD_TEXTUAL_LITERAL@27..28 "\n" [] [] + 1: (empty) + 2: EOF@28..28 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_image.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_image.md new file mode 100644 index 000000000000..eb686c5bb932 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_image.md @@ -0,0 +1 @@ +This has ![unclosed image diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_image.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_image.md.snap new file mode 100644 index 000000000000..66b4c340faef --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_image.md.snap @@ -0,0 +1,66 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has ![unclosed image + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..11 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@11..25 "unclosed image" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@25..26 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@26..26 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..26 + 0: (empty) + 1: MD_BLOCK_LIST@0..26 + 0: MD_PARAGRAPH@0..26 + 0: MD_INLINE_ITEM_LIST@0..26 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "!" [] [] + 2: MD_TEXTUAL@10..11 + 0: MD_TEXTUAL_LITERAL@10..11 "[" [] [] + 3: MD_TEXTUAL@11..25 + 0: MD_TEXTUAL_LITERAL@11..25 "unclosed image" [] [] + 4: MD_TEXTUAL@25..26 + 0: MD_TEXTUAL_LITERAL@25..26 "\n" [] [] + 1: (empty) + 2: EOF@26..26 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_link.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_link.md new file mode 100644 index 000000000000..91bfd8656e0d --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_link.md @@ -0,0 +1 @@ +This has [unclosed link diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_link.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_link.md.snap new file mode 100644 index 000000000000..5889b9c5a072 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_link.md.snap @@ -0,0 +1,61 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has [unclosed link + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..23 "unclosed link" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@23..24 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@24..24 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..24 + 0: (empty) + 1: MD_BLOCK_LIST@0..24 + 0: MD_PARAGRAPH@0..24 + 0: MD_INLINE_ITEM_LIST@0..24 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "[" [] [] + 2: MD_TEXTUAL@10..23 + 0: MD_TEXTUAL_LITERAL@10..23 "unclosed link" [] [] + 3: MD_TEXTUAL@23..24 + 0: MD_TEXTUAL_LITERAL@23..24 "\n" [] [] + 1: (empty) + 2: EOF@24..24 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_image_label.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_image_label.md new file mode 100644 index 000000000000..28913e5cc8c0 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_image_label.md @@ -0,0 +1 @@ +This has ![alt][unclosed label diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_image_label.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_image_label.md.snap new file mode 100644 index 000000000000..baf2a4b27f71 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_image_label.md.snap @@ -0,0 +1,81 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has ![alt][unclosed label + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "!" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..11 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@11..14 "alt" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..15 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..16 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..30 "unclosed label" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@31..31 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..31 + 0: (empty) + 1: MD_BLOCK_LIST@0..31 + 0: MD_PARAGRAPH@0..31 + 0: MD_INLINE_ITEM_LIST@0..31 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "!" [] [] + 2: MD_TEXTUAL@10..11 + 0: MD_TEXTUAL_LITERAL@10..11 "[" [] [] + 3: MD_TEXTUAL@11..14 + 0: MD_TEXTUAL_LITERAL@11..14 "alt" [] [] + 4: MD_TEXTUAL@14..15 + 0: MD_TEXTUAL_LITERAL@14..15 "]" [] [] + 5: MD_TEXTUAL@15..16 + 0: MD_TEXTUAL_LITERAL@15..16 "[" [] [] + 6: MD_TEXTUAL@16..30 + 0: MD_TEXTUAL_LITERAL@16..30 "unclosed label" [] [] + 7: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 "\n" [] [] + 1: (empty) + 2: EOF@31..31 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_link_label.md b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_link_label.md new file mode 100644 index 000000000000..3aaa9c5bf1fd --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_link_label.md @@ -0,0 +1 @@ +This has [text][unclosed label diff --git a/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_link_label.md.snap b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_link_label.md.snap new file mode 100644 index 000000000000..67dcb93d1640 --- /dev/null +++ b/crates/biome_markdown_parser/tests/md_test_suite/ok/unclosed_reference_link_label.md.snap @@ -0,0 +1,76 @@ +--- +source: crates/biome_markdown_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +``` +This has [text][unclosed label + +``` + + +## AST + +``` +MdDocument { + bom_token: missing (optional), + value: MdBlockList [ + MdParagraph { + list: MdInlineItemList [ + MdTextual { + value_token: MD_TEXTUAL_LITERAL@0..9 "This has " [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@9..10 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@10..14 "text" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@14..15 "]" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@15..16 "[" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@16..30 "unclosed label" [] [], + }, + MdTextual { + value_token: MD_TEXTUAL_LITERAL@30..31 "\n" [] [], + }, + ], + hard_line: missing (optional), + }, + ], + eof_token: EOF@31..31 "" [] [], +} +``` + +## CST + +``` +0: MD_DOCUMENT@0..31 + 0: (empty) + 1: MD_BLOCK_LIST@0..31 + 0: MD_PARAGRAPH@0..31 + 0: MD_INLINE_ITEM_LIST@0..31 + 0: MD_TEXTUAL@0..9 + 0: MD_TEXTUAL_LITERAL@0..9 "This has " [] [] + 1: MD_TEXTUAL@9..10 + 0: MD_TEXTUAL_LITERAL@9..10 "[" [] [] + 2: MD_TEXTUAL@10..14 + 0: MD_TEXTUAL_LITERAL@10..14 "text" [] [] + 3: MD_TEXTUAL@14..15 + 0: MD_TEXTUAL_LITERAL@14..15 "]" [] [] + 4: MD_TEXTUAL@15..16 + 0: MD_TEXTUAL_LITERAL@15..16 "[" [] [] + 5: MD_TEXTUAL@16..30 + 0: MD_TEXTUAL_LITERAL@16..30 "unclosed label" [] [] + 6: MD_TEXTUAL@30..31 + 0: MD_TEXTUAL_LITERAL@30..31 "\n" [] [] + 1: (empty) + 2: EOF@31..31 "" [] [] + +``` diff --git a/crates/biome_markdown_parser/tests/spec_test.rs b/crates/biome_markdown_parser/tests/spec_test.rs index dee024b5e7e8..796833429571 100644 --- a/crates/biome_markdown_parser/tests/spec_test.rs +++ b/crates/biome_markdown_parser/tests/spec_test.rs @@ -132,17 +132,65 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ }); } -#[ignore] #[test] pub fn quick_test() { - let code = r#" -your test code -"#; - - let root = parse_markdown(code); - let syntax = root.syntax(); - dbg!(&syntax, root.diagnostics(), root.has_errors()); - if has_bogus_nodes_or_empty_slots(&syntax) { - panic!("modified tree has bogus nodes or empty slots:\n{syntax:#?} \n\n {syntax}") + use biome_markdown_parser::document_to_html; + use biome_markdown_syntax::MdDocument; + use biome_rowan::AstNode; + + fn test_example(num: u32, input: &str, expected: &str) { + let root = parse_markdown(input); + let doc = MdDocument::cast(root.syntax()) + .unwrap_or_else(|| panic!("Example {:03}: parse failed", num)); + let html = document_to_html( + &doc, + root.list_tightness(), + root.list_item_indents(), + root.quote_indents(), + ); + + assert_eq!(expected, html, "Example {:03} failed", num); } + + test_example( + 7, + "-\t\tfoo\n", + "
      \n
    • \n
        foo\n
      \n
    • \n
    \n", + ); + test_example( + 42, + "- `one\n- two`\n", + "
      \n
    • `one
    • \n
    • two`
    • \n
    \n", + ); + test_example( + 61, + "- Foo\n- * * *\n", + "
      \n
    • Foo
    • \n
    • \n
      \n
    • \n
    \n", + ); + test_example( + 66, + "# foo *bar* \\*baz\\*\n", + "

    foo bar *baz*

    \n", + ); + test_example(73, "### foo ### \n", "

    foo

    \n"); + test_example( + 93, + "> foo\nbar\n===\n", + "
    \n

    foo\nbar\n===

    \n
    \n", + ); + test_example( + 223, + "aaa\n bbb\n ccc\n", + "

    aaa\nbbb\nccc

    \n", + ); + test_example( + 259, + " > > 1. one\n>>\n>> two\n", + "
    \n
    \n
      \n
    1. \n

      one

      \n

      two

      \n
    2. \n
    \n
    \n
    \n", + ); + test_example( + 9991, + "![a & b < c](url)\n", + "

    \"a

    \n", + ); } diff --git a/crates/biome_markdown_syntax/src/generated/kind.rs b/crates/biome_markdown_syntax/src/generated/kind.rs index a43d2ee90ad1..a703b98db8ab 100644 --- a/crates/biome_markdown_syntax/src/generated/kind.rs +++ b/crates/biome_markdown_syntax/src/generated/kind.rs @@ -23,15 +23,18 @@ pub enum MarkdownSyntaxKind { BANG, MINUS, STAR, + PLUS, DOUBLE_STAR, BACKTICK, TRIPLE_BACKTICK, TILDE, + TRIPLE_TILDE, WHITESPACE3, UNDERSCORE, DOUBLE_UNDERSCORE, HASH, COMMA, + COLON, NULL_KW, MD_HARD_LINE_LITERAL, MD_SOFT_BREAK_LITERAL, @@ -39,13 +42,17 @@ pub enum MarkdownSyntaxKind { MD_STRING_LITERAL, MD_INDENT_CHUNK_LITERAL, MD_THEMATIC_BREAK_LITERAL, + MD_SETEXT_UNDERLINE_LITERAL, + MD_ORDERED_LIST_MARKER, MD_ERROR_LITERAL, + MD_ENTITY_LITERAL, ERROR_TOKEN, NEWLINE, WHITESPACE, TAB, BOGUS, MD_BOGUS, + MD_BOGUS_BULLET, MD_DOCUMENT, MD_BLOCK_LIST, MD_HASH_LIST, @@ -56,11 +63,14 @@ pub enum MarkdownSyntaxKind { MD_CODE_NAME_LIST, MD_HTML_BLOCK, MD_LINK_BLOCK, + MD_LINK_REFERENCE_DEFINITION, + MD_LINK_LABEL, + MD_LINK_DESTINATION, + MD_LINK_TITLE, MD_QUOTE, - MD_ORDER_LIST_ITEM, + MD_ORDERED_LIST_ITEM, MD_BULLET_LIST_ITEM, MD_BULLET_LIST, - MD_ORDER_LIST, MD_PARAGRAPH, MD_INLINE_ITEM_LIST, MD_INLINE_EMPHASIS, @@ -69,6 +79,12 @@ pub enum MarkdownSyntaxKind { MD_BULLET, MD_INLINE_LINK, MD_INLINE_IMAGE, + MD_REFERENCE_LINK, + MD_REFERENCE_IMAGE, + MD_REFERENCE_LINK_LABEL, + MD_AUTOLINK, + MD_INLINE_HTML, + MD_ENTITY_REFERENCE, MD_INLINE_IMAGE_ALT, MD_INDENTED_CODE_LINE, MD_INLINE_IMAGE_LINK, @@ -81,6 +97,7 @@ pub enum MarkdownSyntaxKind { MD_STRING, MD_INDENT, MD_THEMATIC_BREAK_BLOCK, + MD_NEWLINE, #[doc(hidden)] __LAST, } @@ -100,15 +117,18 @@ impl MarkdownSyntaxKind { | BANG | MINUS | STAR + | PLUS | DOUBLE_STAR | BACKTICK | TRIPLE_BACKTICK | TILDE + | TRIPLE_TILDE | WHITESPACE3 | UNDERSCORE | DOUBLE_UNDERSCORE | HASH | COMMA + | COLON ) } pub const fn is_literal(self) -> bool { @@ -120,7 +140,10 @@ impl MarkdownSyntaxKind { | MD_STRING_LITERAL | MD_INDENT_CHUNK_LITERAL | MD_THEMATIC_BREAK_LITERAL + | MD_SETEXT_UNDERLINE_LITERAL + | MD_ORDERED_LIST_MARKER | MD_ERROR_LITERAL + | MD_ENTITY_LITERAL ) } pub const fn is_list(self) -> bool { @@ -130,7 +153,6 @@ impl MarkdownSyntaxKind { | MD_HASH_LIST | MD_CODE_NAME_LIST | MD_BULLET_LIST - | MD_ORDER_LIST | MD_INLINE_ITEM_LIST | MD_INDENTED_CODE_LINE_LIST ) @@ -155,17 +177,20 @@ impl MarkdownSyntaxKind { BANG => "!", MINUS => "-", STAR => "*", + PLUS => "+", DOUBLE_STAR => "**", BACKTICK => "`", TRIPLE_BACKTICK => "```", TILDE => "~", + TRIPLE_TILDE => "~~~", WHITESPACE3 => " ", UNDERSCORE => "_", DOUBLE_UNDERSCORE => "__", HASH => "#", COMMA => ",", + COLON => ":", NULL_KW => "null", - EOF => "EOF", + EOF => "", _ => return None, }; Some(tok) @@ -173,4 +198,4 @@ impl MarkdownSyntaxKind { } #[doc = r" Utility macro for creating a SyntaxKind through simple macro syntax"] #[macro_export] -macro_rules ! T { [<] => { $ crate :: MarkdownSyntaxKind :: L_ANGLE } ; [>] => { $ crate :: MarkdownSyntaxKind :: R_ANGLE } ; ['('] => { $ crate :: MarkdownSyntaxKind :: L_PAREN } ; [')'] => { $ crate :: MarkdownSyntaxKind :: R_PAREN } ; ['['] => { $ crate :: MarkdownSyntaxKind :: L_BRACK } ; [']'] => { $ crate :: MarkdownSyntaxKind :: R_BRACK } ; [/] => { $ crate :: MarkdownSyntaxKind :: SLASH } ; [=] => { $ crate :: MarkdownSyntaxKind :: EQ } ; [!] => { $ crate :: MarkdownSyntaxKind :: BANG } ; [-] => { $ crate :: MarkdownSyntaxKind :: MINUS } ; [*] => { $ crate :: MarkdownSyntaxKind :: STAR } ; [**] => { $ crate :: MarkdownSyntaxKind :: DOUBLE_STAR } ; ['`'] => { $ crate :: MarkdownSyntaxKind :: BACKTICK } ; ["```"] => { $ crate :: MarkdownSyntaxKind :: TRIPLE_BACKTICK } ; [~] => { $ crate :: MarkdownSyntaxKind :: TILDE } ; [" "] => { $ crate :: MarkdownSyntaxKind :: WHITESPACE3 } ; ["_"] => { $ crate :: MarkdownSyntaxKind :: UNDERSCORE } ; ["__"] => { $ crate :: MarkdownSyntaxKind :: DOUBLE_UNDERSCORE } ; [#] => { $ crate :: MarkdownSyntaxKind :: HASH } ; [,] => { $ crate :: MarkdownSyntaxKind :: COMMA } ; [null] => { $ crate :: MarkdownSyntaxKind :: NULL_KW } ; [ident] => { $ crate :: MarkdownSyntaxKind :: IDENT } ; [EOF] => { $ crate :: MarkdownSyntaxKind :: EOF } ; [UNICODE_BOM] => { $ crate :: MarkdownSyntaxKind :: UNICODE_BOM } ; [#] => { $ crate :: MarkdownSyntaxKind :: HASH } ; } +macro_rules ! T { [<] => { $ crate :: MarkdownSyntaxKind :: L_ANGLE } ; [>] => { $ crate :: MarkdownSyntaxKind :: R_ANGLE } ; ['('] => { $ crate :: MarkdownSyntaxKind :: L_PAREN } ; [')'] => { $ crate :: MarkdownSyntaxKind :: R_PAREN } ; ['['] => { $ crate :: MarkdownSyntaxKind :: L_BRACK } ; [']'] => { $ crate :: MarkdownSyntaxKind :: R_BRACK } ; [/] => { $ crate :: MarkdownSyntaxKind :: SLASH } ; [=] => { $ crate :: MarkdownSyntaxKind :: EQ } ; [!] => { $ crate :: MarkdownSyntaxKind :: BANG } ; [-] => { $ crate :: MarkdownSyntaxKind :: MINUS } ; [*] => { $ crate :: MarkdownSyntaxKind :: STAR } ; [+] => { $ crate :: MarkdownSyntaxKind :: PLUS } ; [**] => { $ crate :: MarkdownSyntaxKind :: DOUBLE_STAR } ; ['`'] => { $ crate :: MarkdownSyntaxKind :: BACKTICK } ; ["```"] => { $ crate :: MarkdownSyntaxKind :: TRIPLE_BACKTICK } ; [~] => { $ crate :: MarkdownSyntaxKind :: TILDE } ; [~~~] => { $ crate :: MarkdownSyntaxKind :: TRIPLE_TILDE } ; [" "] => { $ crate :: MarkdownSyntaxKind :: WHITESPACE3 } ; ["_"] => { $ crate :: MarkdownSyntaxKind :: UNDERSCORE } ; ["__"] => { $ crate :: MarkdownSyntaxKind :: DOUBLE_UNDERSCORE } ; [#] => { $ crate :: MarkdownSyntaxKind :: HASH } ; [,] => { $ crate :: MarkdownSyntaxKind :: COMMA } ; [:] => { $ crate :: MarkdownSyntaxKind :: COLON } ; [null] => { $ crate :: MarkdownSyntaxKind :: NULL_KW } ; [ident] => { $ crate :: MarkdownSyntaxKind :: IDENT } ; [EOF] => { $ crate :: MarkdownSyntaxKind :: EOF } ; [UNICODE_BOM] => { $ crate :: MarkdownSyntaxKind :: UNICODE_BOM } ; [#] => { $ crate :: MarkdownSyntaxKind :: HASH } ; } diff --git a/crates/biome_markdown_syntax/src/generated/macros.rs b/crates/biome_markdown_syntax/src/generated/macros.rs index 834bc5073bde..e10554278277 100644 --- a/crates/biome_markdown_syntax/src/generated/macros.rs +++ b/crates/biome_markdown_syntax/src/generated/macros.rs @@ -16,6 +16,10 @@ macro_rules! map_syntax_node { ($ node : expr , $ pattern : pat => $ body : expr) => { match $node { node => match $crate::MarkdownSyntaxNode::kind(&node) { + $crate::MarkdownSyntaxKind::MD_AUTOLINK => { + let $pattern = unsafe { $crate::MdAutolink::new_unchecked(node) }; + $body + } $crate::MarkdownSyntaxKind::MD_BULLET => { let $pattern = unsafe { $crate::MdBullet::new_unchecked(node) }; $body @@ -28,6 +32,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::MdDocument::new_unchecked(node) }; $body } + $crate::MarkdownSyntaxKind::MD_ENTITY_REFERENCE => { + let $pattern = unsafe { $crate::MdEntityReference::new_unchecked(node) }; + $body + } $crate::MarkdownSyntaxKind::MD_FENCED_CODE_BLOCK => { let $pattern = unsafe { $crate::MdFencedCodeBlock::new_unchecked(node) }; $body @@ -56,10 +64,6 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::MdIndentCodeBlock::new_unchecked(node) }; $body } - $crate::MarkdownSyntaxKind::MD_INDENTED_CODE_LINE => { - let $pattern = unsafe { $crate::MdIndentedCodeLine::new_unchecked(node) }; - $body - } $crate::MarkdownSyntaxKind::MD_INLINE_CODE => { let $pattern = unsafe { $crate::MdInlineCode::new_unchecked(node) }; $body @@ -68,20 +72,12 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::MdInlineEmphasis::new_unchecked(node) }; $body } - $crate::MarkdownSyntaxKind::MD_INLINE_IMAGE => { - let $pattern = unsafe { $crate::MdInlineImage::new_unchecked(node) }; + $crate::MarkdownSyntaxKind::MD_INLINE_HTML => { + let $pattern = unsafe { $crate::MdInlineHtml::new_unchecked(node) }; $body } - $crate::MarkdownSyntaxKind::MD_INLINE_IMAGE_ALT => { - let $pattern = unsafe { $crate::MdInlineImageAlt::new_unchecked(node) }; - $body - } - $crate::MarkdownSyntaxKind::MD_INLINE_IMAGE_LINK => { - let $pattern = unsafe { $crate::MdInlineImageLink::new_unchecked(node) }; - $body - } - $crate::MarkdownSyntaxKind::MD_INLINE_IMAGE_SOURCE => { - let $pattern = unsafe { $crate::MdInlineImageSource::new_unchecked(node) }; + $crate::MarkdownSyntaxKind::MD_INLINE_IMAGE => { + let $pattern = unsafe { $crate::MdInlineImage::new_unchecked(node) }; $body } $crate::MarkdownSyntaxKind::MD_INLINE_ITALIC => { @@ -96,8 +92,29 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::MdLinkBlock::new_unchecked(node) }; $body } - $crate::MarkdownSyntaxKind::MD_ORDER_LIST_ITEM => { - let $pattern = unsafe { $crate::MdOrderListItem::new_unchecked(node) }; + $crate::MarkdownSyntaxKind::MD_LINK_DESTINATION => { + let $pattern = unsafe { $crate::MdLinkDestination::new_unchecked(node) }; + $body + } + $crate::MarkdownSyntaxKind::MD_LINK_LABEL => { + let $pattern = unsafe { $crate::MdLinkLabel::new_unchecked(node) }; + $body + } + $crate::MarkdownSyntaxKind::MD_LINK_REFERENCE_DEFINITION => { + let $pattern = + unsafe { $crate::MdLinkReferenceDefinition::new_unchecked(node) }; + $body + } + $crate::MarkdownSyntaxKind::MD_LINK_TITLE => { + let $pattern = unsafe { $crate::MdLinkTitle::new_unchecked(node) }; + $body + } + $crate::MarkdownSyntaxKind::MD_NEWLINE => { + let $pattern = unsafe { $crate::MdNewline::new_unchecked(node) }; + $body + } + $crate::MarkdownSyntaxKind::MD_ORDERED_LIST_ITEM => { + let $pattern = unsafe { $crate::MdOrderedListItem::new_unchecked(node) }; $body } $crate::MarkdownSyntaxKind::MD_PARAGRAPH => { @@ -108,6 +125,18 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::MdQuote::new_unchecked(node) }; $body } + $crate::MarkdownSyntaxKind::MD_REFERENCE_IMAGE => { + let $pattern = unsafe { $crate::MdReferenceImage::new_unchecked(node) }; + $body + } + $crate::MarkdownSyntaxKind::MD_REFERENCE_LINK => { + let $pattern = unsafe { $crate::MdReferenceLink::new_unchecked(node) }; + $body + } + $crate::MarkdownSyntaxKind::MD_REFERENCE_LINK_LABEL => { + let $pattern = unsafe { $crate::MdReferenceLinkLabel::new_unchecked(node) }; + $body + } $crate::MarkdownSyntaxKind::MD_SETEXT_HEADER => { let $pattern = unsafe { $crate::MdSetextHeader::new_unchecked(node) }; $body @@ -144,18 +173,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::MdHashList::new_unchecked(node) }; $body } - $crate::MarkdownSyntaxKind::MD_INDENTED_CODE_LINE_LIST => { - let $pattern = unsafe { $crate::MdIndentedCodeLineList::new_unchecked(node) }; - $body - } $crate::MarkdownSyntaxKind::MD_INLINE_ITEM_LIST => { let $pattern = unsafe { $crate::MdInlineItemList::new_unchecked(node) }; $body } - $crate::MarkdownSyntaxKind::MD_ORDER_LIST => { - let $pattern = unsafe { $crate::MdOrderList::new_unchecked(node) }; - $body - } _ => unreachable!(), }, } diff --git a/crates/biome_markdown_syntax/src/generated/nodes.rs b/crates/biome_markdown_syntax/src/generated/nodes.rs index b3c37bc737d4..cb1bd8ff55dd 100644 --- a/crates/biome_markdown_syntax/src/generated/nodes.rs +++ b/crates/biome_markdown_syntax/src/generated/nodes.rs @@ -21,6 +21,51 @@ use std::fmt::{Debug, Formatter}; #[doc = r" the slots are not statically known."] pub(crate) const SLOT_MAP_EMPTY_VALUE: u8 = u8::MAX; #[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdAutolink { + pub(crate) syntax: SyntaxNode, +} +impl MdAutolink { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> MdAutolinkFields { + MdAutolinkFields { + l_angle_token: self.l_angle_token(), + value: self.value(), + r_angle_token: self.r_angle_token(), + } + } + pub fn l_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn value(&self) -> MdInlineItemList { + support::list(&self.syntax, 1usize) + } + pub fn r_angle_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } +} +impl Serialize for MdAutolink { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdAutolinkFields { + pub l_angle_token: SyntaxResult, + pub value: MdInlineItemList, + pub r_angle_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct MdBullet { pub(crate) syntax: SyntaxNode, } @@ -37,18 +82,14 @@ impl MdBullet { pub fn as_fields(&self) -> MdBulletFields { MdBulletFields { bullet: self.bullet(), - space_token: self.space_token(), content: self.content(), } } pub fn bullet(&self) -> SyntaxResult { support::required_token(&self.syntax, 0usize) } - pub fn space_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 1usize) - } - pub fn content(&self) -> MdInlineItemList { - support::list(&self.syntax, 2usize) + pub fn content(&self) -> MdBlockList { + support::list(&self.syntax, 1usize) } } impl Serialize for MdBullet { @@ -62,8 +103,7 @@ impl Serialize for MdBullet { #[derive(Serialize)] pub struct MdBulletFields { pub bullet: SyntaxResult, - pub space_token: SyntaxResult, - pub content: MdInlineItemList, + pub content: MdBlockList, } #[derive(Clone, PartialEq, Eq, Hash)] pub struct MdBulletListItem { @@ -146,6 +186,41 @@ pub struct MdDocumentFields { pub eof_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdEntityReference { + pub(crate) syntax: SyntaxNode, +} +impl MdEntityReference { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> MdEntityReferenceFields { + MdEntityReferenceFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +impl Serialize for MdEntityReference { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdEntityReferenceFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct MdFencedCodeBlock { pub(crate) syntax: SyntaxNode, } @@ -161,31 +236,23 @@ impl MdFencedCodeBlock { } pub fn as_fields(&self) -> MdFencedCodeBlockFields { MdFencedCodeBlockFields { - l_fence_token: self.l_fence_token(), + l_fence: self.l_fence(), code_list: self.code_list(), - l_hard_line: self.l_hard_line(), content: self.content(), - r_hard_line: self.r_hard_line(), - r_fence_token: self.r_fence_token(), + r_fence: self.r_fence(), } } - pub fn l_fence_token(&self) -> SyntaxResult { + pub fn l_fence(&self) -> SyntaxResult { support::required_token(&self.syntax, 0usize) } pub fn code_list(&self) -> MdCodeNameList { support::list(&self.syntax, 1usize) } - pub fn l_hard_line(&self) -> SyntaxResult { - support::required_node(&self.syntax, 2usize) - } - pub fn content(&self) -> SyntaxResult { - support::required_node(&self.syntax, 3usize) - } - pub fn r_hard_line(&self) -> SyntaxResult { - support::required_node(&self.syntax, 4usize) + pub fn content(&self) -> MdInlineItemList { + support::list(&self.syntax, 2usize) } - pub fn r_fence_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 5usize) + pub fn r_fence(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) } } impl Serialize for MdFencedCodeBlock { @@ -198,12 +265,10 @@ impl Serialize for MdFencedCodeBlock { } #[derive(Serialize)] pub struct MdFencedCodeBlockFields { - pub l_fence_token: SyntaxResult, + pub l_fence: SyntaxResult, pub code_list: MdCodeNameList, - pub l_hard_line: SyntaxResult, - pub content: SyntaxResult, - pub r_hard_line: SyntaxResult, - pub r_fence_token: SyntaxResult, + pub content: MdInlineItemList, + pub r_fence: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] pub struct MdHardLine { @@ -336,11 +401,11 @@ impl MdHtmlBlock { } pub fn as_fields(&self) -> MdHtmlBlockFields { MdHtmlBlockFields { - md_textual: self.md_textual(), + content: self.content(), } } - pub fn md_textual(&self) -> SyntaxResult { - support::required_node(&self.syntax, 0usize) + pub fn content(&self) -> MdInlineItemList { + support::list(&self.syntax, 0usize) } } impl Serialize for MdHtmlBlock { @@ -353,7 +418,7 @@ impl Serialize for MdHtmlBlock { } #[derive(Serialize)] pub struct MdHtmlBlockFields { - pub md_textual: SyntaxResult, + pub content: MdInlineItemList, } #[derive(Clone, PartialEq, Eq, Hash)] pub struct MdIndent { @@ -406,10 +471,10 @@ impl MdIndentCodeBlock { } pub fn as_fields(&self) -> MdIndentCodeBlockFields { MdIndentCodeBlockFields { - lines: self.lines(), + content: self.content(), } } - pub fn lines(&self) -> MdIndentedCodeLineList { + pub fn content(&self) -> MdInlineItemList { support::list(&self.syntax, 0usize) } } @@ -423,47 +488,7 @@ impl Serialize for MdIndentCodeBlock { } #[derive(Serialize)] pub struct MdIndentCodeBlockFields { - pub lines: MdIndentedCodeLineList, -} -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdIndentedCodeLine { - pub(crate) syntax: SyntaxNode, -} -impl MdIndentedCodeLine { - #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] - #[doc = r" or a match on [SyntaxNode::kind]"] - #[inline] - pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { - Self { syntax } - } - pub fn as_fields(&self) -> MdIndentedCodeLineFields { - MdIndentedCodeLineFields { - indentation: self.indentation(), - content: self.content(), - } - } - pub fn indentation(&self) -> SyntaxResult { - support::required_node(&self.syntax, 0usize) - } - pub fn content(&self) -> SyntaxResult { - support::required_node(&self.syntax, 1usize) - } -} -impl Serialize for MdIndentedCodeLine { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.as_fields().serialize(serializer) - } -} -#[derive(Serialize)] -pub struct MdIndentedCodeLineFields { - pub indentation: SyntaxResult, - pub content: SyntaxResult, + pub content: MdInlineItemList, } #[derive(Clone, PartialEq, Eq, Hash)] pub struct MdInlineCode { @@ -556,10 +581,10 @@ pub struct MdInlineEmphasisFields { pub r_fence: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdInlineImage { +pub struct MdInlineHtml { pub(crate) syntax: SyntaxNode, } -impl MdInlineImage { +impl MdInlineHtml { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -569,36 +594,16 @@ impl MdInlineImage { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdInlineImageFields { - MdInlineImageFields { - l_brack_token: self.l_brack_token(), - excl_token: self.excl_token(), - alt: self.alt(), - source: self.source(), - r_brack_token: self.r_brack_token(), - link: self.link(), + pub fn as_fields(&self) -> MdInlineHtmlFields { + MdInlineHtmlFields { + value: self.value(), } } - pub fn l_brack_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 0usize) - } - pub fn excl_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 1usize) - } - pub fn alt(&self) -> SyntaxResult { - support::required_node(&self.syntax, 2usize) - } - pub fn source(&self) -> SyntaxResult { - support::required_node(&self.syntax, 3usize) - } - pub fn r_brack_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 4usize) - } - pub fn link(&self) -> Option { - support::node(&self.syntax, 5usize) + pub fn value(&self) -> MdInlineItemList { + support::list(&self.syntax, 0usize) } } -impl Serialize for MdInlineImage { +impl Serialize for MdInlineHtml { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -607,19 +612,14 @@ impl Serialize for MdInlineImage { } } #[derive(Serialize)] -pub struct MdInlineImageFields { - pub l_brack_token: SyntaxResult, - pub excl_token: SyntaxResult, - pub alt: SyntaxResult, - pub source: SyntaxResult, - pub r_brack_token: SyntaxResult, - pub link: Option, +pub struct MdInlineHtmlFields { + pub value: MdInlineItemList, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdInlineImageAlt { +pub struct MdInlineImage { pub(crate) syntax: SyntaxNode, } -impl MdInlineImageAlt { +impl MdInlineImage { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -629,114 +629,44 @@ impl MdInlineImageAlt { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdInlineImageAltFields { - MdInlineImageAltFields { + pub fn as_fields(&self) -> MdInlineImageFields { + MdInlineImageFields { + excl_token: self.excl_token(), l_brack_token: self.l_brack_token(), - content: self.content(), + alt: self.alt(), r_brack_token: self.r_brack_token(), - } - } - pub fn l_brack_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 0usize) - } - pub fn content(&self) -> MdInlineItemList { - support::list(&self.syntax, 1usize) - } - pub fn r_brack_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 2usize) - } -} -impl Serialize for MdInlineImageAlt { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.as_fields().serialize(serializer) - } -} -#[derive(Serialize)] -pub struct MdInlineImageAltFields { - pub l_brack_token: SyntaxResult, - pub content: MdInlineItemList, - pub r_brack_token: SyntaxResult, -} -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdInlineImageLink { - pub(crate) syntax: SyntaxNode, -} -impl MdInlineImageLink { - #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] - #[doc = r" or a match on [SyntaxNode::kind]"] - #[inline] - pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { - Self { syntax } - } - pub fn as_fields(&self) -> MdInlineImageLinkFields { - MdInlineImageLinkFields { l_paren_token: self.l_paren_token(), - content: self.content(), + destination: self.destination(), + title: self.title(), r_paren_token: self.r_paren_token(), } } - pub fn l_paren_token(&self) -> SyntaxResult { + pub fn excl_token(&self) -> SyntaxResult { support::required_token(&self.syntax, 0usize) } - pub fn content(&self) -> MdInlineItemList { - support::list(&self.syntax, 1usize) - } - pub fn r_paren_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 2usize) - } -} -impl Serialize for MdInlineImageLink { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.as_fields().serialize(serializer) + pub fn l_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) } -} -#[derive(Serialize)] -pub struct MdInlineImageLinkFields { - pub l_paren_token: SyntaxResult, - pub content: MdInlineItemList, - pub r_paren_token: SyntaxResult, -} -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdInlineImageSource { - pub(crate) syntax: SyntaxNode, -} -impl MdInlineImageSource { - #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] - #[doc = r" or a match on [SyntaxNode::kind]"] - #[inline] - pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { - Self { syntax } + pub fn alt(&self) -> MdInlineItemList { + support::list(&self.syntax, 2usize) } - pub fn as_fields(&self) -> MdInlineImageSourceFields { - MdInlineImageSourceFields { - l_paren_token: self.l_paren_token(), - content: self.content(), - r_paren_token: self.r_paren_token(), - } + pub fn r_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) } pub fn l_paren_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 0usize) + support::required_token(&self.syntax, 4usize) } - pub fn content(&self) -> MdInlineItemList { - support::list(&self.syntax, 1usize) + pub fn destination(&self) -> MdInlineItemList { + support::list(&self.syntax, 5usize) + } + pub fn title(&self) -> Option { + support::node(&self.syntax, 6usize) } pub fn r_paren_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 2usize) + support::required_token(&self.syntax, 7usize) } } -impl Serialize for MdInlineImageSource { +impl Serialize for MdInlineImage { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -745,9 +675,14 @@ impl Serialize for MdInlineImageSource { } } #[derive(Serialize)] -pub struct MdInlineImageSourceFields { +pub struct MdInlineImageFields { + pub excl_token: SyntaxResult, + pub l_brack_token: SyntaxResult, + pub alt: MdInlineItemList, + pub r_brack_token: SyntaxResult, pub l_paren_token: SyntaxResult, - pub content: MdInlineItemList, + pub destination: MdInlineItemList, + pub title: Option, pub r_paren_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] @@ -815,7 +750,8 @@ impl MdInlineLink { text: self.text(), r_brack_token: self.r_brack_token(), l_paren_token: self.l_paren_token(), - source: self.source(), + destination: self.destination(), + title: self.title(), r_paren_token: self.r_paren_token(), } } @@ -831,11 +767,14 @@ impl MdInlineLink { pub fn l_paren_token(&self) -> SyntaxResult { support::required_token(&self.syntax, 3usize) } - pub fn source(&self) -> MdInlineItemList { + pub fn destination(&self) -> MdInlineItemList { support::list(&self.syntax, 4usize) } + pub fn title(&self) -> Option { + support::node(&self.syntax, 5usize) + } pub fn r_paren_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 5usize) + support::required_token(&self.syntax, 6usize) } } impl Serialize for MdInlineLink { @@ -852,7 +791,8 @@ pub struct MdInlineLinkFields { pub text: MdInlineItemList, pub r_brack_token: SyntaxResult, pub l_paren_token: SyntaxResult, - pub source: MdInlineItemList, + pub destination: MdInlineItemList, + pub title: Option, pub r_paren_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] @@ -901,10 +841,10 @@ pub struct MdLinkBlockFields { pub title: Option, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdOrderListItem { +pub struct MdLinkDestination { pub(crate) syntax: SyntaxNode, } -impl MdOrderListItem { +impl MdLinkDestination { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -914,16 +854,16 @@ impl MdOrderListItem { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdOrderListItemFields { - MdOrderListItemFields { - md_bullet_list: self.md_bullet_list(), + pub fn as_fields(&self) -> MdLinkDestinationFields { + MdLinkDestinationFields { + content: self.content(), } } - pub fn md_bullet_list(&self) -> MdBulletList { + pub fn content(&self) -> MdInlineItemList { support::list(&self.syntax, 0usize) } } -impl Serialize for MdOrderListItem { +impl Serialize for MdLinkDestination { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -932,14 +872,14 @@ impl Serialize for MdOrderListItem { } } #[derive(Serialize)] -pub struct MdOrderListItemFields { - pub md_bullet_list: MdBulletList, +pub struct MdLinkDestinationFields { + pub content: MdInlineItemList, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdParagraph { +pub struct MdLinkLabel { pub(crate) syntax: SyntaxNode, } -impl MdParagraph { +impl MdLinkLabel { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -949,20 +889,16 @@ impl MdParagraph { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdParagraphFields { - MdParagraphFields { - list: self.list(), - hard_line: self.hard_line(), + pub fn as_fields(&self) -> MdLinkLabelFields { + MdLinkLabelFields { + content: self.content(), } } - pub fn list(&self) -> MdInlineItemList { + pub fn content(&self) -> MdInlineItemList { support::list(&self.syntax, 0usize) } - pub fn hard_line(&self) -> SyntaxResult { - support::required_node(&self.syntax, 1usize) - } } -impl Serialize for MdParagraph { +impl Serialize for MdLinkLabel { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -971,15 +907,14 @@ impl Serialize for MdParagraph { } } #[derive(Serialize)] -pub struct MdParagraphFields { - pub list: MdInlineItemList, - pub hard_line: SyntaxResult, +pub struct MdLinkLabelFields { + pub content: MdInlineItemList, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdQuote { +pub struct MdLinkReferenceDefinition { pub(crate) syntax: SyntaxNode, } -impl MdQuote { +impl MdLinkReferenceDefinition { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -989,16 +924,36 @@ impl MdQuote { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdQuoteFields { - MdQuoteFields { - any_md_block: self.any_md_block(), + pub fn as_fields(&self) -> MdLinkReferenceDefinitionFields { + MdLinkReferenceDefinitionFields { + l_brack_token: self.l_brack_token(), + label: self.label(), + r_brack_token: self.r_brack_token(), + colon_token: self.colon_token(), + destination: self.destination(), + title: self.title(), } } - pub fn any_md_block(&self) -> SyntaxResult { - support::required_node(&self.syntax, 0usize) + pub fn l_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn label(&self) -> SyntaxResult { + support::required_node(&self.syntax, 1usize) + } + pub fn r_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } + pub fn colon_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) + } + pub fn destination(&self) -> SyntaxResult { + support::required_node(&self.syntax, 4usize) + } + pub fn title(&self) -> Option { + support::node(&self.syntax, 5usize) } } -impl Serialize for MdQuote { +impl Serialize for MdLinkReferenceDefinition { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1007,14 +962,19 @@ impl Serialize for MdQuote { } } #[derive(Serialize)] -pub struct MdQuoteFields { - pub any_md_block: SyntaxResult, +pub struct MdLinkReferenceDefinitionFields { + pub l_brack_token: SyntaxResult, + pub label: SyntaxResult, + pub r_brack_token: SyntaxResult, + pub colon_token: SyntaxResult, + pub destination: SyntaxResult, + pub title: Option, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdSetextHeader { +pub struct MdLinkTitle { pub(crate) syntax: SyntaxNode, } -impl MdSetextHeader { +impl MdLinkTitle { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -1024,16 +984,16 @@ impl MdSetextHeader { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdSetextHeaderFields { - MdSetextHeaderFields { - md_paragraph: self.md_paragraph(), + pub fn as_fields(&self) -> MdLinkTitleFields { + MdLinkTitleFields { + content: self.content(), } } - pub fn md_paragraph(&self) -> SyntaxResult { - support::required_node(&self.syntax, 0usize) + pub fn content(&self) -> MdInlineItemList { + support::list(&self.syntax, 0usize) } } -impl Serialize for MdSetextHeader { +impl Serialize for MdLinkTitle { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1042,14 +1002,14 @@ impl Serialize for MdSetextHeader { } } #[derive(Serialize)] -pub struct MdSetextHeaderFields { - pub md_paragraph: SyntaxResult, +pub struct MdLinkTitleFields { + pub content: MdInlineItemList, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdSoftBreak { +pub struct MdNewline { pub(crate) syntax: SyntaxNode, } -impl MdSoftBreak { +impl MdNewline { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -1059,8 +1019,8 @@ impl MdSoftBreak { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdSoftBreakFields { - MdSoftBreakFields { + pub fn as_fields(&self) -> MdNewlineFields { + MdNewlineFields { value_token: self.value_token(), } } @@ -1068,7 +1028,7 @@ impl MdSoftBreak { support::required_token(&self.syntax, 0usize) } } -impl Serialize for MdSoftBreak { +impl Serialize for MdNewline { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1077,14 +1037,14 @@ impl Serialize for MdSoftBreak { } } #[derive(Serialize)] -pub struct MdSoftBreakFields { +pub struct MdNewlineFields { pub value_token: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdTextual { +pub struct MdOrderedListItem { pub(crate) syntax: SyntaxNode, } -impl MdTextual { +impl MdOrderedListItem { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -1094,16 +1054,16 @@ impl MdTextual { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdTextualFields { - MdTextualFields { - value_token: self.value_token(), + pub fn as_fields(&self) -> MdOrderedListItemFields { + MdOrderedListItemFields { + md_bullet_list: self.md_bullet_list(), } } - pub fn value_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 0usize) + pub fn md_bullet_list(&self) -> MdBulletList { + support::list(&self.syntax, 0usize) } } -impl Serialize for MdTextual { +impl Serialize for MdOrderedListItem { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1112,14 +1072,14 @@ impl Serialize for MdTextual { } } #[derive(Serialize)] -pub struct MdTextualFields { - pub value_token: SyntaxResult, +pub struct MdOrderedListItemFields { + pub md_bullet_list: MdBulletList, } #[derive(Clone, PartialEq, Eq, Hash)] -pub struct MdThematicBreakBlock { +pub struct MdParagraph { pub(crate) syntax: SyntaxNode, } -impl MdThematicBreakBlock { +impl MdParagraph { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -1129,16 +1089,20 @@ impl MdThematicBreakBlock { pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } - pub fn as_fields(&self) -> MdThematicBreakBlockFields { - MdThematicBreakBlockFields { - value_token: self.value_token(), + pub fn as_fields(&self) -> MdParagraphFields { + MdParagraphFields { + list: self.list(), + hard_line: self.hard_line(), } } - pub fn value_token(&self) -> SyntaxResult { - support::required_token(&self.syntax, 0usize) + pub fn list(&self) -> MdInlineItemList { + support::list(&self.syntax, 0usize) + } + pub fn hard_line(&self) -> Option { + support::node(&self.syntax, 1usize) } } -impl Serialize for MdThematicBreakBlock { +impl Serialize for MdParagraph { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -1147,201 +1111,888 @@ impl Serialize for MdThematicBreakBlock { } } #[derive(Serialize)] -pub struct MdThematicBreakBlockFields { - pub value_token: SyntaxResult, +pub struct MdParagraphFields { + pub list: MdInlineItemList, + pub hard_line: Option, } -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnyCodeBlock { - MdFencedCodeBlock(MdFencedCodeBlock), - MdIndentCodeBlock(MdIndentCodeBlock), +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdQuote { + pub(crate) syntax: SyntaxNode, } -impl AnyCodeBlock { - pub fn as_md_fenced_code_block(&self) -> Option<&MdFencedCodeBlock> { - match &self { - Self::MdFencedCodeBlock(item) => Some(item), - _ => None, - } +impl MdQuote { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } } - pub fn as_md_indent_code_block(&self) -> Option<&MdIndentCodeBlock> { - match &self { - Self::MdIndentCodeBlock(item) => Some(item), - _ => None, + pub fn as_fields(&self) -> MdQuoteFields { + MdQuoteFields { + marker_token: self.marker_token(), + content: self.content(), } } -} -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnyContainerBlock { - MdBulletListItem(MdBulletListItem), - MdOrderListItem(MdOrderListItem), - MdQuote(MdQuote), -} -impl AnyContainerBlock { - pub fn as_md_bullet_list_item(&self) -> Option<&MdBulletListItem> { - match &self { - Self::MdBulletListItem(item) => Some(item), - _ => None, - } + pub fn marker_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) } - pub fn as_md_order_list_item(&self) -> Option<&MdOrderListItem> { - match &self { - Self::MdOrderListItem(item) => Some(item), - _ => None, - } + pub fn content(&self) -> MdBlockList { + support::list(&self.syntax, 1usize) } - pub fn as_md_quote(&self) -> Option<&MdQuote> { - match &self { - Self::MdQuote(item) => Some(item), - _ => None, - } +} +impl Serialize for MdQuote { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) } } -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnyLeafBlock { - AnyCodeBlock(AnyCodeBlock), - MdHeader(MdHeader), - MdHtmlBlock(MdHtmlBlock), - MdLinkBlock(MdLinkBlock), - MdParagraph(MdParagraph), - MdSetextHeader(MdSetextHeader), - MdThematicBreakBlock(MdThematicBreakBlock), +#[derive(Serialize)] +pub struct MdQuoteFields { + pub marker_token: SyntaxResult, + pub content: MdBlockList, } -impl AnyLeafBlock { - pub fn as_any_code_block(&self) -> Option<&AnyCodeBlock> { - match &self { - Self::AnyCodeBlock(item) => Some(item), - _ => None, - } +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdReferenceImage { + pub(crate) syntax: SyntaxNode, +} +impl MdReferenceImage { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } } - pub fn as_md_header(&self) -> Option<&MdHeader> { - match &self { - Self::MdHeader(item) => Some(item), - _ => None, + pub fn as_fields(&self) -> MdReferenceImageFields { + MdReferenceImageFields { + excl_token: self.excl_token(), + l_brack_token: self.l_brack_token(), + alt: self.alt(), + r_brack_token: self.r_brack_token(), + label: self.label(), } } - pub fn as_md_html_block(&self) -> Option<&MdHtmlBlock> { - match &self { - Self::MdHtmlBlock(item) => Some(item), - _ => None, - } + pub fn excl_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) } - pub fn as_md_link_block(&self) -> Option<&MdLinkBlock> { - match &self { - Self::MdLinkBlock(item) => Some(item), - _ => None, - } + pub fn l_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) } - pub fn as_md_paragraph(&self) -> Option<&MdParagraph> { - match &self { - Self::MdParagraph(item) => Some(item), - _ => None, - } + pub fn alt(&self) -> MdInlineItemList { + support::list(&self.syntax, 2usize) } - pub fn as_md_setext_header(&self) -> Option<&MdSetextHeader> { - match &self { - Self::MdSetextHeader(item) => Some(item), - _ => None, - } + pub fn r_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 3usize) } - pub fn as_md_thematic_break_block(&self) -> Option<&MdThematicBreakBlock> { - match &self { - Self::MdThematicBreakBlock(item) => Some(item), - _ => None, - } + pub fn label(&self) -> Option { + support::node(&self.syntax, 4usize) } } -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnyMdBlock { - AnyContainerBlock(AnyContainerBlock), - AnyLeafBlock(AnyLeafBlock), +impl Serialize for MdReferenceImage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } } -impl AnyMdBlock { - pub fn as_any_container_block(&self) -> Option<&AnyContainerBlock> { - match &self { - Self::AnyContainerBlock(item) => Some(item), - _ => None, - } +#[derive(Serialize)] +pub struct MdReferenceImageFields { + pub excl_token: SyntaxResult, + pub l_brack_token: SyntaxResult, + pub alt: MdInlineItemList, + pub r_brack_token: SyntaxResult, + pub label: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdReferenceLink { + pub(crate) syntax: SyntaxNode, +} +impl MdReferenceLink { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } } - pub fn as_any_leaf_block(&self) -> Option<&AnyLeafBlock> { - match &self { - Self::AnyLeafBlock(item) => Some(item), - _ => None, + pub fn as_fields(&self) -> MdReferenceLinkFields { + MdReferenceLinkFields { + l_brack_token: self.l_brack_token(), + text: self.text(), + r_brack_token: self.r_brack_token(), + label: self.label(), } } + pub fn l_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn text(&self) -> MdInlineItemList { + support::list(&self.syntax, 1usize) + } + pub fn r_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } + pub fn label(&self) -> Option { + support::node(&self.syntax, 3usize) + } } -#[derive(Clone, PartialEq, Eq, Hash, Serialize)] -pub enum AnyMdInline { - MdHardLine(MdHardLine), - MdHtmlBlock(MdHtmlBlock), - MdInlineCode(MdInlineCode), - MdInlineEmphasis(MdInlineEmphasis), +impl Serialize for MdReferenceLink { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdReferenceLinkFields { + pub l_brack_token: SyntaxResult, + pub text: MdInlineItemList, + pub r_brack_token: SyntaxResult, + pub label: Option, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdReferenceLinkLabel { + pub(crate) syntax: SyntaxNode, +} +impl MdReferenceLinkLabel { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> MdReferenceLinkLabelFields { + MdReferenceLinkLabelFields { + l_brack_token: self.l_brack_token(), + label: self.label(), + r_brack_token: self.r_brack_token(), + } + } + pub fn l_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } + pub fn label(&self) -> MdInlineItemList { + support::list(&self.syntax, 1usize) + } + pub fn r_brack_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 2usize) + } +} +impl Serialize for MdReferenceLinkLabel { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdReferenceLinkLabelFields { + pub l_brack_token: SyntaxResult, + pub label: MdInlineItemList, + pub r_brack_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdSetextHeader { + pub(crate) syntax: SyntaxNode, +} +impl MdSetextHeader { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> MdSetextHeaderFields { + MdSetextHeaderFields { + content: self.content(), + underline_token: self.underline_token(), + } + } + pub fn content(&self) -> MdInlineItemList { + support::list(&self.syntax, 0usize) + } + pub fn underline_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } +} +impl Serialize for MdSetextHeader { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdSetextHeaderFields { + pub content: MdInlineItemList, + pub underline_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdSoftBreak { + pub(crate) syntax: SyntaxNode, +} +impl MdSoftBreak { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> MdSoftBreakFields { + MdSoftBreakFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +impl Serialize for MdSoftBreak { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdSoftBreakFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdTextual { + pub(crate) syntax: SyntaxNode, +} +impl MdTextual { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> MdTextualFields { + MdTextualFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +impl Serialize for MdTextual { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdTextualFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct MdThematicBreakBlock { + pub(crate) syntax: SyntaxNode, +} +impl MdThematicBreakBlock { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { + Self { syntax } + } + pub fn as_fields(&self) -> MdThematicBreakBlockFields { + MdThematicBreakBlockFields { + value_token: self.value_token(), + } + } + pub fn value_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 0usize) + } +} +impl Serialize for MdThematicBreakBlock { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[derive(Serialize)] +pub struct MdThematicBreakBlockFields { + pub value_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyMdBlock { + AnyMdContainerBlock(AnyMdContainerBlock), + AnyMdLeafBlock(AnyMdLeafBlock), +} +impl AnyMdBlock { + pub fn as_any_md_container_block(&self) -> Option<&AnyMdContainerBlock> { + match &self { + Self::AnyMdContainerBlock(item) => Some(item), + _ => None, + } + } + pub fn as_any_md_leaf_block(&self) -> Option<&AnyMdLeafBlock> { + match &self { + Self::AnyMdLeafBlock(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyMdCodeBlock { + MdFencedCodeBlock(MdFencedCodeBlock), + MdIndentCodeBlock(MdIndentCodeBlock), +} +impl AnyMdCodeBlock { + pub fn as_md_fenced_code_block(&self) -> Option<&MdFencedCodeBlock> { + match &self { + Self::MdFencedCodeBlock(item) => Some(item), + _ => None, + } + } + pub fn as_md_indent_code_block(&self) -> Option<&MdIndentCodeBlock> { + match &self { + Self::MdIndentCodeBlock(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyMdContainerBlock { + MdBulletListItem(MdBulletListItem), + MdOrderedListItem(MdOrderedListItem), + MdQuote(MdQuote), +} +impl AnyMdContainerBlock { + pub fn as_md_bullet_list_item(&self) -> Option<&MdBulletListItem> { + match &self { + Self::MdBulletListItem(item) => Some(item), + _ => None, + } + } + pub fn as_md_ordered_list_item(&self) -> Option<&MdOrderedListItem> { + match &self { + Self::MdOrderedListItem(item) => Some(item), + _ => None, + } + } + pub fn as_md_quote(&self) -> Option<&MdQuote> { + match &self { + Self::MdQuote(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyMdInline { + MdAutolink(MdAutolink), + MdEntityReference(MdEntityReference), + MdHardLine(MdHardLine), + MdHtmlBlock(MdHtmlBlock), + MdInlineCode(MdInlineCode), + MdInlineEmphasis(MdInlineEmphasis), + MdInlineHtml(MdInlineHtml), MdInlineImage(MdInlineImage), MdInlineItalic(MdInlineItalic), MdInlineLink(MdInlineLink), + MdReferenceImage(MdReferenceImage), + MdReferenceLink(MdReferenceLink), MdSoftBreak(MdSoftBreak), MdTextual(MdTextual), } -impl AnyMdInline { - pub fn as_md_hard_line(&self) -> Option<&MdHardLine> { - match &self { - Self::MdHardLine(item) => Some(item), - _ => None, - } +impl AnyMdInline { + pub fn as_md_autolink(&self) -> Option<&MdAutolink> { + match &self { + Self::MdAutolink(item) => Some(item), + _ => None, + } + } + pub fn as_md_entity_reference(&self) -> Option<&MdEntityReference> { + match &self { + Self::MdEntityReference(item) => Some(item), + _ => None, + } + } + pub fn as_md_hard_line(&self) -> Option<&MdHardLine> { + match &self { + Self::MdHardLine(item) => Some(item), + _ => None, + } + } + pub fn as_md_html_block(&self) -> Option<&MdHtmlBlock> { + match &self { + Self::MdHtmlBlock(item) => Some(item), + _ => None, + } + } + pub fn as_md_inline_code(&self) -> Option<&MdInlineCode> { + match &self { + Self::MdInlineCode(item) => Some(item), + _ => None, + } + } + pub fn as_md_inline_emphasis(&self) -> Option<&MdInlineEmphasis> { + match &self { + Self::MdInlineEmphasis(item) => Some(item), + _ => None, + } + } + pub fn as_md_inline_html(&self) -> Option<&MdInlineHtml> { + match &self { + Self::MdInlineHtml(item) => Some(item), + _ => None, + } + } + pub fn as_md_inline_image(&self) -> Option<&MdInlineImage> { + match &self { + Self::MdInlineImage(item) => Some(item), + _ => None, + } + } + pub fn as_md_inline_italic(&self) -> Option<&MdInlineItalic> { + match &self { + Self::MdInlineItalic(item) => Some(item), + _ => None, + } + } + pub fn as_md_inline_link(&self) -> Option<&MdInlineLink> { + match &self { + Self::MdInlineLink(item) => Some(item), + _ => None, + } + } + pub fn as_md_reference_image(&self) -> Option<&MdReferenceImage> { + match &self { + Self::MdReferenceImage(item) => Some(item), + _ => None, + } + } + pub fn as_md_reference_link(&self) -> Option<&MdReferenceLink> { + match &self { + Self::MdReferenceLink(item) => Some(item), + _ => None, + } + } + pub fn as_md_soft_break(&self) -> Option<&MdSoftBreak> { + match &self { + Self::MdSoftBreak(item) => Some(item), + _ => None, + } + } + pub fn as_md_textual(&self) -> Option<&MdTextual> { + match &self { + Self::MdTextual(item) => Some(item), + _ => None, + } + } +} +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub enum AnyMdLeafBlock { + AnyMdCodeBlock(AnyMdCodeBlock), + MdHeader(MdHeader), + MdHtmlBlock(MdHtmlBlock), + MdLinkBlock(MdLinkBlock), + MdLinkReferenceDefinition(MdLinkReferenceDefinition), + MdNewline(MdNewline), + MdParagraph(MdParagraph), + MdSetextHeader(MdSetextHeader), + MdThematicBreakBlock(MdThematicBreakBlock), +} +impl AnyMdLeafBlock { + pub fn as_any_md_code_block(&self) -> Option<&AnyMdCodeBlock> { + match &self { + Self::AnyMdCodeBlock(item) => Some(item), + _ => None, + } + } + pub fn as_md_header(&self) -> Option<&MdHeader> { + match &self { + Self::MdHeader(item) => Some(item), + _ => None, + } + } + pub fn as_md_html_block(&self) -> Option<&MdHtmlBlock> { + match &self { + Self::MdHtmlBlock(item) => Some(item), + _ => None, + } + } + pub fn as_md_link_block(&self) -> Option<&MdLinkBlock> { + match &self { + Self::MdLinkBlock(item) => Some(item), + _ => None, + } + } + pub fn as_md_link_reference_definition(&self) -> Option<&MdLinkReferenceDefinition> { + match &self { + Self::MdLinkReferenceDefinition(item) => Some(item), + _ => None, + } + } + pub fn as_md_newline(&self) -> Option<&MdNewline> { + match &self { + Self::MdNewline(item) => Some(item), + _ => None, + } + } + pub fn as_md_paragraph(&self) -> Option<&MdParagraph> { + match &self { + Self::MdParagraph(item) => Some(item), + _ => None, + } + } + pub fn as_md_setext_header(&self) -> Option<&MdSetextHeader> { + match &self { + Self::MdSetextHeader(item) => Some(item), + _ => None, + } + } + pub fn as_md_thematic_break_block(&self) -> Option<&MdThematicBreakBlock> { + match &self { + Self::MdThematicBreakBlock(item) => Some(item), + _ => None, + } + } +} +impl AstNode for MdAutolink { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(MD_AUTOLINK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == MD_AUTOLINK + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for MdAutolink { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("MdAutolink") + .field( + "l_angle_token", + &support::DebugSyntaxResult(self.l_angle_token()), + ) + .field("value", &self.value()) + .field( + "r_angle_token", + &support::DebugSyntaxResult(self.r_angle_token()), + ) + .finish() + } else { + f.debug_struct("MdAutolink").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: MdAutolink) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: MdAutolink) -> Self { + n.syntax.into() + } +} +impl AstNode for MdBullet { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(MD_BULLET as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == MD_BULLET + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for MdBullet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("MdBullet") + .field("bullet", &support::DebugSyntaxResult(self.bullet())) + .field("content", &self.content()) + .finish() + } else { + f.debug_struct("MdBullet").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: MdBullet) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: MdBullet) -> Self { + n.syntax.into() + } +} +impl AstNode for MdBulletListItem { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(MD_BULLET_LIST_ITEM as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == MD_BULLET_LIST_ITEM + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for MdBulletListItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("MdBulletListItem") + .field("md_bullet_list", &self.md_bullet_list()) + .finish() + } else { + f.debug_struct("MdBulletListItem").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: MdBulletListItem) -> Self { + n.syntax } - pub fn as_md_html_block(&self) -> Option<&MdHtmlBlock> { - match &self { - Self::MdHtmlBlock(item) => Some(item), - _ => None, - } +} +impl From for SyntaxElement { + fn from(n: MdBulletListItem) -> Self { + n.syntax.into() } - pub fn as_md_inline_code(&self) -> Option<&MdInlineCode> { - match &self { - Self::MdInlineCode(item) => Some(item), - _ => None, - } +} +impl AstNode for MdDocument { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(MD_DOCUMENT as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == MD_DOCUMENT } - pub fn as_md_inline_emphasis(&self) -> Option<&MdInlineEmphasis> { - match &self { - Self::MdInlineEmphasis(item) => Some(item), - _ => None, + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None } } - pub fn as_md_inline_image(&self) -> Option<&MdInlineImage> { - match &self { - Self::MdInlineImage(item) => Some(item), - _ => None, - } + fn syntax(&self) -> &SyntaxNode { + &self.syntax } - pub fn as_md_inline_italic(&self) -> Option<&MdInlineItalic> { - match &self { - Self::MdInlineItalic(item) => Some(item), - _ => None, - } + fn into_syntax(self) -> SyntaxNode { + self.syntax } - pub fn as_md_inline_link(&self) -> Option<&MdInlineLink> { - match &self { - Self::MdInlineLink(item) => Some(item), - _ => None, - } +} +impl std::fmt::Debug for MdDocument { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("MdDocument") + .field( + "bom_token", + &support::DebugOptionalElement(self.bom_token()), + ) + .field("value", &self.value()) + .field("eof_token", &support::DebugSyntaxResult(self.eof_token())) + .finish() + } else { + f.debug_struct("MdDocument").finish() + }; + DEPTH.set(current_depth); + result } - pub fn as_md_soft_break(&self) -> Option<&MdSoftBreak> { - match &self { - Self::MdSoftBreak(item) => Some(item), - _ => None, +} +impl From for SyntaxNode { + fn from(n: MdDocument) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: MdDocument) -> Self { + n.syntax.into() + } +} +impl AstNode for MdEntityReference { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(MD_ENTITY_REFERENCE as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == MD_ENTITY_REFERENCE + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None } } - pub fn as_md_textual(&self) -> Option<&MdTextual> { - match &self { - Self::MdTextual(item) => Some(item), - _ => None, + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for MdEntityReference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("MdEntityReference") + .field( + "value_token", + &support::DebugSyntaxResult(self.value_token()), + ) + .finish() + } else { + f.debug_struct("MdEntityReference").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: MdEntityReference) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: MdEntityReference) -> Self { + n.syntax.into() + } +} +impl AstNode for MdFencedCodeBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(MD_FENCED_CODE_BLOCK as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == MD_FENCED_CODE_BLOCK + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None } } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } } -impl AstNode for MdBullet { +impl std::fmt::Debug for MdFencedCodeBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("MdFencedCodeBlock") + .field("l_fence", &support::DebugSyntaxResult(self.l_fence())) + .field("code_list", &self.code_list()) + .field("content", &self.content()) + .field("r_fence", &support::DebugSyntaxResult(self.r_fence())) + .finish() + } else { + f.debug_struct("MdFencedCodeBlock").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: MdFencedCodeBlock) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: MdFencedCodeBlock) -> Self { + n.syntax.into() + } +} +impl AstNode for MdHardLine { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_BULLET as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_HARD_LINE as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_BULLET + kind == MD_HARD_LINE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1357,43 +2008,88 @@ impl AstNode for MdBullet { self.syntax } } -impl std::fmt::Debug for MdBullet { +impl std::fmt::Debug for MdHardLine { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdBullet") - .field("bullet", &support::DebugSyntaxResult(self.bullet())) + f.debug_struct("MdHardLine") .field( - "space_token", - &support::DebugSyntaxResult(self.space_token()), + "value_token", + &support::DebugSyntaxResult(self.value_token()), ) - .field("content", &self.content()) .finish() } else { - f.debug_struct("MdBullet").finish() + f.debug_struct("MdHardLine").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdBullet) -> Self { +impl From for SyntaxNode { + fn from(n: MdHardLine) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdBullet) -> Self { +impl From for SyntaxElement { + fn from(n: MdHardLine) -> Self { + n.syntax.into() + } +} +impl AstNode for MdHash { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(MD_HASH as u16)); + fn can_cast(kind: SyntaxKind) -> bool { + kind == MD_HASH + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } + fn into_syntax(self) -> SyntaxNode { + self.syntax + } +} +impl std::fmt::Debug for MdHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; + let current_depth = DEPTH.get(); + let result = if current_depth < 16 { + DEPTH.set(current_depth + 1); + f.debug_struct("MdHash") + .field("hash_token", &support::DebugSyntaxResult(self.hash_token())) + .finish() + } else { + f.debug_struct("MdHash").finish() + }; + DEPTH.set(current_depth); + result + } +} +impl From for SyntaxNode { + fn from(n: MdHash) -> Self { + n.syntax + } +} +impl From for SyntaxElement { + fn from(n: MdHash) -> Self { n.syntax.into() } } -impl AstNode for MdBulletListItem { +impl AstNode for MdHeader { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_BULLET_LIST_ITEM as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_HEADER as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_BULLET_LIST_ITEM + kind == MD_HEADER } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1409,38 +2105,40 @@ impl AstNode for MdBulletListItem { self.syntax } } -impl std::fmt::Debug for MdBulletListItem { +impl std::fmt::Debug for MdHeader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdBulletListItem") - .field("md_bullet_list", &self.md_bullet_list()) + f.debug_struct("MdHeader") + .field("before", &self.before()) + .field("content", &support::DebugOptionalElement(self.content())) + .field("after", &self.after()) .finish() } else { - f.debug_struct("MdBulletListItem").finish() + f.debug_struct("MdHeader").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdBulletListItem) -> Self { +impl From for SyntaxNode { + fn from(n: MdHeader) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdBulletListItem) -> Self { +impl From for SyntaxElement { + fn from(n: MdHeader) -> Self { n.syntax.into() } } -impl AstNode for MdDocument { +impl AstNode for MdHtmlBlock { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_DOCUMENT as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_HTML_BLOCK as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_DOCUMENT + kind == MD_HTML_BLOCK } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1456,43 +2154,38 @@ impl AstNode for MdDocument { self.syntax } } -impl std::fmt::Debug for MdDocument { +impl std::fmt::Debug for MdHtmlBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdDocument") - .field( - "bom_token", - &support::DebugOptionalElement(self.bom_token()), - ) - .field("value", &self.value()) - .field("eof_token", &support::DebugSyntaxResult(self.eof_token())) + f.debug_struct("MdHtmlBlock") + .field("content", &self.content()) .finish() } else { - f.debug_struct("MdDocument").finish() + f.debug_struct("MdHtmlBlock").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdDocument) -> Self { +impl From for SyntaxNode { + fn from(n: MdHtmlBlock) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdDocument) -> Self { +impl From for SyntaxElement { + fn from(n: MdHtmlBlock) -> Self { n.syntax.into() } } -impl AstNode for MdFencedCodeBlock { +impl AstNode for MdIndent { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_FENCED_CODE_BLOCK as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INDENT as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_FENCED_CODE_BLOCK + kind == MD_INDENT } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1508,55 +2201,41 @@ impl AstNode for MdFencedCodeBlock { self.syntax } } -impl std::fmt::Debug for MdFencedCodeBlock { +impl std::fmt::Debug for MdIndent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdFencedCodeBlock") - .field( - "l_fence_token", - &support::DebugSyntaxResult(self.l_fence_token()), - ) - .field("code_list", &self.code_list()) - .field( - "l_hard_line", - &support::DebugSyntaxResult(self.l_hard_line()), - ) - .field("content", &support::DebugSyntaxResult(self.content())) - .field( - "r_hard_line", - &support::DebugSyntaxResult(self.r_hard_line()), - ) + f.debug_struct("MdIndent") .field( - "r_fence_token", - &support::DebugSyntaxResult(self.r_fence_token()), + "value_token", + &support::DebugSyntaxResult(self.value_token()), ) .finish() } else { - f.debug_struct("MdFencedCodeBlock").finish() + f.debug_struct("MdIndent").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdFencedCodeBlock) -> Self { +impl From for SyntaxNode { + fn from(n: MdIndent) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdFencedCodeBlock) -> Self { +impl From for SyntaxElement { + fn from(n: MdIndent) -> Self { n.syntax.into() } } -impl AstNode for MdHardLine { +impl AstNode for MdIndentCodeBlock { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_HARD_LINE as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INDENT_CODE_BLOCK as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_HARD_LINE + kind == MD_INDENT_CODE_BLOCK } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1572,41 +2251,38 @@ impl AstNode for MdHardLine { self.syntax } } -impl std::fmt::Debug for MdHardLine { +impl std::fmt::Debug for MdIndentCodeBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdHardLine") - .field( - "value_token", - &support::DebugSyntaxResult(self.value_token()), - ) + f.debug_struct("MdIndentCodeBlock") + .field("content", &self.content()) .finish() } else { - f.debug_struct("MdHardLine").finish() + f.debug_struct("MdIndentCodeBlock").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdHardLine) -> Self { +impl From for SyntaxNode { + fn from(n: MdIndentCodeBlock) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdHardLine) -> Self { +impl From for SyntaxElement { + fn from(n: MdIndentCodeBlock) -> Self { n.syntax.into() } } -impl AstNode for MdHash { +impl AstNode for MdInlineCode { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_HASH as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_CODE as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_HASH + kind == MD_INLINE_CODE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1622,38 +2298,46 @@ impl AstNode for MdHash { self.syntax } } -impl std::fmt::Debug for MdHash { +impl std::fmt::Debug for MdInlineCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdHash") - .field("hash_token", &support::DebugSyntaxResult(self.hash_token())) + f.debug_struct("MdInlineCode") + .field( + "l_tick_token", + &support::DebugSyntaxResult(self.l_tick_token()), + ) + .field("content", &self.content()) + .field( + "r_tick_token", + &support::DebugSyntaxResult(self.r_tick_token()), + ) .finish() } else { - f.debug_struct("MdHash").finish() + f.debug_struct("MdInlineCode").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdHash) -> Self { +impl From for SyntaxNode { + fn from(n: MdInlineCode) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdHash) -> Self { +impl From for SyntaxElement { + fn from(n: MdInlineCode) -> Self { n.syntax.into() } } -impl AstNode for MdHeader { +impl AstNode for MdInlineEmphasis { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_HEADER as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_EMPHASIS as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_HEADER + kind == MD_INLINE_EMPHASIS } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1669,40 +2353,40 @@ impl AstNode for MdHeader { self.syntax } } -impl std::fmt::Debug for MdHeader { +impl std::fmt::Debug for MdInlineEmphasis { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdHeader") - .field("before", &self.before()) - .field("content", &support::DebugOptionalElement(self.content())) - .field("after", &self.after()) + f.debug_struct("MdInlineEmphasis") + .field("l_fence", &support::DebugSyntaxResult(self.l_fence())) + .field("content", &self.content()) + .field("r_fence", &support::DebugSyntaxResult(self.r_fence())) .finish() } else { - f.debug_struct("MdHeader").finish() + f.debug_struct("MdInlineEmphasis").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdHeader) -> Self { +impl From for SyntaxNode { + fn from(n: MdInlineEmphasis) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdHeader) -> Self { +impl From for SyntaxElement { + fn from(n: MdInlineEmphasis) -> Self { n.syntax.into() } } -impl AstNode for MdHtmlBlock { +impl AstNode for MdInlineHtml { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_HTML_BLOCK as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_HTML as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_HTML_BLOCK + kind == MD_INLINE_HTML } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1718,38 +2402,38 @@ impl AstNode for MdHtmlBlock { self.syntax } } -impl std::fmt::Debug for MdHtmlBlock { +impl std::fmt::Debug for MdInlineHtml { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdHtmlBlock") - .field("md_textual", &support::DebugSyntaxResult(self.md_textual())) + f.debug_struct("MdInlineHtml") + .field("value", &self.value()) .finish() } else { - f.debug_struct("MdHtmlBlock").finish() + f.debug_struct("MdInlineHtml").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdHtmlBlock) -> Self { +impl From for SyntaxNode { + fn from(n: MdInlineHtml) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdHtmlBlock) -> Self { +impl From for SyntaxElement { + fn from(n: MdInlineHtml) -> Self { n.syntax.into() } } -impl AstNode for MdIndent { +impl AstNode for MdInlineImage { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INDENT as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_IMAGE as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INDENT + kind == MD_INLINE_IMAGE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1765,41 +2449,57 @@ impl AstNode for MdIndent { self.syntax } } -impl std::fmt::Debug for MdIndent { +impl std::fmt::Debug for MdInlineImage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdIndent") + f.debug_struct("MdInlineImage") + .field("excl_token", &support::DebugSyntaxResult(self.excl_token())) .field( - "value_token", - &support::DebugSyntaxResult(self.value_token()), + "l_brack_token", + &support::DebugSyntaxResult(self.l_brack_token()), + ) + .field("alt", &self.alt()) + .field( + "r_brack_token", + &support::DebugSyntaxResult(self.r_brack_token()), + ) + .field( + "l_paren_token", + &support::DebugSyntaxResult(self.l_paren_token()), + ) + .field("destination", &self.destination()) + .field("title", &support::DebugOptionalElement(self.title())) + .field( + "r_paren_token", + &support::DebugSyntaxResult(self.r_paren_token()), ) .finish() } else { - f.debug_struct("MdIndent").finish() + f.debug_struct("MdInlineImage").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdIndent) -> Self { +impl From for SyntaxNode { + fn from(n: MdInlineImage) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdIndent) -> Self { +impl From for SyntaxElement { + fn from(n: MdInlineImage) -> Self { n.syntax.into() } } -impl AstNode for MdIndentCodeBlock { +impl AstNode for MdInlineItalic { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INDENT_CODE_BLOCK as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_ITALIC as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INDENT_CODE_BLOCK + kind == MD_INLINE_ITALIC } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1815,38 +2515,40 @@ impl AstNode for MdIndentCodeBlock { self.syntax } } -impl std::fmt::Debug for MdIndentCodeBlock { +impl std::fmt::Debug for MdInlineItalic { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdIndentCodeBlock") - .field("lines", &self.lines()) + f.debug_struct("MdInlineItalic") + .field("l_fence", &support::DebugSyntaxResult(self.l_fence())) + .field("content", &self.content()) + .field("r_fence", &support::DebugSyntaxResult(self.r_fence())) .finish() } else { - f.debug_struct("MdIndentCodeBlock").finish() + f.debug_struct("MdInlineItalic").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdIndentCodeBlock) -> Self { +impl From for SyntaxNode { + fn from(n: MdInlineItalic) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdIndentCodeBlock) -> Self { +impl From for SyntaxElement { + fn from(n: MdInlineItalic) -> Self { n.syntax.into() } } -impl AstNode for MdIndentedCodeLine { +impl AstNode for MdInlineLink { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INDENTED_CODE_LINE as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_LINK as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INDENTED_CODE_LINE + kind == MD_INLINE_LINK } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1862,42 +2564,56 @@ impl AstNode for MdIndentedCodeLine { self.syntax } } -impl std::fmt::Debug for MdIndentedCodeLine { +impl std::fmt::Debug for MdInlineLink { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdIndentedCodeLine") + f.debug_struct("MdInlineLink") + .field( + "l_brack_token", + &support::DebugSyntaxResult(self.l_brack_token()), + ) + .field("text", &self.text()) + .field( + "r_brack_token", + &support::DebugSyntaxResult(self.r_brack_token()), + ) + .field( + "l_paren_token", + &support::DebugSyntaxResult(self.l_paren_token()), + ) + .field("destination", &self.destination()) + .field("title", &support::DebugOptionalElement(self.title())) .field( - "indentation", - &support::DebugSyntaxResult(self.indentation()), + "r_paren_token", + &support::DebugSyntaxResult(self.r_paren_token()), ) - .field("content", &support::DebugSyntaxResult(self.content())) .finish() } else { - f.debug_struct("MdIndentedCodeLine").finish() + f.debug_struct("MdInlineLink").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdIndentedCodeLine) -> Self { +impl From for SyntaxNode { + fn from(n: MdInlineLink) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdIndentedCodeLine) -> Self { +impl From for SyntaxElement { + fn from(n: MdInlineLink) -> Self { n.syntax.into() } } -impl AstNode for MdInlineCode { +impl AstNode for MdLinkBlock { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_CODE as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_LINK_BLOCK as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_CODE + kind == MD_LINK_BLOCK } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1913,46 +2629,40 @@ impl AstNode for MdInlineCode { self.syntax } } -impl std::fmt::Debug for MdInlineCode { +impl std::fmt::Debug for MdLinkBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineCode") - .field( - "l_tick_token", - &support::DebugSyntaxResult(self.l_tick_token()), - ) - .field("content", &self.content()) - .field( - "r_tick_token", - &support::DebugSyntaxResult(self.r_tick_token()), - ) + f.debug_struct("MdLinkBlock") + .field("label", &support::DebugSyntaxResult(self.label())) + .field("url", &support::DebugSyntaxResult(self.url())) + .field("title", &support::DebugOptionalElement(self.title())) .finish() } else { - f.debug_struct("MdInlineCode").finish() + f.debug_struct("MdLinkBlock").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineCode) -> Self { +impl From for SyntaxNode { + fn from(n: MdLinkBlock) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineCode) -> Self { +impl From for SyntaxElement { + fn from(n: MdLinkBlock) -> Self { n.syntax.into() } } -impl AstNode for MdInlineEmphasis { +impl AstNode for MdLinkDestination { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_EMPHASIS as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_LINK_DESTINATION as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_EMPHASIS + kind == MD_LINK_DESTINATION } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -1968,40 +2678,38 @@ impl AstNode for MdInlineEmphasis { self.syntax } } -impl std::fmt::Debug for MdInlineEmphasis { +impl std::fmt::Debug for MdLinkDestination { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineEmphasis") - .field("l_fence", &support::DebugSyntaxResult(self.l_fence())) + f.debug_struct("MdLinkDestination") .field("content", &self.content()) - .field("r_fence", &support::DebugSyntaxResult(self.r_fence())) .finish() } else { - f.debug_struct("MdInlineEmphasis").finish() + f.debug_struct("MdLinkDestination").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineEmphasis) -> Self { +impl From for SyntaxNode { + fn from(n: MdLinkDestination) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineEmphasis) -> Self { +impl From for SyntaxElement { + fn from(n: MdLinkDestination) -> Self { n.syntax.into() } } -impl AstNode for MdInlineImage { +impl AstNode for MdLinkLabel { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_IMAGE as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_LINK_LABEL as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_IMAGE + kind == MD_LINK_LABEL } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2017,49 +2725,38 @@ impl AstNode for MdInlineImage { self.syntax } } -impl std::fmt::Debug for MdInlineImage { +impl std::fmt::Debug for MdLinkLabel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineImage") - .field( - "l_brack_token", - &support::DebugSyntaxResult(self.l_brack_token()), - ) - .field("excl_token", &support::DebugSyntaxResult(self.excl_token())) - .field("alt", &support::DebugSyntaxResult(self.alt())) - .field("source", &support::DebugSyntaxResult(self.source())) - .field( - "r_brack_token", - &support::DebugSyntaxResult(self.r_brack_token()), - ) - .field("link", &support::DebugOptionalElement(self.link())) + f.debug_struct("MdLinkLabel") + .field("content", &self.content()) .finish() } else { - f.debug_struct("MdInlineImage").finish() + f.debug_struct("MdLinkLabel").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineImage) -> Self { +impl From for SyntaxNode { + fn from(n: MdLinkLabel) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineImage) -> Self { +impl From for SyntaxElement { + fn from(n: MdLinkLabel) -> Self { n.syntax.into() } } -impl AstNode for MdInlineImageAlt { +impl AstNode for MdLinkReferenceDefinition { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_IMAGE_ALT as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_LINK_REFERENCE_DEFINITION as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_IMAGE_ALT + kind == MD_LINK_REFERENCE_DEFINITION } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2075,46 +2772,55 @@ impl AstNode for MdInlineImageAlt { self.syntax } } -impl std::fmt::Debug for MdInlineImageAlt { +impl std::fmt::Debug for MdLinkReferenceDefinition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineImageAlt") + f.debug_struct("MdLinkReferenceDefinition") .field( "l_brack_token", &support::DebugSyntaxResult(self.l_brack_token()), ) - .field("content", &self.content()) + .field("label", &support::DebugSyntaxResult(self.label())) .field( "r_brack_token", &support::DebugSyntaxResult(self.r_brack_token()), ) + .field( + "colon_token", + &support::DebugSyntaxResult(self.colon_token()), + ) + .field( + "destination", + &support::DebugSyntaxResult(self.destination()), + ) + .field("title", &support::DebugOptionalElement(self.title())) .finish() } else { - f.debug_struct("MdInlineImageAlt").finish() + f.debug_struct("MdLinkReferenceDefinition").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineImageAlt) -> Self { +impl From for SyntaxNode { + fn from(n: MdLinkReferenceDefinition) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineImageAlt) -> Self { +impl From for SyntaxElement { + fn from(n: MdLinkReferenceDefinition) -> Self { n.syntax.into() } } -impl AstNode for MdInlineImageLink { +impl AstNode for MdLinkTitle { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_IMAGE_LINK as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_LINK_TITLE as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_IMAGE_LINK + kind == MD_LINK_TITLE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2130,46 +2836,38 @@ impl AstNode for MdInlineImageLink { self.syntax } } -impl std::fmt::Debug for MdInlineImageLink { +impl std::fmt::Debug for MdLinkTitle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineImageLink") - .field( - "l_paren_token", - &support::DebugSyntaxResult(self.l_paren_token()), - ) + f.debug_struct("MdLinkTitle") .field("content", &self.content()) - .field( - "r_paren_token", - &support::DebugSyntaxResult(self.r_paren_token()), - ) .finish() } else { - f.debug_struct("MdInlineImageLink").finish() + f.debug_struct("MdLinkTitle").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineImageLink) -> Self { +impl From for SyntaxNode { + fn from(n: MdLinkTitle) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineImageLink) -> Self { +impl From for SyntaxElement { + fn from(n: MdLinkTitle) -> Self { n.syntax.into() } } -impl AstNode for MdInlineImageSource { +impl AstNode for MdNewline { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_IMAGE_SOURCE as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_NEWLINE as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_IMAGE_SOURCE + kind == MD_NEWLINE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2185,46 +2883,41 @@ impl AstNode for MdInlineImageSource { self.syntax } } -impl std::fmt::Debug for MdInlineImageSource { +impl std::fmt::Debug for MdNewline { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineImageSource") - .field( - "l_paren_token", - &support::DebugSyntaxResult(self.l_paren_token()), - ) - .field("content", &self.content()) + f.debug_struct("MdNewline") .field( - "r_paren_token", - &support::DebugSyntaxResult(self.r_paren_token()), + "value_token", + &support::DebugSyntaxResult(self.value_token()), ) .finish() } else { - f.debug_struct("MdInlineImageSource").finish() + f.debug_struct("MdNewline").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineImageSource) -> Self { +impl From for SyntaxNode { + fn from(n: MdNewline) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineImageSource) -> Self { +impl From for SyntaxElement { + fn from(n: MdNewline) -> Self { n.syntax.into() } } -impl AstNode for MdInlineItalic { +impl AstNode for MdOrderedListItem { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_ITALIC as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_ORDERED_LIST_ITEM as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_ITALIC + kind == MD_ORDERED_LIST_ITEM } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2240,40 +2933,38 @@ impl AstNode for MdInlineItalic { self.syntax } } -impl std::fmt::Debug for MdInlineItalic { +impl std::fmt::Debug for MdOrderedListItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineItalic") - .field("l_fence", &support::DebugSyntaxResult(self.l_fence())) - .field("content", &self.content()) - .field("r_fence", &support::DebugSyntaxResult(self.r_fence())) + f.debug_struct("MdOrderedListItem") + .field("md_bullet_list", &self.md_bullet_list()) .finish() } else { - f.debug_struct("MdInlineItalic").finish() + f.debug_struct("MdOrderedListItem").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineItalic) -> Self { +impl From for SyntaxNode { + fn from(n: MdOrderedListItem) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineItalic) -> Self { +impl From for SyntaxElement { + fn from(n: MdOrderedListItem) -> Self { n.syntax.into() } } -impl AstNode for MdInlineLink { +impl AstNode for MdParagraph { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INLINE_LINK as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_PARAGRAPH as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INLINE_LINK + kind == MD_PARAGRAPH } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2289,55 +2980,42 @@ impl AstNode for MdInlineLink { self.syntax } } -impl std::fmt::Debug for MdInlineLink { +impl std::fmt::Debug for MdParagraph { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdInlineLink") - .field( - "l_brack_token", - &support::DebugSyntaxResult(self.l_brack_token()), - ) - .field("text", &self.text()) - .field( - "r_brack_token", - &support::DebugSyntaxResult(self.r_brack_token()), - ) - .field( - "l_paren_token", - &support::DebugSyntaxResult(self.l_paren_token()), - ) - .field("source", &self.source()) + f.debug_struct("MdParagraph") + .field("list", &self.list()) .field( - "r_paren_token", - &support::DebugSyntaxResult(self.r_paren_token()), + "hard_line", + &support::DebugOptionalElement(self.hard_line()), ) .finish() } else { - f.debug_struct("MdInlineLink").finish() + f.debug_struct("MdParagraph").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdInlineLink) -> Self { +impl From for SyntaxNode { + fn from(n: MdParagraph) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdInlineLink) -> Self { +impl From for SyntaxElement { + fn from(n: MdParagraph) -> Self { n.syntax.into() } } -impl AstNode for MdLinkBlock { +impl AstNode for MdQuote { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_LINK_BLOCK as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_QUOTE as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_LINK_BLOCK + kind == MD_QUOTE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2353,40 +3031,42 @@ impl AstNode for MdLinkBlock { self.syntax } } -impl std::fmt::Debug for MdLinkBlock { +impl std::fmt::Debug for MdQuote { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdLinkBlock") - .field("label", &support::DebugSyntaxResult(self.label())) - .field("url", &support::DebugSyntaxResult(self.url())) - .field("title", &support::DebugOptionalElement(self.title())) + f.debug_struct("MdQuote") + .field( + "marker_token", + &support::DebugSyntaxResult(self.marker_token()), + ) + .field("content", &self.content()) .finish() } else { - f.debug_struct("MdLinkBlock").finish() + f.debug_struct("MdQuote").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdLinkBlock) -> Self { +impl From for SyntaxNode { + fn from(n: MdQuote) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdLinkBlock) -> Self { +impl From for SyntaxElement { + fn from(n: MdQuote) -> Self { n.syntax.into() } } -impl AstNode for MdOrderListItem { +impl AstNode for MdReferenceImage { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_ORDER_LIST_ITEM as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_REFERENCE_IMAGE as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_ORDER_LIST_ITEM + kind == MD_REFERENCE_IMAGE } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2402,38 +3082,48 @@ impl AstNode for MdOrderListItem { self.syntax } } -impl std::fmt::Debug for MdOrderListItem { +impl std::fmt::Debug for MdReferenceImage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdOrderListItem") - .field("md_bullet_list", &self.md_bullet_list()) + f.debug_struct("MdReferenceImage") + .field("excl_token", &support::DebugSyntaxResult(self.excl_token())) + .field( + "l_brack_token", + &support::DebugSyntaxResult(self.l_brack_token()), + ) + .field("alt", &self.alt()) + .field( + "r_brack_token", + &support::DebugSyntaxResult(self.r_brack_token()), + ) + .field("label", &support::DebugOptionalElement(self.label())) .finish() } else { - f.debug_struct("MdOrderListItem").finish() + f.debug_struct("MdReferenceImage").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdOrderListItem) -> Self { +impl From for SyntaxNode { + fn from(n: MdReferenceImage) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdOrderListItem) -> Self { +impl From for SyntaxElement { + fn from(n: MdReferenceImage) -> Self { n.syntax.into() } } -impl AstNode for MdParagraph { +impl AstNode for MdReferenceLink { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_PARAGRAPH as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_REFERENCE_LINK as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_PARAGRAPH + kind == MD_REFERENCE_LINK } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2449,39 +3139,47 @@ impl AstNode for MdParagraph { self.syntax } } -impl std::fmt::Debug for MdParagraph { +impl std::fmt::Debug for MdReferenceLink { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdParagraph") - .field("list", &self.list()) - .field("hard_line", &support::DebugSyntaxResult(self.hard_line())) + f.debug_struct("MdReferenceLink") + .field( + "l_brack_token", + &support::DebugSyntaxResult(self.l_brack_token()), + ) + .field("text", &self.text()) + .field( + "r_brack_token", + &support::DebugSyntaxResult(self.r_brack_token()), + ) + .field("label", &support::DebugOptionalElement(self.label())) .finish() } else { - f.debug_struct("MdParagraph").finish() + f.debug_struct("MdReferenceLink").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdParagraph) -> Self { +impl From for SyntaxNode { + fn from(n: MdReferenceLink) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdParagraph) -> Self { +impl From for SyntaxElement { + fn from(n: MdReferenceLink) -> Self { n.syntax.into() } } -impl AstNode for MdQuote { +impl AstNode for MdReferenceLinkLabel { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_QUOTE as u16)); + SyntaxKindSet::from_raw(RawSyntaxKind(MD_REFERENCE_LINK_LABEL as u16)); fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_QUOTE + kind == MD_REFERENCE_LINK_LABEL } fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { @@ -2497,32 +3195,37 @@ impl AstNode for MdQuote { self.syntax } } -impl std::fmt::Debug for MdQuote { +impl std::fmt::Debug for MdReferenceLinkLabel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { thread_local! { static DEPTH : std :: cell :: Cell < u8 > = const { std :: cell :: Cell :: new (0) } }; let current_depth = DEPTH.get(); let result = if current_depth < 16 { DEPTH.set(current_depth + 1); - f.debug_struct("MdQuote") + f.debug_struct("MdReferenceLinkLabel") + .field( + "l_brack_token", + &support::DebugSyntaxResult(self.l_brack_token()), + ) + .field("label", &self.label()) .field( - "any_md_block", - &support::DebugSyntaxResult(self.any_md_block()), + "r_brack_token", + &support::DebugSyntaxResult(self.r_brack_token()), ) .finish() } else { - f.debug_struct("MdQuote").finish() + f.debug_struct("MdReferenceLinkLabel").finish() }; DEPTH.set(current_depth); result } } -impl From for SyntaxNode { - fn from(n: MdQuote) -> Self { +impl From for SyntaxNode { + fn from(n: MdReferenceLinkLabel) -> Self { n.syntax } } -impl From for SyntaxElement { - fn from(n: MdQuote) -> Self { +impl From for SyntaxElement { + fn from(n: MdReferenceLinkLabel) -> Self { n.syntax.into() } } @@ -2554,9 +3257,10 @@ impl std::fmt::Debug for MdSetextHeader { let result = if current_depth < 16 { DEPTH.set(current_depth + 1); f.debug_struct("MdSetextHeader") + .field("content", &self.content()) .field( - "md_paragraph", - &support::DebugSyntaxResult(self.md_paragraph()), + "underline_token", + &support::DebugSyntaxResult(self.underline_token()), ) .finish() } else { @@ -2726,17 +3430,75 @@ impl From for SyntaxElement { n.syntax.into() } } -impl From for AnyCodeBlock { +impl AstNode for AnyMdBlock { + type Language = Language; + const KIND_SET: SyntaxKindSet = + AnyMdContainerBlock::KIND_SET.union(AnyMdLeafBlock::KIND_SET); + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + k if AnyMdContainerBlock::can_cast(k) => true, + k if AnyMdLeafBlock::can_cast(k) => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + let syntax = match AnyMdContainerBlock::try_cast(syntax) { + Ok(any_md_container_block) => { + return Some(Self::AnyMdContainerBlock(any_md_container_block)); + } + Err(syntax) => syntax, + }; + if let Some(any_md_leaf_block) = AnyMdLeafBlock::cast(syntax) { + return Some(Self::AnyMdLeafBlock(any_md_leaf_block)); + } + None + } + fn syntax(&self) -> &SyntaxNode { + match self { + Self::AnyMdContainerBlock(it) => it.syntax(), + Self::AnyMdLeafBlock(it) => it.syntax(), + } + } + fn into_syntax(self) -> SyntaxNode { + match self { + Self::AnyMdContainerBlock(it) => it.into_syntax(), + Self::AnyMdLeafBlock(it) => it.into_syntax(), + } + } +} +impl std::fmt::Debug for AnyMdBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::AnyMdContainerBlock(it) => std::fmt::Debug::fmt(it, f), + Self::AnyMdLeafBlock(it) => std::fmt::Debug::fmt(it, f), + } + } +} +impl From for SyntaxNode { + fn from(n: AnyMdBlock) -> Self { + match n { + AnyMdBlock::AnyMdContainerBlock(it) => it.into_syntax(), + AnyMdBlock::AnyMdLeafBlock(it) => it.into_syntax(), + } + } +} +impl From for SyntaxElement { + fn from(n: AnyMdBlock) -> Self { + let node: SyntaxNode = n.into(); + node.into() + } +} +impl From for AnyMdCodeBlock { fn from(node: MdFencedCodeBlock) -> Self { Self::MdFencedCodeBlock(node) } } -impl From for AnyCodeBlock { +impl From for AnyMdCodeBlock { fn from(node: MdIndentCodeBlock) -> Self { Self::MdIndentCodeBlock(node) } } -impl AstNode for AnyCodeBlock { +impl AstNode for AnyMdCodeBlock { type Language = Language; const KIND_SET: SyntaxKindSet = MdFencedCodeBlock::KIND_SET.union(MdIndentCodeBlock::KIND_SET); @@ -2764,7 +3526,7 @@ impl AstNode for AnyCodeBlock { } } } -impl std::fmt::Debug for AnyCodeBlock { +impl std::fmt::Debug for AnyMdCodeBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::MdFencedCodeBlock(it) => std::fmt::Debug::fmt(it, f), @@ -2772,47 +3534,47 @@ impl std::fmt::Debug for AnyCodeBlock { } } } -impl From for SyntaxNode { - fn from(n: AnyCodeBlock) -> Self { +impl From for SyntaxNode { + fn from(n: AnyMdCodeBlock) -> Self { match n { - AnyCodeBlock::MdFencedCodeBlock(it) => it.into_syntax(), - AnyCodeBlock::MdIndentCodeBlock(it) => it.into_syntax(), + AnyMdCodeBlock::MdFencedCodeBlock(it) => it.into_syntax(), + AnyMdCodeBlock::MdIndentCodeBlock(it) => it.into_syntax(), } } } -impl From for SyntaxElement { - fn from(n: AnyCodeBlock) -> Self { +impl From for SyntaxElement { + fn from(n: AnyMdCodeBlock) -> Self { let node: SyntaxNode = n.into(); node.into() } } -impl From for AnyContainerBlock { +impl From for AnyMdContainerBlock { fn from(node: MdBulletListItem) -> Self { Self::MdBulletListItem(node) } } -impl From for AnyContainerBlock { - fn from(node: MdOrderListItem) -> Self { - Self::MdOrderListItem(node) +impl From for AnyMdContainerBlock { + fn from(node: MdOrderedListItem) -> Self { + Self::MdOrderedListItem(node) } } -impl From for AnyContainerBlock { +impl From for AnyMdContainerBlock { fn from(node: MdQuote) -> Self { Self::MdQuote(node) } } -impl AstNode for AnyContainerBlock { +impl AstNode for AnyMdContainerBlock { type Language = Language; const KIND_SET: SyntaxKindSet = MdBulletListItem::KIND_SET - .union(MdOrderListItem::KIND_SET) + .union(MdOrderedListItem::KIND_SET) .union(MdQuote::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, MD_BULLET_LIST_ITEM | MD_ORDER_LIST_ITEM | MD_QUOTE) + matches!(kind, MD_BULLET_LIST_ITEM | MD_ORDERED_LIST_ITEM | MD_QUOTE) } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { MD_BULLET_LIST_ITEM => Self::MdBulletListItem(MdBulletListItem { syntax }), - MD_ORDER_LIST_ITEM => Self::MdOrderListItem(MdOrderListItem { syntax }), + MD_ORDERED_LIST_ITEM => Self::MdOrderedListItem(MdOrderedListItem { syntax }), MD_QUOTE => Self::MdQuote(MdQuote { syntax }), _ => return None, }; @@ -2821,392 +3583,425 @@ impl AstNode for AnyContainerBlock { fn syntax(&self) -> &SyntaxNode { match self { Self::MdBulletListItem(it) => it.syntax(), - Self::MdOrderListItem(it) => it.syntax(), + Self::MdOrderedListItem(it) => it.syntax(), Self::MdQuote(it) => it.syntax(), } } fn into_syntax(self) -> SyntaxNode { match self { Self::MdBulletListItem(it) => it.into_syntax(), - Self::MdOrderListItem(it) => it.into_syntax(), + Self::MdOrderedListItem(it) => it.into_syntax(), Self::MdQuote(it) => it.into_syntax(), } } } -impl std::fmt::Debug for AnyContainerBlock { +impl std::fmt::Debug for AnyMdContainerBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::MdBulletListItem(it) => std::fmt::Debug::fmt(it, f), - Self::MdOrderListItem(it) => std::fmt::Debug::fmt(it, f), + Self::MdOrderedListItem(it) => std::fmt::Debug::fmt(it, f), Self::MdQuote(it) => std::fmt::Debug::fmt(it, f), } } } -impl From for SyntaxNode { - fn from(n: AnyContainerBlock) -> Self { +impl From for SyntaxNode { + fn from(n: AnyMdContainerBlock) -> Self { match n { - AnyContainerBlock::MdBulletListItem(it) => it.into_syntax(), - AnyContainerBlock::MdOrderListItem(it) => it.into_syntax(), - AnyContainerBlock::MdQuote(it) => it.into_syntax(), + AnyMdContainerBlock::MdBulletListItem(it) => it.into_syntax(), + AnyMdContainerBlock::MdOrderedListItem(it) => it.into_syntax(), + AnyMdContainerBlock::MdQuote(it) => it.into_syntax(), } } } -impl From for SyntaxElement { - fn from(n: AnyContainerBlock) -> Self { +impl From for SyntaxElement { + fn from(n: AnyMdContainerBlock) -> Self { let node: SyntaxNode = n.into(); node.into() } } -impl From for AnyLeafBlock { - fn from(node: MdHeader) -> Self { - Self::MdHeader(node) +impl From for AnyMdInline { + fn from(node: MdAutolink) -> Self { + Self::MdAutolink(node) + } +} +impl From for AnyMdInline { + fn from(node: MdEntityReference) -> Self { + Self::MdEntityReference(node) + } +} +impl From for AnyMdInline { + fn from(node: MdHardLine) -> Self { + Self::MdHardLine(node) } } -impl From for AnyLeafBlock { +impl From for AnyMdInline { fn from(node: MdHtmlBlock) -> Self { Self::MdHtmlBlock(node) } } -impl From for AnyLeafBlock { - fn from(node: MdLinkBlock) -> Self { - Self::MdLinkBlock(node) +impl From for AnyMdInline { + fn from(node: MdInlineCode) -> Self { + Self::MdInlineCode(node) } } -impl From for AnyLeafBlock { - fn from(node: MdParagraph) -> Self { - Self::MdParagraph(node) +impl From for AnyMdInline { + fn from(node: MdInlineEmphasis) -> Self { + Self::MdInlineEmphasis(node) } } -impl From for AnyLeafBlock { - fn from(node: MdSetextHeader) -> Self { - Self::MdSetextHeader(node) +impl From for AnyMdInline { + fn from(node: MdInlineHtml) -> Self { + Self::MdInlineHtml(node) } } -impl From for AnyLeafBlock { - fn from(node: MdThematicBreakBlock) -> Self { - Self::MdThematicBreakBlock(node) +impl From for AnyMdInline { + fn from(node: MdInlineImage) -> Self { + Self::MdInlineImage(node) } } -impl AstNode for AnyLeafBlock { - type Language = Language; - const KIND_SET: SyntaxKindSet = AnyCodeBlock::KIND_SET - .union(MdHeader::KIND_SET) - .union(MdHtmlBlock::KIND_SET) - .union(MdLinkBlock::KIND_SET) - .union(MdParagraph::KIND_SET) - .union(MdSetextHeader::KIND_SET) - .union(MdThematicBreakBlock::KIND_SET); - fn can_cast(kind: SyntaxKind) -> bool { - match kind { - MD_HEADER - | MD_HTML_BLOCK - | MD_LINK_BLOCK - | MD_PARAGRAPH - | MD_SETEXT_HEADER - | MD_THEMATIC_BREAK_BLOCK => true, - k if AnyCodeBlock::can_cast(k) => true, - _ => false, - } - } - fn cast(syntax: SyntaxNode) -> Option { - let res = match syntax.kind() { - MD_HEADER => Self::MdHeader(MdHeader { syntax }), - MD_HTML_BLOCK => Self::MdHtmlBlock(MdHtmlBlock { syntax }), - MD_LINK_BLOCK => Self::MdLinkBlock(MdLinkBlock { syntax }), - MD_PARAGRAPH => Self::MdParagraph(MdParagraph { syntax }), - MD_SETEXT_HEADER => Self::MdSetextHeader(MdSetextHeader { syntax }), - MD_THEMATIC_BREAK_BLOCK => Self::MdThematicBreakBlock(MdThematicBreakBlock { syntax }), - _ => { - if let Some(any_code_block) = AnyCodeBlock::cast(syntax) { - return Some(Self::AnyCodeBlock(any_code_block)); - } - return None; - } - }; - Some(res) +impl From for AnyMdInline { + fn from(node: MdInlineItalic) -> Self { + Self::MdInlineItalic(node) } - fn syntax(&self) -> &SyntaxNode { - match self { - Self::MdHeader(it) => it.syntax(), - Self::MdHtmlBlock(it) => it.syntax(), - Self::MdLinkBlock(it) => it.syntax(), - Self::MdParagraph(it) => it.syntax(), - Self::MdSetextHeader(it) => it.syntax(), - Self::MdThematicBreakBlock(it) => it.syntax(), - Self::AnyCodeBlock(it) => it.syntax(), - } +} +impl From for AnyMdInline { + fn from(node: MdInlineLink) -> Self { + Self::MdInlineLink(node) } - fn into_syntax(self) -> SyntaxNode { - match self { - Self::MdHeader(it) => it.into_syntax(), - Self::MdHtmlBlock(it) => it.into_syntax(), - Self::MdLinkBlock(it) => it.into_syntax(), - Self::MdParagraph(it) => it.into_syntax(), - Self::MdSetextHeader(it) => it.into_syntax(), - Self::MdThematicBreakBlock(it) => it.into_syntax(), - Self::AnyCodeBlock(it) => it.into_syntax(), - } +} +impl From for AnyMdInline { + fn from(node: MdReferenceImage) -> Self { + Self::MdReferenceImage(node) } } -impl std::fmt::Debug for AnyLeafBlock { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::AnyCodeBlock(it) => std::fmt::Debug::fmt(it, f), - Self::MdHeader(it) => std::fmt::Debug::fmt(it, f), - Self::MdHtmlBlock(it) => std::fmt::Debug::fmt(it, f), - Self::MdLinkBlock(it) => std::fmt::Debug::fmt(it, f), - Self::MdParagraph(it) => std::fmt::Debug::fmt(it, f), - Self::MdSetextHeader(it) => std::fmt::Debug::fmt(it, f), - Self::MdThematicBreakBlock(it) => std::fmt::Debug::fmt(it, f), - } +impl From for AnyMdInline { + fn from(node: MdReferenceLink) -> Self { + Self::MdReferenceLink(node) } } -impl From for SyntaxNode { - fn from(n: AnyLeafBlock) -> Self { - match n { - AnyLeafBlock::AnyCodeBlock(it) => it.into_syntax(), - AnyLeafBlock::MdHeader(it) => it.into_syntax(), - AnyLeafBlock::MdHtmlBlock(it) => it.into_syntax(), - AnyLeafBlock::MdLinkBlock(it) => it.into_syntax(), - AnyLeafBlock::MdParagraph(it) => it.into_syntax(), - AnyLeafBlock::MdSetextHeader(it) => it.into_syntax(), - AnyLeafBlock::MdThematicBreakBlock(it) => it.into_syntax(), - } +impl From for AnyMdInline { + fn from(node: MdSoftBreak) -> Self { + Self::MdSoftBreak(node) } } -impl From for SyntaxElement { - fn from(n: AnyLeafBlock) -> Self { - let node: SyntaxNode = n.into(); - node.into() +impl From for AnyMdInline { + fn from(node: MdTextual) -> Self { + Self::MdTextual(node) } } -impl AstNode for AnyMdBlock { +impl AstNode for AnyMdInline { type Language = Language; - const KIND_SET: SyntaxKindSet = - AnyContainerBlock::KIND_SET.union(AnyLeafBlock::KIND_SET); + const KIND_SET: SyntaxKindSet = MdAutolink::KIND_SET + .union(MdEntityReference::KIND_SET) + .union(MdHardLine::KIND_SET) + .union(MdHtmlBlock::KIND_SET) + .union(MdInlineCode::KIND_SET) + .union(MdInlineEmphasis::KIND_SET) + .union(MdInlineHtml::KIND_SET) + .union(MdInlineImage::KIND_SET) + .union(MdInlineItalic::KIND_SET) + .union(MdInlineLink::KIND_SET) + .union(MdReferenceImage::KIND_SET) + .union(MdReferenceLink::KIND_SET) + .union(MdSoftBreak::KIND_SET) + .union(MdTextual::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - match kind { - k if AnyContainerBlock::can_cast(k) => true, - k if AnyLeafBlock::can_cast(k) => true, - _ => false, - } + matches!( + kind, + MD_AUTOLINK + | MD_ENTITY_REFERENCE + | MD_HARD_LINE + | MD_HTML_BLOCK + | MD_INLINE_CODE + | MD_INLINE_EMPHASIS + | MD_INLINE_HTML + | MD_INLINE_IMAGE + | MD_INLINE_ITALIC + | MD_INLINE_LINK + | MD_REFERENCE_IMAGE + | MD_REFERENCE_LINK + | MD_SOFT_BREAK + | MD_TEXTUAL + ) } fn cast(syntax: SyntaxNode) -> Option { - let syntax = match AnyContainerBlock::try_cast(syntax) { - Ok(any_container_block) => { - return Some(Self::AnyContainerBlock(any_container_block)); - } - Err(syntax) => syntax, + let res = match syntax.kind() { + MD_AUTOLINK => Self::MdAutolink(MdAutolink { syntax }), + MD_ENTITY_REFERENCE => Self::MdEntityReference(MdEntityReference { syntax }), + MD_HARD_LINE => Self::MdHardLine(MdHardLine { syntax }), + MD_HTML_BLOCK => Self::MdHtmlBlock(MdHtmlBlock { syntax }), + MD_INLINE_CODE => Self::MdInlineCode(MdInlineCode { syntax }), + MD_INLINE_EMPHASIS => Self::MdInlineEmphasis(MdInlineEmphasis { syntax }), + MD_INLINE_HTML => Self::MdInlineHtml(MdInlineHtml { syntax }), + MD_INLINE_IMAGE => Self::MdInlineImage(MdInlineImage { syntax }), + MD_INLINE_ITALIC => Self::MdInlineItalic(MdInlineItalic { syntax }), + MD_INLINE_LINK => Self::MdInlineLink(MdInlineLink { syntax }), + MD_REFERENCE_IMAGE => Self::MdReferenceImage(MdReferenceImage { syntax }), + MD_REFERENCE_LINK => Self::MdReferenceLink(MdReferenceLink { syntax }), + MD_SOFT_BREAK => Self::MdSoftBreak(MdSoftBreak { syntax }), + MD_TEXTUAL => Self::MdTextual(MdTextual { syntax }), + _ => return None, }; - if let Some(any_leaf_block) = AnyLeafBlock::cast(syntax) { - return Some(Self::AnyLeafBlock(any_leaf_block)); - } - None + Some(res) } fn syntax(&self) -> &SyntaxNode { match self { - Self::AnyContainerBlock(it) => it.syntax(), - Self::AnyLeafBlock(it) => it.syntax(), + Self::MdAutolink(it) => it.syntax(), + Self::MdEntityReference(it) => it.syntax(), + Self::MdHardLine(it) => it.syntax(), + Self::MdHtmlBlock(it) => it.syntax(), + Self::MdInlineCode(it) => it.syntax(), + Self::MdInlineEmphasis(it) => it.syntax(), + Self::MdInlineHtml(it) => it.syntax(), + Self::MdInlineImage(it) => it.syntax(), + Self::MdInlineItalic(it) => it.syntax(), + Self::MdInlineLink(it) => it.syntax(), + Self::MdReferenceImage(it) => it.syntax(), + Self::MdReferenceLink(it) => it.syntax(), + Self::MdSoftBreak(it) => it.syntax(), + Self::MdTextual(it) => it.syntax(), } } fn into_syntax(self) -> SyntaxNode { match self { - Self::AnyContainerBlock(it) => it.into_syntax(), - Self::AnyLeafBlock(it) => it.into_syntax(), + Self::MdAutolink(it) => it.into_syntax(), + Self::MdEntityReference(it) => it.into_syntax(), + Self::MdHardLine(it) => it.into_syntax(), + Self::MdHtmlBlock(it) => it.into_syntax(), + Self::MdInlineCode(it) => it.into_syntax(), + Self::MdInlineEmphasis(it) => it.into_syntax(), + Self::MdInlineHtml(it) => it.into_syntax(), + Self::MdInlineImage(it) => it.into_syntax(), + Self::MdInlineItalic(it) => it.into_syntax(), + Self::MdInlineLink(it) => it.into_syntax(), + Self::MdReferenceImage(it) => it.into_syntax(), + Self::MdReferenceLink(it) => it.into_syntax(), + Self::MdSoftBreak(it) => it.into_syntax(), + Self::MdTextual(it) => it.into_syntax(), } } } -impl std::fmt::Debug for AnyMdBlock { +impl std::fmt::Debug for AnyMdInline { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::AnyContainerBlock(it) => std::fmt::Debug::fmt(it, f), - Self::AnyLeafBlock(it) => std::fmt::Debug::fmt(it, f), + Self::MdAutolink(it) => std::fmt::Debug::fmt(it, f), + Self::MdEntityReference(it) => std::fmt::Debug::fmt(it, f), + Self::MdHardLine(it) => std::fmt::Debug::fmt(it, f), + Self::MdHtmlBlock(it) => std::fmt::Debug::fmt(it, f), + Self::MdInlineCode(it) => std::fmt::Debug::fmt(it, f), + Self::MdInlineEmphasis(it) => std::fmt::Debug::fmt(it, f), + Self::MdInlineHtml(it) => std::fmt::Debug::fmt(it, f), + Self::MdInlineImage(it) => std::fmt::Debug::fmt(it, f), + Self::MdInlineItalic(it) => std::fmt::Debug::fmt(it, f), + Self::MdInlineLink(it) => std::fmt::Debug::fmt(it, f), + Self::MdReferenceImage(it) => std::fmt::Debug::fmt(it, f), + Self::MdReferenceLink(it) => std::fmt::Debug::fmt(it, f), + Self::MdSoftBreak(it) => std::fmt::Debug::fmt(it, f), + Self::MdTextual(it) => std::fmt::Debug::fmt(it, f), } } } -impl From for SyntaxNode { - fn from(n: AnyMdBlock) -> Self { +impl From for SyntaxNode { + fn from(n: AnyMdInline) -> Self { match n { - AnyMdBlock::AnyContainerBlock(it) => it.into_syntax(), - AnyMdBlock::AnyLeafBlock(it) => it.into_syntax(), + AnyMdInline::MdAutolink(it) => it.into_syntax(), + AnyMdInline::MdEntityReference(it) => it.into_syntax(), + AnyMdInline::MdHardLine(it) => it.into_syntax(), + AnyMdInline::MdHtmlBlock(it) => it.into_syntax(), + AnyMdInline::MdInlineCode(it) => it.into_syntax(), + AnyMdInline::MdInlineEmphasis(it) => it.into_syntax(), + AnyMdInline::MdInlineHtml(it) => it.into_syntax(), + AnyMdInline::MdInlineImage(it) => it.into_syntax(), + AnyMdInline::MdInlineItalic(it) => it.into_syntax(), + AnyMdInline::MdInlineLink(it) => it.into_syntax(), + AnyMdInline::MdReferenceImage(it) => it.into_syntax(), + AnyMdInline::MdReferenceLink(it) => it.into_syntax(), + AnyMdInline::MdSoftBreak(it) => it.into_syntax(), + AnyMdInline::MdTextual(it) => it.into_syntax(), } } } -impl From for SyntaxElement { - fn from(n: AnyMdBlock) -> Self { +impl From for SyntaxElement { + fn from(n: AnyMdInline) -> Self { let node: SyntaxNode = n.into(); node.into() } } -impl From for AnyMdInline { - fn from(node: MdHardLine) -> Self { - Self::MdHardLine(node) +impl From for AnyMdLeafBlock { + fn from(node: MdHeader) -> Self { + Self::MdHeader(node) } } -impl From for AnyMdInline { +impl From for AnyMdLeafBlock { fn from(node: MdHtmlBlock) -> Self { Self::MdHtmlBlock(node) } } -impl From for AnyMdInline { - fn from(node: MdInlineCode) -> Self { - Self::MdInlineCode(node) - } -} -impl From for AnyMdInline { - fn from(node: MdInlineEmphasis) -> Self { - Self::MdInlineEmphasis(node) +impl From for AnyMdLeafBlock { + fn from(node: MdLinkBlock) -> Self { + Self::MdLinkBlock(node) } } -impl From for AnyMdInline { - fn from(node: MdInlineImage) -> Self { - Self::MdInlineImage(node) +impl From for AnyMdLeafBlock { + fn from(node: MdLinkReferenceDefinition) -> Self { + Self::MdLinkReferenceDefinition(node) } } -impl From for AnyMdInline { - fn from(node: MdInlineItalic) -> Self { - Self::MdInlineItalic(node) +impl From for AnyMdLeafBlock { + fn from(node: MdNewline) -> Self { + Self::MdNewline(node) } } -impl From for AnyMdInline { - fn from(node: MdInlineLink) -> Self { - Self::MdInlineLink(node) +impl From for AnyMdLeafBlock { + fn from(node: MdParagraph) -> Self { + Self::MdParagraph(node) } } -impl From for AnyMdInline { - fn from(node: MdSoftBreak) -> Self { - Self::MdSoftBreak(node) +impl From for AnyMdLeafBlock { + fn from(node: MdSetextHeader) -> Self { + Self::MdSetextHeader(node) } } -impl From for AnyMdInline { - fn from(node: MdTextual) -> Self { - Self::MdTextual(node) +impl From for AnyMdLeafBlock { + fn from(node: MdThematicBreakBlock) -> Self { + Self::MdThematicBreakBlock(node) } } -impl AstNode for AnyMdInline { +impl AstNode for AnyMdLeafBlock { type Language = Language; - const KIND_SET: SyntaxKindSet = MdHardLine::KIND_SET + const KIND_SET: SyntaxKindSet = AnyMdCodeBlock::KIND_SET + .union(MdHeader::KIND_SET) .union(MdHtmlBlock::KIND_SET) - .union(MdInlineCode::KIND_SET) - .union(MdInlineEmphasis::KIND_SET) - .union(MdInlineImage::KIND_SET) - .union(MdInlineItalic::KIND_SET) - .union(MdInlineLink::KIND_SET) - .union(MdSoftBreak::KIND_SET) - .union(MdTextual::KIND_SET); + .union(MdLinkBlock::KIND_SET) + .union(MdLinkReferenceDefinition::KIND_SET) + .union(MdNewline::KIND_SET) + .union(MdParagraph::KIND_SET) + .union(MdSetextHeader::KIND_SET) + .union(MdThematicBreakBlock::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - matches!( - kind, - MD_HARD_LINE - | MD_HTML_BLOCK - | MD_INLINE_CODE - | MD_INLINE_EMPHASIS - | MD_INLINE_IMAGE - | MD_INLINE_ITALIC - | MD_INLINE_LINK - | MD_SOFT_BREAK - | MD_TEXTUAL - ) + match kind { + MD_HEADER + | MD_HTML_BLOCK + | MD_LINK_BLOCK + | MD_LINK_REFERENCE_DEFINITION + | MD_NEWLINE + | MD_PARAGRAPH + | MD_SETEXT_HEADER + | MD_THEMATIC_BREAK_BLOCK => true, + k if AnyMdCodeBlock::can_cast(k) => true, + _ => false, + } } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { - MD_HARD_LINE => Self::MdHardLine(MdHardLine { syntax }), + MD_HEADER => Self::MdHeader(MdHeader { syntax }), MD_HTML_BLOCK => Self::MdHtmlBlock(MdHtmlBlock { syntax }), - MD_INLINE_CODE => Self::MdInlineCode(MdInlineCode { syntax }), - MD_INLINE_EMPHASIS => Self::MdInlineEmphasis(MdInlineEmphasis { syntax }), - MD_INLINE_IMAGE => Self::MdInlineImage(MdInlineImage { syntax }), - MD_INLINE_ITALIC => Self::MdInlineItalic(MdInlineItalic { syntax }), - MD_INLINE_LINK => Self::MdInlineLink(MdInlineLink { syntax }), - MD_SOFT_BREAK => Self::MdSoftBreak(MdSoftBreak { syntax }), - MD_TEXTUAL => Self::MdTextual(MdTextual { syntax }), - _ => return None, + MD_LINK_BLOCK => Self::MdLinkBlock(MdLinkBlock { syntax }), + MD_LINK_REFERENCE_DEFINITION => { + Self::MdLinkReferenceDefinition(MdLinkReferenceDefinition { syntax }) + } + MD_NEWLINE => Self::MdNewline(MdNewline { syntax }), + MD_PARAGRAPH => Self::MdParagraph(MdParagraph { syntax }), + MD_SETEXT_HEADER => Self::MdSetextHeader(MdSetextHeader { syntax }), + MD_THEMATIC_BREAK_BLOCK => Self::MdThematicBreakBlock(MdThematicBreakBlock { syntax }), + _ => { + if let Some(any_md_code_block) = AnyMdCodeBlock::cast(syntax) { + return Some(Self::AnyMdCodeBlock(any_md_code_block)); + } + return None; + } }; Some(res) } fn syntax(&self) -> &SyntaxNode { match self { - Self::MdHardLine(it) => it.syntax(), + Self::MdHeader(it) => it.syntax(), Self::MdHtmlBlock(it) => it.syntax(), - Self::MdInlineCode(it) => it.syntax(), - Self::MdInlineEmphasis(it) => it.syntax(), - Self::MdInlineImage(it) => it.syntax(), - Self::MdInlineItalic(it) => it.syntax(), - Self::MdInlineLink(it) => it.syntax(), - Self::MdSoftBreak(it) => it.syntax(), - Self::MdTextual(it) => it.syntax(), + Self::MdLinkBlock(it) => it.syntax(), + Self::MdLinkReferenceDefinition(it) => it.syntax(), + Self::MdNewline(it) => it.syntax(), + Self::MdParagraph(it) => it.syntax(), + Self::MdSetextHeader(it) => it.syntax(), + Self::MdThematicBreakBlock(it) => it.syntax(), + Self::AnyMdCodeBlock(it) => it.syntax(), } } fn into_syntax(self) -> SyntaxNode { match self { - Self::MdHardLine(it) => it.into_syntax(), + Self::MdHeader(it) => it.into_syntax(), Self::MdHtmlBlock(it) => it.into_syntax(), - Self::MdInlineCode(it) => it.into_syntax(), - Self::MdInlineEmphasis(it) => it.into_syntax(), - Self::MdInlineImage(it) => it.into_syntax(), - Self::MdInlineItalic(it) => it.into_syntax(), - Self::MdInlineLink(it) => it.into_syntax(), - Self::MdSoftBreak(it) => it.into_syntax(), - Self::MdTextual(it) => it.into_syntax(), + Self::MdLinkBlock(it) => it.into_syntax(), + Self::MdLinkReferenceDefinition(it) => it.into_syntax(), + Self::MdNewline(it) => it.into_syntax(), + Self::MdParagraph(it) => it.into_syntax(), + Self::MdSetextHeader(it) => it.into_syntax(), + Self::MdThematicBreakBlock(it) => it.into_syntax(), + Self::AnyMdCodeBlock(it) => it.into_syntax(), } } } -impl std::fmt::Debug for AnyMdInline { +impl std::fmt::Debug for AnyMdLeafBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::MdHardLine(it) => std::fmt::Debug::fmt(it, f), + Self::AnyMdCodeBlock(it) => std::fmt::Debug::fmt(it, f), + Self::MdHeader(it) => std::fmt::Debug::fmt(it, f), Self::MdHtmlBlock(it) => std::fmt::Debug::fmt(it, f), - Self::MdInlineCode(it) => std::fmt::Debug::fmt(it, f), - Self::MdInlineEmphasis(it) => std::fmt::Debug::fmt(it, f), - Self::MdInlineImage(it) => std::fmt::Debug::fmt(it, f), - Self::MdInlineItalic(it) => std::fmt::Debug::fmt(it, f), - Self::MdInlineLink(it) => std::fmt::Debug::fmt(it, f), - Self::MdSoftBreak(it) => std::fmt::Debug::fmt(it, f), - Self::MdTextual(it) => std::fmt::Debug::fmt(it, f), + Self::MdLinkBlock(it) => std::fmt::Debug::fmt(it, f), + Self::MdLinkReferenceDefinition(it) => std::fmt::Debug::fmt(it, f), + Self::MdNewline(it) => std::fmt::Debug::fmt(it, f), + Self::MdParagraph(it) => std::fmt::Debug::fmt(it, f), + Self::MdSetextHeader(it) => std::fmt::Debug::fmt(it, f), + Self::MdThematicBreakBlock(it) => std::fmt::Debug::fmt(it, f), } } } -impl From for SyntaxNode { - fn from(n: AnyMdInline) -> Self { +impl From for SyntaxNode { + fn from(n: AnyMdLeafBlock) -> Self { match n { - AnyMdInline::MdHardLine(it) => it.into_syntax(), - AnyMdInline::MdHtmlBlock(it) => it.into_syntax(), - AnyMdInline::MdInlineCode(it) => it.into_syntax(), - AnyMdInline::MdInlineEmphasis(it) => it.into_syntax(), - AnyMdInline::MdInlineImage(it) => it.into_syntax(), - AnyMdInline::MdInlineItalic(it) => it.into_syntax(), - AnyMdInline::MdInlineLink(it) => it.into_syntax(), - AnyMdInline::MdSoftBreak(it) => it.into_syntax(), - AnyMdInline::MdTextual(it) => it.into_syntax(), + AnyMdLeafBlock::AnyMdCodeBlock(it) => it.into_syntax(), + AnyMdLeafBlock::MdHeader(it) => it.into_syntax(), + AnyMdLeafBlock::MdHtmlBlock(it) => it.into_syntax(), + AnyMdLeafBlock::MdLinkBlock(it) => it.into_syntax(), + AnyMdLeafBlock::MdLinkReferenceDefinition(it) => it.into_syntax(), + AnyMdLeafBlock::MdNewline(it) => it.into_syntax(), + AnyMdLeafBlock::MdParagraph(it) => it.into_syntax(), + AnyMdLeafBlock::MdSetextHeader(it) => it.into_syntax(), + AnyMdLeafBlock::MdThematicBreakBlock(it) => it.into_syntax(), } } } -impl From for SyntaxElement { - fn from(n: AnyMdInline) -> Self { +impl From for SyntaxElement { + fn from(n: AnyMdLeafBlock) -> Self { let node: SyntaxNode = n.into(); node.into() } } -impl std::fmt::Display for AnyCodeBlock { +impl std::fmt::Display for AnyMdBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for AnyContainerBlock { +impl std::fmt::Display for AnyMdCodeBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for AnyLeafBlock { +impl std::fmt::Display for AnyMdContainerBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for AnyMdBlock { +impl std::fmt::Display for AnyMdInline { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for AnyMdInline { +impl std::fmt::Display for AnyMdLeafBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for MdAutolink { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } @@ -3226,6 +4021,11 @@ impl std::fmt::Display for MdDocument { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for MdEntityReference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for MdFencedCodeBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -3261,17 +4061,17 @@ impl std::fmt::Display for MdIndentCodeBlock { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdIndentedCodeLine { +impl std::fmt::Display for MdInlineCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdInlineCode { +impl std::fmt::Display for MdInlineEmphasis { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdInlineEmphasis { +impl std::fmt::Display for MdInlineHtml { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } @@ -3281,37 +4081,47 @@ impl std::fmt::Display for MdInlineImage { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdInlineImageAlt { +impl std::fmt::Display for MdInlineItalic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for MdInlineLink { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdInlineImageLink { +impl std::fmt::Display for MdLinkBlock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdInlineImageSource { +impl std::fmt::Display for MdLinkDestination { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdInlineItalic { +impl std::fmt::Display for MdLinkLabel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdInlineLink { +impl std::fmt::Display for MdLinkReferenceDefinition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdLinkBlock { +impl std::fmt::Display for MdLinkTitle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for MdNewline { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } -impl std::fmt::Display for MdOrderListItem { +impl std::fmt::Display for MdOrderedListItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } @@ -3326,6 +4136,21 @@ impl std::fmt::Display for MdQuote { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for MdReferenceImage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for MdReferenceLink { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} +impl std::fmt::Display for MdReferenceLinkLabel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for MdSetextHeader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -3619,7 +4444,7 @@ impl Serialize for MdCodeNameList { seq.end() } } -impl AstSeparatedList for MdCodeNameList { +impl AstNodeList for MdCodeNameList { type Language = Language; type Node = MdTextual; fn syntax_list(&self) -> &SyntaxList { @@ -3632,19 +4457,19 @@ impl AstSeparatedList for MdCodeNameList { impl Debug for MdCodeNameList { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str("MdCodeNameList ")?; - f.debug_list().entries(self.elements()).finish() + f.debug_list().entries(self.iter()).finish() } } -impl IntoIterator for MdCodeNameList { - type Item = SyntaxResult; - type IntoIter = AstSeparatedListNodesIterator; +impl IntoIterator for &MdCodeNameList { + type Item = MdTextual; + type IntoIter = AstNodeListIterator; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl IntoIterator for &MdCodeNameList { - type Item = SyntaxResult; - type IntoIter = AstSeparatedListNodesIterator; +impl IntoIterator for MdCodeNameList { + type Item = MdTextual; + type IntoIter = AstNodeListIterator; fn into_iter(self) -> Self::IntoIter { self.iter() } @@ -3732,88 +4557,6 @@ impl IntoIterator for MdHashList { } } #[derive(Clone, Eq, PartialEq, Hash)] -pub struct MdIndentedCodeLineList { - syntax_list: SyntaxList, -} -impl MdIndentedCodeLineList { - #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] - #[doc = r" or a match on [SyntaxNode::kind]"] - #[inline] - pub unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { - Self { - syntax_list: syntax.into_list(), - } - } -} -impl AstNode for MdIndentedCodeLineList { - type Language = Language; - const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_INDENTED_CODE_LINE_LIST as u16)); - fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_INDENTED_CODE_LINE_LIST - } - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { - syntax_list: syntax.into_list(), - }) - } else { - None - } - } - fn syntax(&self) -> &SyntaxNode { - self.syntax_list.node() - } - fn into_syntax(self) -> SyntaxNode { - self.syntax_list.into_node() - } -} -impl Serialize for MdIndentedCodeLineList { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(self.len()))?; - for e in self.iter() { - seq.serialize_element(&e)?; - } - seq.end() - } -} -impl AstNodeList for MdIndentedCodeLineList { - type Language = Language; - type Node = MdIndentedCodeLine; - fn syntax_list(&self) -> &SyntaxList { - &self.syntax_list - } - fn into_syntax_list(self) -> SyntaxList { - self.syntax_list - } -} -impl Debug for MdIndentedCodeLineList { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("MdIndentedCodeLineList ")?; - f.debug_list().entries(self.iter()).finish() - } -} -impl IntoIterator for &MdIndentedCodeLineList { - type Item = MdIndentedCodeLine; - type IntoIter = AstNodeListIterator; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} -impl IntoIterator for MdIndentedCodeLineList { - type Item = MdIndentedCodeLine; - type IntoIter = AstNodeListIterator; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} -#[derive(Clone, Eq, PartialEq, Hash)] pub struct MdInlineItemList { syntax_list: SyntaxList, } @@ -3895,88 +4638,6 @@ impl IntoIterator for MdInlineItemList { self.iter() } } -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct MdOrderList { - syntax_list: SyntaxList, -} -impl MdOrderList { - #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] - #[doc = r" or a match on [SyntaxNode::kind]"] - #[inline] - pub unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { - Self { - syntax_list: syntax.into_list(), - } - } -} -impl AstNode for MdOrderList { - type Language = Language; - const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(MD_ORDER_LIST as u16)); - fn can_cast(kind: SyntaxKind) -> bool { - kind == MD_ORDER_LIST - } - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { - syntax_list: syntax.into_list(), - }) - } else { - None - } - } - fn syntax(&self) -> &SyntaxNode { - self.syntax_list.node() - } - fn into_syntax(self) -> SyntaxNode { - self.syntax_list.into_node() - } -} -impl Serialize for MdOrderList { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(self.len()))?; - for e in self.iter() { - seq.serialize_element(&e)?; - } - seq.end() - } -} -impl AstNodeList for MdOrderList { - type Language = Language; - type Node = AnyCodeBlock; - fn syntax_list(&self) -> &SyntaxList { - &self.syntax_list - } - fn into_syntax_list(self) -> SyntaxList { - self.syntax_list - } -} -impl Debug for MdOrderList { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("MdOrderList ")?; - f.debug_list().entries(self.iter()).finish() - } -} -impl IntoIterator for &MdOrderList { - type Item = AnyCodeBlock; - type IntoIter = AstNodeListIterator; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} -impl IntoIterator for MdOrderList { - type Item = AnyCodeBlock; - type IntoIter = AstNodeListIterator; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} #[derive(Clone)] pub struct DebugSyntaxElementChildren(pub SyntaxElementChildren); impl Debug for DebugSyntaxElementChildren { diff --git a/crates/biome_markdown_syntax/src/generated/nodes_mut.rs b/crates/biome_markdown_syntax/src/generated/nodes_mut.rs index 74939ef00364..d44555c5be76 100644 --- a/crates/biome_markdown_syntax/src/generated/nodes_mut.rs +++ b/crates/biome_markdown_syntax/src/generated/nodes_mut.rs @@ -3,23 +3,37 @@ use crate::{MarkdownSyntaxToken as SyntaxToken, generated::nodes::*}; use biome_rowan::AstNode; use std::iter::once; -impl MdBullet { - pub fn with_bullet_token(self, element: SyntaxToken) -> Self { +impl MdAutolink { + pub fn with_l_angle_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_space_token(self, element: SyntaxToken) -> Self { + pub fn with_value(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(1usize..=1usize, once(Some(element.into()))), + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_content(self, element: MdInlineItemList) -> Self { + pub fn with_r_angle_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + .splice_slots(2usize..=2usize, once(Some(element.into()))), + ) + } +} +impl MdBullet { + pub fn with_bullet_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_content(self, element: MdBlockList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } } @@ -51,6 +65,14 @@ impl MdDocument { ) } } +impl MdEntityReference { + pub fn with_value_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } +} impl MdFencedCodeBlock { pub fn with_l_fence_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( @@ -64,28 +86,16 @@ impl MdFencedCodeBlock { .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_l_hard_line(self, element: MdHardLine) -> Self { + pub fn with_content(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_content(self, element: MdTextual) -> Self { - Self::unwrap_cast( - self.syntax - .splice_slots(3usize..=3usize, once(Some(element.into_syntax().into()))), - ) - } - pub fn with_r_hard_line(self, element: MdHardLine) -> Self { - Self::unwrap_cast( - self.syntax - .splice_slots(4usize..=4usize, once(Some(element.into_syntax().into()))), - ) - } pub fn with_r_fence_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(5usize..=5usize, once(Some(element.into()))), + .splice_slots(3usize..=3usize, once(Some(element.into()))), ) } } @@ -126,7 +136,7 @@ impl MdHeader { } } impl MdHtmlBlock { - pub fn with_md_textual(self, element: MdTextual) -> Self { + pub fn with_content(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), @@ -142,26 +152,12 @@ impl MdIndent { } } impl MdIndentCodeBlock { - pub fn with_lines(self, element: MdIndentedCodeLineList) -> Self { - Self::unwrap_cast( - self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), - ) - } -} -impl MdIndentedCodeLine { - pub fn with_indentation(self, element: MdIndent) -> Self { + pub fn with_content(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_content(self, element: MdTextual) -> Self { - Self::unwrap_cast( - self.syntax - .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), - ) - } } impl MdInlineCode { pub fn with_l_tick_token(self, element: SyntaxToken) -> Self { @@ -203,46 +199,66 @@ impl MdInlineEmphasis { ) } } +impl MdInlineHtml { + pub fn with_value(self, element: MdInlineItemList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + ) + } +} impl MdInlineImage { - pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { + pub fn with_excl_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_excl_token(self, element: SyntaxToken) -> Self { + pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(1usize..=1usize, once(Some(element.into()))), ) } - pub fn with_alt(self, element: MdInlineImageAlt) -> Self { + pub fn with_alt(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_source(self, element: MdInlineImageSource) -> Self { + pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(3usize..=3usize, once(Some(element.into_syntax().into()))), + .splice_slots(3usize..=3usize, once(Some(element.into()))), ) } - pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { + pub fn with_l_paren_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(4usize..=4usize, once(Some(element.into()))), ) } - pub fn with_link(self, element: Option) -> Self { + pub fn with_destination(self, element: MdInlineItemList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(5usize..=5usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_title(self, element: Option) -> Self { Self::unwrap_cast(self.syntax.splice_slots( - 5usize..=5usize, + 6usize..=6usize, once(element.map(|element| element.into_syntax().into())), )) } + pub fn with_r_paren_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(7usize..=7usize, once(Some(element.into()))), + ) + } } -impl MdInlineImageAlt { - pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { +impl MdInlineItalic { + pub fn with_l_fence_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into()))), @@ -254,81 +270,101 @@ impl MdInlineImageAlt { .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { + pub fn with_r_fence_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(2usize..=2usize, once(Some(element.into()))), ) } } -impl MdInlineImageLink { - pub fn with_l_paren_token(self, element: SyntaxToken) -> Self { +impl MdInlineLink { + pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_content(self, element: MdInlineItemList) -> Self { + pub fn with_text(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_r_paren_token(self, element: SyntaxToken) -> Self { + pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(2usize..=2usize, once(Some(element.into()))), ) } -} -impl MdInlineImageSource { pub fn with_l_paren_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into()))), + .splice_slots(3usize..=3usize, once(Some(element.into()))), ) } - pub fn with_content(self, element: MdInlineItemList) -> Self { + pub fn with_destination(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + .splice_slots(4usize..=4usize, once(Some(element.into_syntax().into()))), ) } + pub fn with_title(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 5usize..=5usize, + once(element.map(|element| element.into_syntax().into())), + )) + } pub fn with_r_paren_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(2usize..=2usize, once(Some(element.into()))), + .splice_slots(6usize..=6usize, once(Some(element.into()))), ) } } -impl MdInlineItalic { - pub fn with_l_fence_token(self, element: SyntaxToken) -> Self { +impl MdLinkBlock { + pub fn with_label(self, element: MdTextual) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into()))), + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_content(self, element: MdInlineItemList) -> Self { + pub fn with_url(self, element: MdTextual) -> Self { Self::unwrap_cast( self.syntax .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_r_fence_token(self, element: SyntaxToken) -> Self { + pub fn with_title(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 2usize..=2usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl MdLinkDestination { + pub fn with_content(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(2usize..=2usize, once(Some(element.into()))), + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } } -impl MdInlineLink { +impl MdLinkLabel { + pub fn with_content(self, element: MdInlineItemList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + ) + } +} +impl MdLinkReferenceDefinition { pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_text(self, element: MdInlineItemList) -> Self { + pub fn with_label(self, element: MdLinkLabel) -> Self { Self::unwrap_cast( self.syntax .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), @@ -340,82 +376,168 @@ impl MdInlineLink { .splice_slots(2usize..=2usize, once(Some(element.into()))), ) } - pub fn with_l_paren_token(self, element: SyntaxToken) -> Self { + pub fn with_colon_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax .splice_slots(3usize..=3usize, once(Some(element.into()))), ) } - pub fn with_source(self, element: MdInlineItemList) -> Self { + pub fn with_destination(self, element: MdLinkDestination) -> Self { Self::unwrap_cast( self.syntax .splice_slots(4usize..=4usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_r_paren_token(self, element: SyntaxToken) -> Self { + pub fn with_title(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 5usize..=5usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl MdLinkTitle { + pub fn with_content(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(5usize..=5usize, once(Some(element.into()))), + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } } -impl MdLinkBlock { - pub fn with_label(self, element: MdTextual) -> Self { +impl MdNewline { + pub fn with_value_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } +} +impl MdOrderedListItem { + pub fn with_md_bullet_list(self, element: MdBulletList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_url(self, element: MdTextual) -> Self { +} +impl MdParagraph { + pub fn with_list(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } - pub fn with_title(self, element: Option) -> Self { + pub fn with_hard_line(self, element: Option) -> Self { Self::unwrap_cast(self.syntax.splice_slots( - 2usize..=2usize, + 1usize..=1usize, once(element.map(|element| element.into_syntax().into())), )) } } -impl MdOrderListItem { - pub fn with_md_bullet_list(self, element: MdBulletList) -> Self { +impl MdQuote { + pub fn with_marker_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_content(self, element: MdBlockList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } } -impl MdParagraph { - pub fn with_list(self, element: MdInlineItemList) -> Self { +impl MdReferenceImage { + pub fn with_excl_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + .splice_slots(0usize..=0usize, once(Some(element.into()))), ) } - pub fn with_hard_line(self, element: MdHardLine) -> Self { + pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } + pub fn with_alt(self, element: MdInlineItemList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(3usize..=3usize, once(Some(element.into()))), + ) + } + pub fn with_label(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 4usize..=4usize, + once(element.map(|element| element.into_syntax().into())), + )) + } +} +impl MdReferenceLink { + pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_text(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), ) } + pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into()))), + ) + } + pub fn with_label(self, element: Option) -> Self { + Self::unwrap_cast(self.syntax.splice_slots( + 3usize..=3usize, + once(element.map(|element| element.into_syntax().into())), + )) + } } -impl MdQuote { - pub fn with_any_md_block(self, element: AnyMdBlock) -> Self { +impl MdReferenceLinkLabel { + pub fn with_l_brack_token(self, element: SyntaxToken) -> Self { Self::unwrap_cast( self.syntax - .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + .splice_slots(0usize..=0usize, once(Some(element.into()))), + ) + } + pub fn with_label(self, element: MdInlineItemList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_r_brack_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(2usize..=2usize, once(Some(element.into()))), ) } } impl MdSetextHeader { - pub fn with_md_paragraph(self, element: MdParagraph) -> Self { + pub fn with_content(self, element: MdInlineItemList) -> Self { Self::unwrap_cast( self.syntax .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), ) } + pub fn with_underline_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } } impl MdSoftBreak { pub fn with_value_token(self, element: SyntaxToken) -> Self { diff --git a/crates/biome_markdown_syntax/src/lib.rs b/crates/biome_markdown_syntax/src/lib.rs index de2f97a4c428..6f351969d198 100644 --- a/crates/biome_markdown_syntax/src/lib.rs +++ b/crates/biome_markdown_syntax/src/lib.rs @@ -37,7 +37,7 @@ impl SyntaxKind for MarkdownSyntaxKind { } fn is_root(&self) -> bool { - todo!() + matches!(self, Self::MD_DOCUMENT) } fn is_list(&self) -> bool { @@ -45,7 +45,9 @@ impl SyntaxKind for MarkdownSyntaxKind { } fn is_trivia(self) -> bool { - matches!(self, Self::NEWLINE | Self::WHITESPACE | Self::TAB) + // Markdown is markup: whitespace is syntactic, and NEWLINE is explicit. + // We intentionally avoid trivia for whitespace so it becomes part of text. + false } fn to_string(&self) -> Option<&'static str> { @@ -56,16 +58,7 @@ impl SyntaxKind for MarkdownSyntaxKind { impl TryFrom for TriviaPieceKind { type Error = (); - fn try_from(value: MarkdownSyntaxKind) -> Result { - if value.is_trivia() { - match value { - MarkdownSyntaxKind::NEWLINE => Ok(Self::Newline), - MarkdownSyntaxKind::WHITESPACE => Ok(Self::Whitespace), - MarkdownSyntaxKind::TAB => Ok(Self::Skipped), - _ => unreachable!("Not Trivia"), - } - } else { - Err(()) - } + fn try_from(_value: MarkdownSyntaxKind) -> Result { + Err(()) } } diff --git a/crates/biome_migrate/src/analyzers/all.rs b/crates/biome_migrate/src/analyzers/all.rs index 987a3cb6f357..d77bf467a5e3 100644 --- a/crates/biome_migrate/src/analyzers/all.rs +++ b/crates/biome_migrate/src/analyzers/all.rs @@ -23,7 +23,7 @@ impl Rule for RulesAll { let node = ctx.query(); let name = node.name().ok()?; - let node_text = name.inner_string_text().ok()?; + let node_text = name.inner_string_text()?.ok()?; if node_text.text() == "all" { return Some(node.clone()); } diff --git a/crates/biome_migrate/src/analyzers/ignore_scanner.rs b/crates/biome_migrate/src/analyzers/ignore_scanner.rs index ba24988d9521..b70c1b2686b8 100644 --- a/crates/biome_migrate/src/analyzers/ignore_scanner.rs +++ b/crates/biome_migrate/src/analyzers/ignore_scanner.rs @@ -28,7 +28,7 @@ impl Rule for IgnoreScanner { let member = ctx.query(); let name = member.name().ok()?; - if name.inner_string_text().ok()?.text() != "experimentalScannerIgnores" { + if name.inner_string_text()?.ok()?.text() != "experimentalScannerIgnores" { return None; } @@ -85,7 +85,7 @@ impl Rule for IgnoreScanner { .iter() .flatten() .filter_map(|member| { - let text = member.name().ok()?.inner_string_text().ok()?; + let text = member.name().ok()?.inner_string_text()?.ok()?; if text.text() == "experimentalScannerIgnores" { None } else if text.text() == "includes" { @@ -140,7 +140,7 @@ impl Rule for IgnoreScanner { token(T![']']), ); files_list.push(json_member( - json_member_name(json_string_literal("includes")), + json_member_name(json_string_literal("includes")).into(), token(T![:]), AnyJsonValue::JsonArrayValue(value), )) diff --git a/crates/biome_migrate/src/analyzers/includes.rs b/crates/biome_migrate/src/analyzers/includes.rs index 7621e8dfa87c..ba8abe2ea8af 100644 --- a/crates/biome_migrate/src/analyzers/includes.rs +++ b/crates/biome_migrate/src/analyzers/includes.rs @@ -29,7 +29,12 @@ impl Rule for Includes { }; let mut result = Vec::default(); for root_member in root.json_member_list().into_iter().flatten() { - let Ok(name) = root_member.name().and_then(|name| name.inner_string_text()) else { + let Some(name) = root_member + .name() + .ok() + .and_then(|name| name.inner_string_text()) + .and_then(|r| r.ok()) + else { continue; }; match name.text() { @@ -90,7 +95,7 @@ impl Rule for Includes { } fn action(ctx: &RuleContext, state: &Self::State) -> Option { - let includes_name = make::json_member_name(make::json_string_literal("includes")); + let includes_name = make::json_member_name(make::json_string_literal("includes")).into(); let includes_array = AnyJsonValue::JsonArrayValue(state.to_includes()); let mut mutation = ctx.root().begin(); if let Some(include) = &state.include { @@ -206,10 +211,14 @@ impl TryFrom for State { fn try_from(value: JsonObjectValue) -> Result { let mut result = Self::default(); for member in value.json_member_list().into_iter().flatten() { - let member_name = member.name().and_then(|name| name.inner_string_text()); - if member_name.as_ref().is_ok_and(|name| name == &"include") { + let member_name = member + .name() + .ok() + .and_then(|name| name.inner_string_text()) + .and_then(|r| r.ok()); + if member_name.as_ref().is_some_and(|name| name == &"include") { result.include = Some(member); - } else if member_name.as_ref().is_ok_and(|name| name == &"ignore") { + } else if member_name.as_ref().is_some_and(|name| name == &"ignore") { result.ignore = Some(member); } } diff --git a/crates/biome_migrate/src/analyzers/monorepo.rs b/crates/biome_migrate/src/analyzers/monorepo.rs index c869a8d52d85..fe99a704942b 100644 --- a/crates/biome_migrate/src/analyzers/monorepo.rs +++ b/crates/biome_migrate/src/analyzers/monorepo.rs @@ -67,7 +67,7 @@ impl Rule for Monorepo { let mut list: VecDeque<_> = member_list.iter().flatten().collect(); list.push_front(json_member( - json_member_name(json_string_literal("root")), + json_member_name(json_string_literal("root")).into(), token(T![:]).with_trailing_trivia(vec![(TriviaPieceKind::Whitespace, " ")]), AnyJsonValue::JsonBooleanValue(json_boolean_value(token(T![false]))), )); diff --git a/crates/biome_migrate/src/analyzers/no_restriected_globals.rs b/crates/biome_migrate/src/analyzers/no_restriected_globals.rs index 3c0481230160..5723b8e271a2 100644 --- a/crates/biome_migrate/src/analyzers/no_restriected_globals.rs +++ b/crates/biome_migrate/src/analyzers/no_restriected_globals.rs @@ -25,7 +25,7 @@ impl Rule for NoRestrictedGlobals { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); - if node.name().ok()?.inner_string_text().ok()?.text() != "noRestrictedGlobals" { + if node.name().ok()?.inner_string_text()?.ok()?.text() != "noRestrictedGlobals" { return None; } @@ -73,7 +73,7 @@ impl Rule for NoRestrictedGlobals { let value = value.with_trailing_trivia_pieces([])?; Some(make::json_member( - make::json_member_name(value.value_token().ok()?), + make::json_member_name(value.value_token().ok()?).into(), make::token(T![:]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), make::json_string_value(make::json_string_literal( "TODO: Add a custom message here.", @@ -116,7 +116,8 @@ fn find_json_member_by_name(members: JsonMemberList, name: &str) -> Option bool { for member_val in object.json_member_list().into_iter().flatten() { if member_val .name() + .ok() .and_then(|val| val.inner_string_text()) - .is_ok_and(|val| val.text() == "enabled") + .and_then(|r| r.ok()) + .is_some_and(|val| val.text() == "enabled") && let Ok(AnyJsonValue::JsonBooleanValue(enabled)) = member_val.value() && let Ok(enabled) = enabled.value_token() { diff --git a/crates/biome_migrate/src/analyzers/rule_mover.rs b/crates/biome_migrate/src/analyzers/rule_mover.rs index 4924093c007c..523297e33404 100644 --- a/crates/biome_migrate/src/analyzers/rule_mover.rs +++ b/crates/biome_migrate/src/analyzers/rule_mover.rs @@ -67,11 +67,15 @@ impl Rule for RuleMover { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); - let member_name = node.name().and_then(|name| name.inner_string_text()); + let member_name = node + .name() + .ok() + .and_then(|name| name.inner_string_text()) + .and_then(|r| r.ok()); let is_linter_rules = member_name .as_ref() - .is_ok_and(|name| name.text() == "rules"); - let is_assist_actions = member_name.is_ok_and(|name| name.text() == "actions"); + .is_some_and(|name| name.text() == "rules"); + let is_assist_actions = member_name.is_some_and(|name| name.text() == "actions"); if !is_linter_rules && !is_assist_actions { return Box::default(); } @@ -83,7 +87,12 @@ impl Rule for RuleMover { let Ok(AnyJsonValue::JsonObjectValue(group_rules)) = group_elt.value() else { continue; }; - let Ok(group_name) = group_elt.name().and_then(|name| name.inner_string_text()) else { + let Some(group_name) = group_elt + .name() + .ok() + .and_then(|name| name.inner_string_text()) + .and_then(|r| r.ok()) + else { continue; }; if is_linter_rules { @@ -91,7 +100,11 @@ impl Rule for RuleMover { continue; }; for rule_node in group_rules.json_member_list().into_iter().flatten() { - let Ok(rule_name) = rule_node.name().and_then(|name| name.inner_string_text()) + let Some(rule_name) = rule_node + .name() + .ok() + .and_then(|name| name.inner_string_text()) + .and_then(|r| r.ok()) else { continue; }; @@ -128,7 +141,11 @@ impl Rule for RuleMover { continue; }; for rule_node in group_rules.json_member_list().into_iter().flatten() { - let Ok(rule_name) = rule_node.name().and_then(|name| name.inner_string_text()) + let Some(rule_name) = rule_node + .name() + .ok() + .and_then(|name| name.inner_string_text()) + .and_then(|r| r.ok()) else { continue; }; @@ -236,7 +253,12 @@ impl Rule for RuleMover { let mut is_rule_migrated = false; for elt in old_list.elements() { let node = elt.node.ok()?; - if let Ok(name) = node.name().and_then(|name| name.inner_string_text()) { + if let Some(name) = node + .name() + .ok() + .and_then(|name| name.inner_string_text()) + .and_then(|r| r.ok()) + { if name.text() == new_rule_name { // This happens when: // - the rule has already been manually migrated, but the old one was not removed @@ -299,7 +321,8 @@ impl Rule for RuleMover { let new_group_node = make::json_member( make::json_member_name( make::json_string_literal(new_group_name).prepend_trivia_pieces(indent.clone()), - ), + ) + .into(), make::token(T![:]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), make::json_object_value( make::token(T!['{']).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), @@ -464,12 +487,12 @@ fn transform_value(value: AnyJsonValue, old_rule_name: &'static str) -> Option
    Option AnyJsonValue { if let AnyJsonValue::JsonObjectValue(obj) = &value { for item in obj.json_member_list().into_iter().flatten() { - let text = item.name().and_then(|n| n.inner_string_text()); - if text.is_ok_and(|name| name.text() == "level") { + let text = item + .name() + .ok() + .and_then(|n| n.inner_string_text()) + .and_then(|r| r.ok()); + if text.is_some_and(|name| name.text() == "level") { return item.value().unwrap_or(value); } } @@ -497,7 +524,7 @@ fn get_rule_level(value: AnyJsonValue) -> AnyJsonValue { /// Creates the member for `noConsoleLog` fn create_console_log_options() -> AnyJsonValue { let allow_option = make::json_member( - make::json_member_name(make::json_string_literal("allow")), + make::json_member_name(make::json_string_literal("allow")).into(), make::token(T![:]).with_trailing_trivia([(TriviaPieceKind::Whitespace, " ")]), make::json_array_value( make::token(T!['[']), @@ -521,7 +548,7 @@ fn create_console_log_options() -> AnyJsonValue { fn create_shorthand_array_type_options() -> AnyJsonValue { let syntax_option = make::json_member( - make::json_member_name(make::json_string_literal("syntax")), + make::json_member_name(make::json_string_literal("syntax")).into(), make::token(T![:]).with_trailing_trivia(vec![(TriviaPieceKind::Whitespace, " ")]), make::json_string_value( make::json_string_literal("shorthand") diff --git a/crates/biome_migrate/src/analyzers/schema.rs b/crates/biome_migrate/src/analyzers/schema.rs index b43443856ca7..6406fdc07767 100644 --- a/crates/biome_migrate/src/analyzers/schema.rs +++ b/crates/biome_migrate/src/analyzers/schema.rs @@ -29,7 +29,7 @@ impl Rule for Schema { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); - let node_text = node.name().ok()?.inner_string_text().ok()?; + let node_text = node.name().ok()?.inner_string_text()?.ok()?; let member_value = node.value().ok()?; if node_text.text() == "$schema" { let string_value = member_value.as_json_string_value()?; diff --git a/crates/biome_migrate/src/analyzers/trailing_comma.rs b/crates/biome_migrate/src/analyzers/trailing_comma.rs index 7add647aa15f..f6888528a1e1 100644 --- a/crates/biome_migrate/src/analyzers/trailing_comma.rs +++ b/crates/biome_migrate/src/analyzers/trailing_comma.rs @@ -4,7 +4,7 @@ use biome_analyze::{Ast, Rule, RuleAction, RuleDiagnostic}; use biome_console::markup; use biome_diagnostics::{Applicability, category}; use biome_json_factory::make::{json_member_name, json_string_literal}; -use biome_json_syntax::{JsonMember, JsonMemberName}; +use biome_json_syntax::{AnyJsonMemberName, JsonMember}; use biome_rowan::{AstNode, BatchMutationExt}; declare_migration! { @@ -16,7 +16,7 @@ declare_migration! { impl Rule for TrailingComma { type Query = Ast; - type State = JsonMemberName; + type State = AnyJsonMemberName; type Signals = Option; type Options = (); @@ -24,7 +24,7 @@ impl Rule for TrailingComma { let node = ctx.query(); let name = node.name().ok()?; - let node_text = name.inner_string_text().ok()?; + let node_text = name.inner_string_text()?.ok()?; if node_text.text() == "trailingComma" { return Some(name); } @@ -45,7 +45,7 @@ impl Rule for TrailingComma { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let mut mutation = ctx.root().begin(); - let new_name = json_member_name(json_string_literal("trailingCommas")); + let new_name = json_member_name(json_string_literal("trailingCommas")).into(); mutation.replace_node(state.clone(), new_name); Some(RuleAction::new( diff --git a/crates/biome_migrate/src/analyzers/use_naming_convention_enum_member_case.rs b/crates/biome_migrate/src/analyzers/use_naming_convention_enum_member_case.rs index 81c89610737b..8cca79d76312 100644 --- a/crates/biome_migrate/src/analyzers/use_naming_convention_enum_member_case.rs +++ b/crates/biome_migrate/src/analyzers/use_naming_convention_enum_member_case.rs @@ -23,7 +23,7 @@ impl Rule for UseNamingConventionEnumMemberCase { fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); let name = node.name().ok()?; - let text = name.inner_string_text().ok()?; + let text = name.inner_string_text()?.ok()?; if text.text() == "enumMemberCase" { let value = node.value().ok()?; let value = value.as_json_string_value()?.inner_string_text().ok()?; @@ -52,12 +52,12 @@ impl Rule for UseNamingConventionEnumMemberCase { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let selector_kind = make::json_member( - make::json_member_name(make::json_string_literal("kind")), + make::json_member_name(make::json_string_literal("kind")).into(), make::token(T![:]), make::json_string_value(make::json_string_literal("enumMember")).into(), ); let selector = make::json_member( - make::json_member_name(make::json_string_literal("selector")), + make::json_member_name(make::json_string_literal("selector")).into(), make::token(T![:]), make::json_object_value( make::token(T!['{']), @@ -67,7 +67,7 @@ impl Rule for UseNamingConventionEnumMemberCase { .into(), ); let formats = make::json_member( - make::json_member_name(make::json_string_literal("formats")), + make::json_member_name(make::json_string_literal("formats")).into(), make::token(T![:]), make::json_array_value( make::token(T!['[']), @@ -89,7 +89,7 @@ impl Rule for UseNamingConventionEnumMemberCase { let parent = node.parent::()?; let conventions = parent.into_iter().find_map(|member| { let member = member.ok()?; - let member_name = member.name().ok()?.inner_string_text().ok()?; + let member_name = member.name().ok()?.inner_string_text()?.ok()?; if member_name.text() == "conventions" && let Ok(AnyJsonValue::JsonArrayValue(conventions)) = member.value() { @@ -132,7 +132,7 @@ impl Rule for UseNamingConventionEnumMemberCase { make::token(T![']']), ); let conventions = make::json_member( - make::json_member_name(make::json_string_literal("conventions")), + make::json_member_name(make::json_string_literal("conventions")).into(), make::token(T![:]), conventions_array.into(), ); diff --git a/crates/biome_migrate/tests/specs/migrations/all/group_level.json.snap b/crates/biome_migrate/tests/specs/migrations/all/group_level.json.snap index d86714a14a65..593c8ee77a75 100644 --- a/crates/biome_migrate/tests/specs/migrations/all/group_level.json.snap +++ b/crates/biome_migrate/tests/specs/migrations/all/group_level.json.snap @@ -1,7 +1,6 @@ --- source: crates/biome_migrate/tests/spec_tests.rs expression: group_level.json -snapshot_kind: text --- # Input ```json diff --git a/crates/biome_migrate/tests/specs/migrations/all/top_level.json.snap b/crates/biome_migrate/tests/specs/migrations/all/top_level.json.snap index 8e11fe89f1cd..6e761585a455 100644 --- a/crates/biome_migrate/tests/specs/migrations/all/top_level.json.snap +++ b/crates/biome_migrate/tests/specs/migrations/all/top_level.json.snap @@ -1,7 +1,6 @@ --- source: crates/biome_migrate/tests/spec_tests.rs expression: top_level.json -snapshot_kind: text --- # Input ```json diff --git a/crates/biome_migrate/tests/specs/migrations/trailingComma/invalid.json.snap b/crates/biome_migrate/tests/specs/migrations/trailingComma/invalid.json.snap index 497e6f5d3c23..65400f410e4b 100644 --- a/crates/biome_migrate/tests/specs/migrations/trailingComma/invalid.json.snap +++ b/crates/biome_migrate/tests/specs/migrations/trailingComma/invalid.json.snap @@ -1,7 +1,6 @@ --- source: crates/biome_migrate/tests/spec_tests.rs expression: invalid.json -snapshot_kind: text --- # Input ```json diff --git a/crates/biome_module_graph/benches/module_graph.rs b/crates/biome_module_graph/benches/module_graph.rs index 9da270cb0492..20e59ec9b1ca 100644 --- a/crates/biome_module_graph/benches/module_graph.rs +++ b/crates/biome_module_graph/benches/module_graph.rs @@ -73,6 +73,7 @@ fn bench_index_d_ts(bencher: Bencher, name: &str) { &fs, &ProjectLayout::default(), &[(&path, root)], + true, ); divan::black_box(&module_graph); }); diff --git a/crates/biome_module_graph/src/css_module_info/visitor.rs b/crates/biome_module_graph/src/css_module_info/visitor.rs index a4de1fbb0366..9076b426d837 100644 --- a/crates/biome_module_graph/src/css_module_info/visitor.rs +++ b/crates/biome_module_graph/src/css_module_info/visitor.rs @@ -1,6 +1,6 @@ use crate::css_module_info::{CssImport, CssImports, CssModuleInfo}; use crate::module_graph::ModuleGraphFsProxy; -use biome_css_syntax::{AnyCssImportUrl, CssRoot}; +use biome_css_syntax::{AnyCssImportUrl, AnyCssRoot}; use biome_resolver::{ResolveOptions, ResolvedPath, resolve}; use biome_rowan::{AstNode, Text, WalkEvent}; use camino::Utf8Path; @@ -9,14 +9,14 @@ use std::ops::DerefMut; pub const SUPPORTED_EXTENSIONS: &[&str] = &["css"]; pub(crate) struct CssModuleVisitor<'a> { - root: CssRoot, + root: AnyCssRoot, directory: &'a Utf8Path, fs_proxy: &'a ModuleGraphFsProxy<'a>, } impl<'a> CssModuleVisitor<'a> { pub(crate) fn new( - root: CssRoot, + root: AnyCssRoot, directory: &'a Utf8Path, fs_proxy: &'a ModuleGraphFsProxy, ) -> Self { diff --git a/crates/biome_module_graph/src/js_module_info.rs b/crates/biome_module_graph/src/js_module_info.rs index 100f6f048fdc..84f9a407d84d 100644 --- a/crates/biome_module_graph/src/js_module_info.rs +++ b/crates/biome_module_graph/src/js_module_info.rs @@ -207,6 +207,9 @@ pub struct JsModuleInfoInner { /// Diagnostics emitted during the resolution of the module pub(crate) diagnostics: Vec, + + /// Whether type inference was enabled when this module info was created + pub(crate) infer_types: bool, } #[derive(Debug, Default)] diff --git a/crates/biome_module_graph/src/js_module_info/collector.rs b/crates/biome_module_graph/src/js_module_info/collector.rs index d86fde17d8a0..f099ad114707 100644 --- a/crates/biome_module_graph/src/js_module_info/collector.rs +++ b/crates/biome_module_graph/src/js_module_info/collector.rs @@ -100,6 +100,9 @@ pub(super) struct JsModuleInfoCollector { /// Diagnostics emitted during the collection of module graph information diagnostics: Vec, + + /// Whether to enable type inference when finalizing the module info + infer_types: bool, } /// Intermediary representation for an exported symbol. @@ -556,14 +559,16 @@ impl JsModuleInfoCollector { .collect(), ); - self.infer_all_types(&scope_by_range); - self.resolve_all_and_downgrade_project_references(); + if self.infer_types { + self.infer_all_types(&scope_by_range); + self.resolve_all_and_downgrade_project_references(); - // Purging before flattening will save us from duplicate work during - // flattening. We'll purge again after for a final cleanup. - self.purge_redundant_types(); - self.flatten_all(); - self.purge_redundant_types(); + // Purging before flattening will save us from duplicate work during + // flattening. We'll purge again after for a final cleanup. + self.purge_redundant_types(); + self.flatten_all(); + self.purge_redundant_types(); + } let exports = self.collect_exports(); @@ -1114,7 +1119,8 @@ impl TypeResolver for JsModuleInfoCollector { } impl JsModuleInfo { - pub(super) fn new(mut collector: JsModuleInfoCollector) -> Self { + pub(super) fn new(mut collector: JsModuleInfoCollector, infer_types: bool) -> Self { + collector.infer_types = infer_types; let (exports, scope_by_range) = collector.finalise(); Self(Arc::new(JsModuleInfoInner { @@ -1129,6 +1135,7 @@ impl JsModuleInfo { scope_by_range, types: collector.types.into(), diagnostics: collector.diagnostics.into_iter().map(Into::into).collect(), + infer_types: collector.infer_types, })) } } diff --git a/crates/biome_module_graph/src/js_module_info/module_resolver.rs b/crates/biome_module_graph/src/js_module_info/module_resolver.rs index df52749be10b..f0de32b3887b 100644 --- a/crates/biome_module_graph/src/js_module_info/module_resolver.rs +++ b/crates/biome_module_graph/src/js_module_info/module_resolver.rs @@ -74,19 +74,26 @@ pub struct ModuleResolver { impl ModuleResolver { pub fn for_module(module_info: JsModuleInfo, module_graph: Arc) -> Self { - let num_initial_types = module_info.types.len(); + let infer_types = module_info.infer_types; + let types = if infer_types { + TypeStore::with_capacity(module_info.types.len()) + } else { + TypeStore::default() + }; let mut resolver = Self { module_graph, modules: vec![module_info], modules_by_path: Default::default(), expressions: Default::default(), - types: TypeStore::with_capacity(num_initial_types), + types, type_id_map: Default::default(), diagnostics: Default::default(), }; - resolver.run_inference(); + if infer_types { + resolver.run_inference(); + } resolver } diff --git a/crates/biome_module_graph/src/js_module_info/visitor.rs b/crates/biome_module_graph/src/js_module_info/visitor.rs index 625523a5231b..1ebea296422c 100644 --- a/crates/biome_module_graph/src/js_module_info/visitor.rs +++ b/crates/biome_module_graph/src/js_module_info/visitor.rs @@ -30,14 +30,21 @@ pub(crate) struct JsModuleVisitor<'a> { root: AnyJsRoot, directory: &'a Utf8Path, fs_proxy: &'a ModuleGraphFsProxy<'a>, + infer_types: bool, } impl<'a> JsModuleVisitor<'a> { - pub fn new(root: AnyJsRoot, directory: &'a Utf8Path, fs_proxy: &'a ModuleGraphFsProxy) -> Self { + pub fn new( + root: AnyJsRoot, + directory: &'a Utf8Path, + fs_proxy: &'a ModuleGraphFsProxy, + infer_types: bool, + ) -> Self { Self { root, directory, fs_proxy, + infer_types, } } @@ -62,7 +69,7 @@ impl<'a> JsModuleVisitor<'a> { } } - JsModuleInfo::new(collector) + JsModuleInfo::new(collector, self.infer_types) } fn visit_import(&self, node: AnyJsImportLike, collector: &mut JsModuleInfoCollector) { diff --git a/crates/biome_module_graph/src/module_graph.rs b/crates/biome_module_graph/src/module_graph.rs index 0b67b2e60bb5..b994e6976e41 100644 --- a/crates/biome_module_graph/src/module_graph.rs +++ b/crates/biome_module_graph/src/module_graph.rs @@ -15,7 +15,7 @@ use crate::{ JsExport, JsModuleInfo, JsOwnExport, ModuleDiagnostic, SerializedJsModuleInfo, js_module_info::JsModuleVisitor, }; -use biome_css_syntax::CssRoot; +use biome_css_syntax::AnyCssRoot; use biome_fs::BiomePath; use biome_js_syntax::AnyJsRoot; use biome_js_type_info::ImportSymbol; @@ -82,6 +82,7 @@ impl ModuleGraph { fs: &dyn FsWithResolverProxy, project_layout: &ProjectLayout, added_or_updated_paths: &[(&BiomePath, AnyJsRoot)], + enable_type_inference: bool, ) -> (ModuleDependencies, Vec) { // Make sure all directories are registered for the added/updated paths. let path_info = self.path_info.pin(); @@ -109,7 +110,8 @@ impl ModuleGraph { let modules = self.data.pin(); for (path, root) in added_or_updated_paths { let directory = path.parent().unwrap_or(path); - let visitor = JsModuleVisitor::new(root.clone(), directory, &fs_proxy); + let visitor = + JsModuleVisitor::new(root.clone(), directory, &fs_proxy, enable_type_inference); let module_info = visitor.collect_info(); for import_path in module_info.all_import_paths() { @@ -132,7 +134,7 @@ impl ModuleGraph { &self, fs: &dyn FsWithResolverProxy, project_layout: &ProjectLayout, - added_or_updated_paths: &[(&BiomePath, CssRoot)], + added_or_updated_paths: &[(&BiomePath, AnyCssRoot)], _semantic_model: Option<&biome_css_semantic::model::SemanticModel>, ) -> (ModuleDependencies, Vec) { // Make sure all directories are registered for the added/updated paths. diff --git a/crates/biome_module_graph/tests/snap/mod.rs b/crates/biome_module_graph/tests/snap/mod.rs index ce01583fde61..54a3d580d3fb 100644 --- a/crates/biome_module_graph/tests/snap/mod.rs +++ b/crates/biome_module_graph/tests/snap/mod.rs @@ -57,7 +57,7 @@ impl<'a> ModuleGraphSnapshot<'a> { source_type, JsParserOptions::default(), ); - let formatted = format_node(JsFormatOptions::default(), tree.tree().syntax()) + let formatted = format_node(JsFormatOptions::default(), tree.tree().syntax(), false) .unwrap() .print() .unwrap(); diff --git a/crates/biome_module_graph/tests/snapshots/test_export_const_type_declaration_with_namespace.snap b/crates/biome_module_graph/tests/snapshots/test_export_const_type_declaration_with_namespace.snap index d69ef85bdacc..2cb8bee70023 100644 --- a/crates/biome_module_graph/tests/snapshots/test_export_const_type_declaration_with_namespace.snap +++ b/crates/biome_module_graph/tests/snapshots/test_export_const_type_declaration_with_namespace.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.d.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_export_default_function_declaration.snap b/crates/biome_module_graph/tests/snapshots/test_export_default_function_declaration.snap index 7eb6e7fcb698..ad618acfcaba 100644 --- a/crates/biome_module_graph/tests/snapshots/test_export_default_function_declaration.snap +++ b/crates/biome_module_graph/tests/snapshots/test_export_default_function_declaration.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_export_referenced_function.snap b/crates/biome_module_graph/tests/snapshots/test_export_referenced_function.snap index 4cd6635bef55..b2f603cca9db 100644 --- a/crates/biome_module_graph/tests/snapshots/test_export_referenced_function.snap +++ b/crates/biome_module_graph/tests/snapshots/test_export_referenced_function.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_export_type_referencing_imported_type.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_export_type_referencing_imported_type.snap index a30d23160637..ed0342ec5435 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_export_type_referencing_imported_type.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_export_type_referencing_imported_type.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_export_types.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_export_types.snap index 51332c148f49..ec5584bd7e22 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_export_types.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_export_types.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_exports.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_exports.snap index 3b0acefd68b0..ecefa5438958 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_exports.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_exports.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_generic_mapped_value.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_generic_mapped_value.snap index bc39112e9e84..70109d1984f9 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_generic_mapped_value.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_generic_mapped_value.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value.snap index 254f62006456..fdd1f1b05056 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value_with_multiple_modules.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value_with_multiple_modules.snap index 06dfdaacdd2a..f84847ea12c1 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value_with_multiple_modules.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_generic_return_value_with_multiple_modules.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/bar.ts` (Module 1) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_import_as_namespace.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_import_as_namespace.snap index ef2c757e3e2d..b85caa6d0079 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_import_as_namespace.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_import_as_namespace.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_merged_namespace_with_type.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_merged_namespace_with_type.snap index e0277512abc0..a64e93d77326 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_merged_namespace_with_type.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_merged_namespace_with_type.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_merged_types.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_merged_types.snap index 262570f9324c..616409f52b83 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_merged_types.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_merged_types.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_multiple_reexports.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_multiple_reexports.snap index ead9bfb9c374..f38c9decbb6c 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_multiple_reexports.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_multiple_reexports.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/bar.ts` (Module 3) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_nested_function_call_with_namespace_in_return_type.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_nested_function_call_with_namespace_in_return_type.snap index cfb4249fdf64..9afe943deac5 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_nested_function_call_with_namespace_in_return_type.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_nested_function_call_with_namespace_in_return_type.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_promise_export.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_promise_export.snap index e56399b4070b..878f26e62809 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_promise_export.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_promise_export.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_imported_promise_type.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_imported_promise_type.snap index b32e0eee8dab..58388cef516c 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_imported_promise_type.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_imported_promise_type.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_reexported_promise_type.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_reexported_promise_type.snap index cbe9ea539729..99290da016d1 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_reexported_promise_type.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_promise_from_imported_function_returning_reexported_promise_type.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/reexport.ts` (Module 2) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_country_info.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_country_info.snap index 01ec4ac81a89..9e7d7f25640c 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_country_info.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_country_info.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/node_modules/@types/iso-3166-2/index.d.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_vfile.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_vfile.snap index 035cc0d7ca68..62df37df09b5 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_vfile.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_recursive_looking_vfile.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/node_modules/vfile/types/index.d.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_single_reexport.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_single_reexport.snap index 5fd4bcd90924..7b3bbe305559 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_single_reexport.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_single_reexport.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/reexport.ts` (Module 1) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_destructured_field_of_intersection_of_interfaces.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_destructured_field_of_intersection_of_interfaces.snap index ccce880e8aef..e8acbdf68026 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_destructured_field_of_intersection_of_interfaces.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_destructured_field_of_intersection_of_interfaces.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_intersection_of_interfaces.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_intersection_of_interfaces.snap index 4c8a137abbf1..398b89ac6d98 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_intersection_of_interfaces.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_intersection_of_interfaces.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_property_with_getter.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_property_with_getter.snap index 23c9a3e81d2a..5a210fd1a5cf 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_property_with_getter.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_property_with_getter.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_assign.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_assign.snap index 9bcd07788b57..8578e5721857 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_assign.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_assign.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_export.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_export.snap index 31551c2de8dc..b9618d953d24 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_export.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_export.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_plain.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_plain.snap index 8d5558bed11c..9554deda5543 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_plain.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_plain.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_wrong_scope.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_wrong_scope.snap index 7e4a7d8ca188..41028118ccbe 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_wrong_scope.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_class_wrong_scope.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_object.snap b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_object.snap index 0fd8d535820c..19830eead3b0 100644 --- a/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_object.snap +++ b/crates/biome_module_graph/tests/snapshots/test_resolve_type_of_this_in_object.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `/src/index.ts` ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment.snap b/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment.snap index 7ed3b4a42d88..e7c44b369ca0 100644 --- a/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment.snap +++ b/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment_multiple_values.snap b/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment_multiple_values.snap index f08a8f1b7b36..8590daf4d94f 100644 --- a/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment_multiple_values.snap +++ b/crates/biome_module_graph/tests/snapshots/test_widening_via_assignment_multiple_values.snap @@ -2,6 +2,7 @@ source: crates/biome_module_graph/tests/snap/mod.rs expression: content --- + # `index.ts` (Not imported by resolver) ## Source diff --git a/crates/biome_module_graph/tests/spec_tests.rs b/crates/biome_module_graph/tests/spec_tests.rs index d38db9c30c97..59f24fcac61e 100644 --- a/crates/biome_module_graph/tests/spec_tests.rs +++ b/crates/biome_module_graph/tests/spec_tests.rs @@ -14,14 +14,13 @@ use biome_jsdoc_comment::JsdocComment; use biome_json_parser::{JsonParserOptions, parse_json}; use biome_json_value::{JsonObject, JsonString}; use biome_module_graph::{ - CssImport, ImportSymbol, JsExport, JsImport, JsImportPath, JsImportPhase, - JsModuleInfoDiagnostic, JsReexport, ModuleDiagnostic, ModuleGraph, ModuleResolver, - ResolvedPath, + ImportSymbol, JsExport, JsImport, JsImportPath, JsImportPhase, JsModuleInfoDiagnostic, + JsReexport, ModuleDiagnostic, ModuleGraph, ModuleResolver, ResolvedPath, }; use biome_package::{Dependencies, PackageJson}; use biome_project_layout::ProjectLayout; use biome_rowan::Text; -use biome_test_utils::{get_added_paths, get_css_added_paths}; +use biome_test_utils::get_added_js_paths; use camino::{Utf8Path, Utf8PathBuf}; use walkdir::WalkDir; @@ -137,10 +136,10 @@ fn test_type_flattening_does_not_explode_on_recursive_parent_element_pattern() { let project_layout = ProjectLayout::default(); let added_paths = [BiomePath::new("/src/repro.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let data = module_graph.data(); let module = data.get(Utf8Path::new("/src/repro.ts")).unwrap(); @@ -162,10 +161,10 @@ fn test_resolve_relative_import() { BiomePath::new("/src/index.ts"), BiomePath::new("/src/bar.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let imports = module_graph.data(); let file_imports = imports.get(Utf8Path::new("/src/index.ts")).unwrap(); @@ -189,10 +188,10 @@ fn test_resolve_package_import() { BiomePath::new("/src/index.ts"), BiomePath::new("/node_modules/shared/dist/index.js"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let imports = module_graph.data(); let file_imports = imports.get(Utf8Path::new("/src/index.ts")).unwrap(); @@ -216,10 +215,10 @@ fn test_import_through_path_alias() { BiomePath::new("/src/index.ts"), BiomePath::new("/src/components/Hello.tsx"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let imports = module_graph.data(); let file_imports = imports.get(Utf8Path::new("/src/index.ts")).unwrap(); @@ -292,10 +291,10 @@ fn test_resolve_package_import_in_monorepo_fixtures() { )), BiomePath::new(format!("{fixtures_path}/shared/dist/index.js")), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let imports = module_graph.data(); let file_imports = imports @@ -340,10 +339,10 @@ fn test_export_referenced_function() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); @@ -365,10 +364,10 @@ fn test_export_default_function_declaration() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_export_default_function_declaration"); @@ -393,10 +392,10 @@ fn test_export_const_type_declaration_with_namespace() { ); let added_paths = [BiomePath::new("/src/index.d.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_export_const_type_declaration_with_namespace"); @@ -480,10 +479,10 @@ fn test_resolve_exports() { BiomePath::new("/src/reexports.ts"), BiomePath::new("/src/renamed-reexports.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let dependency_data = module_graph.data(); let data = dependency_data.get(Utf8Path::new("/src/index.ts")).unwrap(); @@ -590,10 +589,10 @@ fn test_resolve_export_types() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_resolve_export_types"); @@ -618,10 +617,10 @@ export const promise = makePromiseCb(); ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -651,10 +650,10 @@ fn test_resolve_generic_mapped_value() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -713,10 +712,10 @@ fn test_resolve_generic_return_value_with_multiple_modules() { BiomePath::new("/src/bar.ts"), BiomePath::new("/src/index.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -760,10 +759,10 @@ fn test_resolve_import_as_namespace() { BiomePath::new("/src/foo.ts"), BiomePath::new("/src/index.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -804,10 +803,10 @@ fn test_resolve_nested_function_call_with_namespace_in_return_type() { BiomePath::new("/src/foo.ts"), BiomePath::new("/src/index.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -837,10 +836,10 @@ fn test_resolve_return_value_of_function() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -895,10 +894,10 @@ fn test_resolve_type_of_property_with_getter() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -988,10 +987,10 @@ fn class_this_test_helper(case_name: &str, prefix: &str) { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1063,10 +1062,10 @@ fn test_resolve_type_of_this_in_object() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1155,10 +1154,10 @@ fn test_resolve_type_of_this_in_class_wrong_scope() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1196,10 +1195,10 @@ fn test_resolve_promise_export() { ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_resolve_promise_export"); @@ -1226,10 +1225,10 @@ export { A, B }; ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_resolve_merged_types"); @@ -1249,10 +1248,10 @@ export type Foo = Foo.Bar; ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); @@ -1318,10 +1317,10 @@ export const codes: { ); let added_paths = [BiomePath::new("/node_modules/@types/iso-3166-2/index.d.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_resolve_recursive_looking_country_info"); @@ -1497,10 +1496,10 @@ export = vfile ); let added_paths = [BiomePath::new("/node_modules/vfile/types/index.d.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_resolve_recursive_looking_vfile"); @@ -1526,7 +1525,7 @@ fn test_resolve_react_types() { BiomePath::new("/node_modules/@types/react/index.d.ts"), BiomePath::new("/src/index.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let project_layout = ProjectLayout::default(); project_layout.insert_node_manifest( @@ -1541,7 +1540,7 @@ fn test_resolve_react_types() { .insert_serialized_tsconfig("/".into(), &tsconfig_json.syntax().as_send().unwrap()); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1581,10 +1580,10 @@ fn test_resolve_redis_commander_types() { BiomePath::new("/RedisCommander.d.ts"), BiomePath::new("/index.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); // We previously had an issue with `RedisCommander.d.ts` that caused types // to be duplicated. We should look out in this snapshot that method @@ -1639,10 +1638,10 @@ fn test_resolve_single_reexport() { BiomePath::new("/src/index.ts"), BiomePath::new("/src/reexport.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1703,10 +1702,10 @@ fn test_resolve_type_of_union_from_imported_module() { BiomePath::new("/src/reexport.ts"), BiomePath::new("/node_modules/react.d.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1769,10 +1768,10 @@ fn test_resolve_multiple_reexports() { BiomePath::new("/src/index.ts"), BiomePath::new("/src/reexports.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1821,10 +1820,10 @@ fn test_resolve_export_type_referencing_imported_type() { BiomePath::new("/src/index.ts"), BiomePath::new("/src/promisedResult.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let snapshot = ModuleGraphSnapshot::new(&module_graph, &fs); snapshot.assert_snapshot("test_resolve_export_type_referencing_imported_type"); @@ -1861,10 +1860,10 @@ fn test_resolve_promise_from_imported_function_returning_imported_promise_type() BiomePath::new("/src/promisedResult.ts"), BiomePath::new("/src/returnPromiseResult.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1925,10 +1924,10 @@ fn test_resolve_promise_from_imported_function_returning_reexported_promise_type BiomePath::new("/src/reexport.ts"), BiomePath::new("/src/returnPromiseResult.ts"), ]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -1980,10 +1979,10 @@ const { mutate } = useSWRConfig(); ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -2041,10 +2040,10 @@ type Intersection = Foo & Bar;"#, ); let added_paths = [BiomePath::new("/src/index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("/src/index.ts")) @@ -2112,10 +2111,10 @@ fn test_resolve_swr_types() { }) { added_paths.push(BiomePath::new(path)); } - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &project_layout, &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new(&format!( @@ -2181,11 +2180,11 @@ function f() { ); let added_paths = [BiomePath::new("index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("index.ts")) @@ -2218,11 +2217,11 @@ function g() { ); let added_paths = [BiomePath::new("index.ts")]; - let added_paths = get_added_paths(&fs, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); let module_graph = Arc::new(ModuleGraph::default()); - module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths); + module_graph.update_graph_for_js_paths(&fs, &ProjectLayout::default(), &added_paths, true); let index_module = module_graph .js_module_info_for_path(Utf8Path::new("index.ts")) @@ -2237,58 +2236,6 @@ function g() { snapshot.assert_snapshot("test_widening_via_assignment_multiple_values"); } -#[test] -fn resolves_css_imports_correctly() { - let fixtures_path = get_fixtures_path(); - - let fs = OsFileSystem::new(fixtures_path.clone()); - - let project_layout = ProjectLayout::default(); - project_layout.insert_node_manifest(format!("{fixtures_path}/css").into(), { - let path = Utf8PathBuf::from(format!("{fixtures_path}/css/package.json")); - deserialize_from_json_str::( - &fs.read_file_from_path(&path) - .expect("package.json must be readable"), - JsonParserOptions::default(), - "package.json", - ) - .into_deserialized() - .expect("package.json must parse") - }); - - let added_paths = [ - BiomePath::new(format!("{fixtures_path}/css/index.css")), - BiomePath::new(format!("{fixtures_path}/css/foo.css")), - BiomePath::new(format!("{fixtures_path}/css/bar.css")), - ]; - let added_paths = get_css_added_paths(&fs, &added_paths); - - let module_graph = ModuleGraph::default(); - module_graph.update_graph_for_css_paths(&fs, &project_layout, &added_paths, None); - - let imports = module_graph.data(); - let file_imports = imports - .get(Utf8Path::new(&format!("{fixtures_path}/css/index.css"))) - .unwrap(); - let file_imports = file_imports.as_css_module_info().unwrap(); - - assert_eq!(file_imports.imports.len(), 2); - assert_eq!( - file_imports.imports.get("foo.css"), - Some(&CssImport { - specifier: "foo.css".into(), - resolved_path: ResolvedPath::from_path(format!("{fixtures_path}/css/foo.css")), - }) - ); - assert_eq!( - file_imports.imports.get("./bar.css"), - Some(&CssImport { - specifier: "./bar.css".into(), - resolved_path: ResolvedPath::from_path(format!("{fixtures_path}/css/bar.css")), - }) - ); -} - fn find_files_recursively_in_directory( directory: &Utf8Path, predicate: impl Fn(&Utf8Path) -> bool, diff --git a/crates/biome_package/src/node_js_package/tsconfig_json.rs b/crates/biome_package/src/node_js_package/tsconfig_json.rs index 2dcda5fd4279..c0708d107dfc 100644 --- a/crates/biome_package/src/node_js_package/tsconfig_json.rs +++ b/crates/biome_package/src/node_js_package/tsconfig_json.rs @@ -116,6 +116,16 @@ impl TsConfigJson { }) }) } + + /// Returns the base identifier from the JSX factory function name. + pub fn jsx_factory_identifier(&self) -> Option<&str> { + self.compiler_options.jsx_factory.as_deref() + } + + /// Returns the base identifier from the JSX fragment factory function name. + pub fn jsx_fragment_factory_identifier(&self) -> Option<&str> { + self.compiler_options.jsx_fragment_factory.as_deref() + } } #[derive(Clone, Debug, Default, Deserializable)] @@ -137,6 +147,20 @@ pub struct CompilerOptions { /// See: https://www.typescriptlang.org/tsconfig/#typeRoots #[deserializable(rename = "typeRoots")] pub type_roots: Option>, + + /// See: https://www.typescriptlang.org/tsconfig/#jsxFactory + /// Specifies the JSX factory function to use when targeting react JSX emit. + /// The value is normalized to the base identifier during deserialization. + /// For example, "React.createElement" becomes "React", "h" stays "h". + #[deserializable(rename = "jsxFactory")] + pub jsx_factory: Option, + + /// See: https://www.typescriptlang.org/tsconfig/#jsxFragmentFactory + /// Specifies the JSX fragment factory function to use when targeting react JSX emit. + /// The value is normalized to the base identifier during deserialization. + /// For example, "React.Fragment" becomes "React", "Fragment" stays "Fragment". + #[deserializable(rename = "jsxFragmentFactory")] + pub jsx_fragment_factory: Option, } pub type CompilerOptionsPathsMap = IndexMap, BuildHasherDefault>; @@ -165,3 +189,275 @@ impl Deserializable for ExtendsField { pub struct ProjectReference { pub path: Utf8PathBuf, } + +/// A JSX factory identifier that is normalized during deserialization. +/// +/// When deserializing from JSON, if the value contains a dot (e.g., "React.createElement"), +/// only the base identifier before the first dot is kept (e.g., "React"). +/// This normalization happens once during deserialization for efficiency. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct JsxFactoryIdentifier(String); + +impl JsxFactoryIdentifier { + /// Returns the normalized identifier as a string slice. + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl std::ops::Deref for JsxFactoryIdentifier { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Deserializable for JsxFactoryIdentifier { + fn deserialize( + ctx: &mut impl DeserializationContext, + value: &impl DeserializableValue, + name: &str, + ) -> Option { + let full_name = String::deserialize(ctx, value, name)?; + // Extract the base identifier (everything before the first dot) + let base_identifier = full_name.split('.').next().unwrap().trim(); + + // Return None if the identifier is empty or whitespace-only + // to avoid "configured but unusable" states downstream + if base_identifier.is_empty() { + return None; + } + + Some(Self(base_identifier.to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_jsx_factory_normalization() { + let json = r#"{ + "compilerOptions": { + "jsxFactory": "React.createElement", + "jsxFragmentFactory": "React.Fragment" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + Some("React"), + "React.createElement should be normalized to React" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + Some("React"), + "React.Fragment should be normalized to React" + ); + } + + #[test] + fn test_jsx_factory_simple_identifier() { + let json = r#"{ + "compilerOptions": { + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + Some("h"), + "h should remain as h" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + Some("Fragment"), + "Fragment should remain as Fragment" + ); + } + + #[test] + fn test_jsx_factory_namespaced() { + let json = r#"{ + "compilerOptions": { + "jsxFactory": "MyLib.createElement", + "jsxFragmentFactory": "MyLib.Fragment" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + Some("MyLib"), + "MyLib.createElement should be normalized to MyLib" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + Some("MyLib"), + "MyLib.Fragment should be normalized to MyLib" + ); + } + + #[test] + fn test_jsx_factory_missing() { + let json = r#"{ + "compilerOptions": {} + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + None, + "Missing jsxFactory should return None" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + None, + "Missing jsxFragmentFactory should return None" + ); + } + + #[test] + fn test_jsx_factory_deeply_nested() { + let json = r#"{ + "compilerOptions": { + "jsxFactory": "Deeply.Nested.Function", + "jsxFragmentFactory": "Another.Nested.Fragment" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + Some("Deeply"), + "Deeply.Nested.Function should be normalized to Deeply" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + Some("Another"), + "Another.Nested.Fragment should be normalized to Another" + ); + } + + #[test] + fn test_jsx_factory_efficiency() { + // This test verifies that normalization happens during deserialization, + // not on every access + let json = r#"{ + "compilerOptions": { + "jsxFactory": "React.createElement" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!(tsconfig.jsx_factory_identifier(), Some("React")); + assert_eq!(tsconfig.jsx_factory_identifier(), Some("React")); // consistency + } + + #[test] + fn test_jsx_factory_empty_string() { + // Empty strings should be treated as None to avoid "configured but unusable" states + let json = r#"{ + "compilerOptions": { + "jsxFactory": "", + "jsxFragmentFactory": "" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + None, + "Empty jsxFactory should be None" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + None, + "Empty jsxFragmentFactory should be None" + ); + } + + #[test] + fn test_jsx_factory_whitespace_only() { + // Whitespace-only strings should be treated as None + let json = r#"{ + "compilerOptions": { + "jsxFactory": " ", + "jsxFragmentFactory": "\t\n" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + None, + "Whitespace-only jsxFactory should be None" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + None, + "Whitespace-only jsxFragmentFactory should be None" + ); + } + + #[test] + fn test_jsx_factory_dot_only() { + // A string with only dots should result in None (empty base identifier) + let json = r#"{ + "compilerOptions": { + "jsxFactory": ".", + "jsxFragmentFactory": "..." + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + None, + "Dot-only jsxFactory should be None" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + None, + "Dot-only jsxFragmentFactory should be None" + ); + } + + #[test] + fn test_jsx_factory_with_surrounding_whitespace() { + // Whitespace should be trimmed + let json = r#"{ + "compilerOptions": { + "jsxFactory": " React.createElement ", + "jsxFragmentFactory": "\tFragment\n" + } + }"#; + + let (tsconfig, _) = TsConfigJson::parse(Utf8Path::new("/test/tsconfig.json"), json); + + assert_eq!( + tsconfig.jsx_factory_identifier(), + Some("React"), + "Whitespace should be trimmed from jsxFactory" + ); + assert_eq!( + tsconfig.jsx_fragment_factory_identifier(), + Some("Fragment"), + "Whitespace should be trimmed from jsxFragmentFactory" + ); + } +} diff --git a/crates/biome_package/tests/manifest/invalid/license_invalid.json.snap b/crates/biome_package/tests/manifest/invalid/license_invalid.json.snap index 3a1a24f89d7f..1f5d7bfe106d 100644 --- a/crates/biome_package/tests/manifest/invalid/license_invalid.json.snap +++ b/crates/biome_package/tests/manifest/invalid/license_invalid.json.snap @@ -10,9 +10,4 @@ license_invalid.json:2:13 project ━━━━━━━━━━━━━━━ > 2 │ "license": "bar" │ ^^^^^ 3 │ } - 4 │ - - - - - + 4 │ diff --git a/crates/biome_package/tests/manifest/invalid/license_not_string.json.snap b/crates/biome_package/tests/manifest/invalid/license_not_string.json.snap index 266f2c581835..6f90098f3caf 100644 --- a/crates/biome_package/tests/manifest/invalid/license_not_string.json.snap +++ b/crates/biome_package/tests/manifest/invalid/license_not_string.json.snap @@ -10,7 +10,4 @@ license_not_string.json:2:13 deserialize ━━━━━━━━━━━━━ > 2 │ "license": false │ ^^^^^ 3 │ } - 4 │ - - - + 4 │ diff --git a/crates/biome_package/tests/manifest/invalid/name.json.snap b/crates/biome_package/tests/manifest/invalid/name.json.snap index a29f91db84d2..d6c129bd277c 100644 --- a/crates/biome_package/tests/manifest/invalid/name.json.snap +++ b/crates/biome_package/tests/manifest/invalid/name.json.snap @@ -10,7 +10,4 @@ name.json:2:10 deserialize ━━━━━━━━━━━━━━━━━ > 2 │ "name": false │ ^^^^^ 3 │ } - 4 │ - - - + 4 │ diff --git a/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap index 64734f71acdd..5b088b09bcd0 100644 --- a/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap +++ b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap @@ -24,6 +24,8 @@ TsConfigJson { paths: None, paths_base: "", type_roots: None, + jsx_factory: None, + jsx_fragment_factory: None, }, references: [], } diff --git a/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json.snap b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json.snap index b33171c7d0ea..a1feb444ee87 100644 --- a/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json.snap +++ b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json.snap @@ -37,6 +37,8 @@ TsConfigJson { ), paths_base: "/tsconfig/valid/src", type_roots: None, + jsx_factory: None, + jsx_fragment_factory: None, }, references: [], } diff --git a/crates/biome_parser/src/lexer.rs b/crates/biome_parser/src/lexer.rs index 23b4a1b36326..0e4ed39f6368 100644 --- a/crates/biome_parser/src/lexer.rs +++ b/crates/biome_parser/src/lexer.rs @@ -499,6 +499,10 @@ impl<'l, Lex: Lexer<'l>> BufferedLexer { } } + pub fn lexer_mut(&mut self) -> &mut Lex { + &mut self.inner + } + /// Returns the kind of the next token and any associated diagnostic. /// /// [See `Lexer.next_token`](Lexer::next_token) @@ -587,8 +591,10 @@ impl<'l, Lex: Lexer<'l>> BufferedLexer { fn reset_lookahead(&mut self) { if let Some(current) = self.current.take() { self.inner.rewind(current); - self.lookahead.clear(); + } else if let Some(first) = self.lookahead.get_checkpoint(0).cloned() { + self.inner.rewind(first); } + self.lookahead.clear(); } /// Returns an iterator over the tokens following the current token to perform lookahead. @@ -609,27 +615,60 @@ where { /// Re-lex the current token in the given context pub fn re_lex(&mut self, context: Lex::ReLexContext) -> Lex::Kind { - let current_kind = self.current(); - let current_checkpoint = self.inner.checkpoint(); - if let Some(current) = self.current.take() { self.inner.rewind(current); + } else if let Some(first) = self.lookahead.get_checkpoint(0).cloned() { + self.inner.rewind(first); } let new_kind = self.inner.re_lex(context); - - if new_kind != current_kind { - // The token has changed, clear the lookahead - self.lookahead.clear(); - } else if !self.lookahead.is_empty() { - // It's still the same kind. So let's move the lexer back to the position it was before re-lexing - // and keep the lookahead as is. - self.current = Some(self.inner.checkpoint()); - self.inner.rewind(current_checkpoint); - } + self.current = Some(self.inner.checkpoint()); + self.lookahead.clear(); new_kind } + + /// Force re-lex the current token in a new lex context, clearing all lookahead. + /// + /// Use this after lookahead operations when you need to switch lexing context + /// and ensure cached tokens from the previous context don't leak through. + /// + /// This method: + /// 1. Rewinds to the current token's START position + /// 2. Clears all lookahead cache + /// 3. Re-lexes the current token fresh in the new context + pub fn force_relex_in_context(&mut self, context: Lex::LexContext) -> Lex::Kind { + let checkpoint = if let Some(current) = self.current.clone() { + current + } else if let Some(first) = self.lookahead.get_checkpoint(0).cloned() { + first + } else { + self.inner.checkpoint() + }; + + // Rewind to the START of the current token (not the end). + // Use neutral values for kind/flags since they're immediately + // overwritten by next_token and shouldn't leak old context state. + let rewind_checkpoint = LexerCheckpoint { + position: checkpoint.current_start, + current_start: checkpoint.current_start, + current_kind: Lex::Kind::EOF, + current_flags: TokenFlags::empty(), + after_line_break: checkpoint.after_line_break, + unicode_bom_length: checkpoint.unicode_bom_length, + diagnostics_pos: checkpoint.diagnostics_pos, + }; + + self.inner.rewind(rewind_checkpoint); + self.current = None; + self.lookahead.clear(); + + // Lex the token fresh in the new context + let kind = self.inner.next_token(context); + self.current = Some(self.inner.checkpoint()); + + kind + } } impl<'l, Lex> BufferedLexer diff --git a/crates/biome_plugin_loader/Cargo.toml b/crates/biome_plugin_loader/Cargo.toml index 41361faf37fd..4f14221ed983 100644 --- a/crates/biome_plugin_loader/Cargo.toml +++ b/crates/biome_plugin_loader/Cargo.toml @@ -23,6 +23,7 @@ biome_grit_patterns = { workspace = true } biome_js_runtime = { workspace = true, optional = true } biome_js_syntax = { workspace = true } biome_json_parser = { workspace = true } +biome_json_syntax = { workspace = true } biome_parser = { workspace = true } biome_resolver = { workspace = true } biome_rowan = { workspace = true } diff --git a/crates/biome_plugin_loader/src/analyzer_grit_plugin.rs b/crates/biome_plugin_loader/src/analyzer_grit_plugin.rs index 692ae9b11422..ee2b0ad8b179 100644 --- a/crates/biome_plugin_loader/src/analyzer_grit_plugin.rs +++ b/crates/biome_plugin_loader/src/analyzer_grit_plugin.rs @@ -10,6 +10,7 @@ use biome_grit_patterns::{ compile_pattern_with_options, }; use biome_js_syntax::{AnyJsRoot, JsSyntaxNode}; +use biome_json_syntax::{JsonRoot, JsonSyntaxNode}; use biome_parser::{AnyParse, NodeParse}; use biome_rowan::{AnySyntaxNode, AstNode, RawSyntaxKind, SyntaxKind, TextRange}; use camino::{Utf8Path, Utf8PathBuf}; @@ -47,6 +48,7 @@ impl AnalyzerPlugin for AnalyzerGritPlugin { match &self.grit_query.language { GritTargetLanguage::JsTargetLanguage(_) => PluginTargetLanguage::JavaScript, GritTargetLanguage::CssTargetLanguage(_) => PluginTargetLanguage::Css, + GritTargetLanguage::JsonTargetLanguage(_) => PluginTargetLanguage::Json, } } @@ -59,6 +61,10 @@ impl AnalyzerPlugin for AnalyzerGritPlugin { PluginTargetLanguage::Css => { CssRoot::KIND_SET.iter().map(|kind| kind.to_raw()).collect() } + PluginTargetLanguage::Json => JsonRoot::KIND_SET + .iter() + .map(|kind| kind.to_raw()) + .collect(), } } @@ -72,6 +78,9 @@ impl AnalyzerPlugin for AnalyzerGritPlugin { PluginTargetLanguage::Css => node .downcast_ref::() .and_then(|node| node.as_send()), + PluginTargetLanguage::Json => node + .downcast_ref::() + .and_then(|node| node.as_send()), }; let parse = AnyParse::Node(NodeParse::new(root.unwrap(), vec![])); diff --git a/crates/biome_rowan/src/tree_builder.rs b/crates/biome_rowan/src/tree_builder.rs index 2d88a326859f..b12ea927bfb0 100644 --- a/crates/biome_rowan/src/tree_builder.rs +++ b/crates/biome_rowan/src/tree_builder.rs @@ -85,6 +85,7 @@ impl> TreeBuilder<'_, L, S> { } /// Adds new token to the current branch. + // TODO: This should return &mut Self for consistency with `token` #[inline] pub fn token_with_trivia( &mut self, diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index a102f102fb6a..157d5bf962ef 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -59,6 +59,7 @@ pub mod no_duplicate_at_import_rules; pub mod no_duplicate_attributes; pub mod no_duplicate_case; pub mod no_duplicate_class_members; +pub mod no_duplicate_classes; pub mod no_duplicate_custom_properties; pub mod no_duplicate_dependencies; pub mod no_duplicate_else_if; @@ -400,6 +401,7 @@ pub mod use_single_var_declarator; pub mod use_solid_for_component; pub mod use_sorted_attributes; pub mod use_sorted_classes; +pub mod use_sorted_interface_members; pub mod use_sorted_keys; pub mod use_sorted_properties; pub mod use_spread; diff --git a/crates/biome_rule_options/src/no_duplicate_classes.rs b/crates/biome_rule_options/src/no_duplicate_classes.rs new file mode 100644 index 000000000000..69246db79ede --- /dev/null +++ b/crates/biome_rule_options/src/no_duplicate_classes.rs @@ -0,0 +1,70 @@ +use std::ops::Deref; + +use biome_deserialize::{Deserializable, DeserializableValue, DeserializationContext}; +use serde::{Deserialize, Serialize}; + +use crate::use_sorted_classes::UseSortedClassesOptions; + +/// Options for the `noDuplicateClasses` assist action. +/// +/// Controls which JSX attributes and utility functions are checked for duplicate classes. +#[derive(Default, Clone, Debug, Eq, PartialEq)] +pub struct NoDuplicateClassesOptions(UseSortedClassesOptions); + +impl Deref for NoDuplicateClassesOptions { + type Target = UseSortedClassesOptions; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl biome_deserialize::Merge for NoDuplicateClassesOptions { + fn merge_with(&mut self, other: Self) { + self.0.merge_with(other.0); + } +} + +// Custom Serialize to match UseSortedClassesOptions format +impl Serialize for NoDuplicateClassesOptions { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.0.serialize(serializer) + } +} + +// Custom Deserialize to match UseSortedClassesOptions format +impl<'de> Deserialize<'de> for NoDuplicateClassesOptions { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + ::deserialize(deserializer).map(Self) + } +} + +// Custom JsonSchema to generate proper schema with distinct type name +#[cfg(feature = "schema")] +impl schemars::JsonSchema for NoDuplicateClassesOptions { + fn schema_name() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed("NoDuplicateClassesOptions") + } + + fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema { + // Generate schema based on the inner UseSortedClassesOptions but with our type name + // The schema is already correct, we just need the distinct type name (handled by schema_name) + UseSortedClassesOptions::json_schema(generator) + } +} + +impl Deserializable for NoDuplicateClassesOptions { + fn deserialize( + ctx: &mut impl DeserializationContext, + value: &impl DeserializableValue, + name: &str, + ) -> Option { + ::deserialize(ctx, value, name).map(Self) + } +} diff --git a/crates/biome_rule_options/src/no_redundant_alt.rs b/crates/biome_rule_options/src/no_redundant_alt.rs index ddd508af5c60..1f0c7de5dd3e 100644 --- a/crates/biome_rule_options/src/no_redundant_alt.rs +++ b/crates/biome_rule_options/src/no_redundant_alt.rs @@ -1,5 +1,6 @@ use biome_deserialize_macros::{Deserializable, Merge}; use serde::{Deserialize, Serialize}; + #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] diff --git a/crates/biome_rule_options/src/no_unknown_function.rs b/crates/biome_rule_options/src/no_unknown_function.rs index 5e36c283f0f1..3e3579cd15c0 100644 --- a/crates/biome_rule_options/src/no_unknown_function.rs +++ b/crates/biome_rule_options/src/no_unknown_function.rs @@ -3,4 +3,8 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoUnknownFunctionOptions {} +pub struct NoUnknownFunctionOptions { + /// A list of unknown function names to ignore (case-insensitive). + #[serde(skip_serializing_if = "Vec::is_empty")] + pub ignore: Vec, +} diff --git a/crates/biome_rule_options/src/no_unknown_property.rs b/crates/biome_rule_options/src/no_unknown_property.rs index 4fc4c5eeb61c..4f9b53fd90f1 100644 --- a/crates/biome_rule_options/src/no_unknown_property.rs +++ b/crates/biome_rule_options/src/no_unknown_property.rs @@ -3,4 +3,8 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoUnknownPropertyOptions {} +pub struct NoUnknownPropertyOptions { + /// A list of unknown property names to ignore (case-insensitive). + #[serde(skip_serializing_if = "Vec::is_empty")] + pub ignore: Vec, +} diff --git a/crates/biome_rule_options/src/no_unknown_pseudo_class.rs b/crates/biome_rule_options/src/no_unknown_pseudo_class.rs index d79054534479..a89ebf320b13 100644 --- a/crates/biome_rule_options/src/no_unknown_pseudo_class.rs +++ b/crates/biome_rule_options/src/no_unknown_pseudo_class.rs @@ -3,4 +3,8 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoUnknownPseudoClassOptions {} +pub struct NoUnknownPseudoClassOptions { + /// A list of unknown pseudo-class names to ignore (case-insensitive). + #[serde(skip_serializing_if = "Vec::is_empty")] + pub ignore: Vec, +} diff --git a/crates/biome_rule_options/src/no_unknown_pseudo_element.rs b/crates/biome_rule_options/src/no_unknown_pseudo_element.rs index f5eae0144727..0e2f59135045 100644 --- a/crates/biome_rule_options/src/no_unknown_pseudo_element.rs +++ b/crates/biome_rule_options/src/no_unknown_pseudo_element.rs @@ -3,4 +3,8 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct NoUnknownPseudoElementOptions {} +pub struct NoUnknownPseudoElementOptions { + /// A list of unknown pseudo-element names to ignore (case-insensitive). + #[serde(skip_serializing_if = "Vec::is_empty")] + pub ignore: Vec, +} diff --git a/crates/biome_rule_options/src/shared/mod.rs b/crates/biome_rule_options/src/shared/mod.rs index a9965fbb4866..9817d15c6053 100644 --- a/crates/biome_rule_options/src/shared/mod.rs +++ b/crates/biome_rule_options/src/shared/mod.rs @@ -1,2 +1,13 @@ +use biome_string_case::StrLikeExtension; + pub mod restricted_regex; pub mod sort_order; + +const REDUNDANT_WORDS: [&str; 3] = ["image", "photo", "picture"]; + +pub fn is_redundant_alt(alt: &str) -> bool { + REDUNDANT_WORDS.into_iter().any(|word| { + alt.split_whitespace() + .any(|x| x.to_ascii_lowercase_cow() == word) + }) +} diff --git a/crates/biome_rule_options/src/use_hook_at_top_level.rs b/crates/biome_rule_options/src/use_hook_at_top_level.rs index 01684c4d202a..c28fd719f061 100644 --- a/crates/biome_rule_options/src/use_hook_at_top_level.rs +++ b/crates/biome_rule_options/src/use_hook_at_top_level.rs @@ -5,12 +5,18 @@ use biome_deserialize::{ }; use biome_deserialize_macros::Merge; use biome_rowan::Text; +use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct UseHookAtTopLevelOptions {} +pub struct UseHookAtTopLevelOptions { + /// List of function names that should not be treated as hooks. + /// Functions in this list will be ignored by the rule even if they follow the `use*` naming convention. + #[serde(skip_serializing_if = "Option::is_none")] + pub ignore: Option>, +} impl Deserializable for UseHookAtTopLevelOptions { fn deserialize( @@ -36,7 +42,9 @@ impl DeserializationVisitor for DeprecatedHooksOptionsVisitor { _range: TextRange, _name: &str, ) -> Option { - const ALLOWED_KEYS: &[&str] = &["hooks"]; + const ALLOWED_KEYS: &[&str] = &["hooks", "ignore"]; + let mut ignore = None; + for (key, value) in members.flatten() { let Some(key_text) = Text::deserialize(ctx, &key, "") else { continue; @@ -53,6 +61,9 @@ impl DeserializationVisitor for DeprecatedHooksOptionsVisitor { }) ); } + "ignore" => { + ignore = Deserializable::deserialize(ctx, &value, key_text.text()); + } text => ctx.report(DeserializationDiagnostic::new_unknown_key( text, key.range(), @@ -60,6 +71,6 @@ impl DeserializationVisitor for DeprecatedHooksOptionsVisitor { )), } } - Some(Self::Output::default()) + Some(Self::Output { ignore }) } } diff --git a/crates/biome_rule_options/src/use_import_extensions.rs b/crates/biome_rule_options/src/use_import_extensions.rs index 29c8d39b7f3a..9e82a1420f9c 100644 --- a/crates/biome_rule_options/src/use_import_extensions.rs +++ b/crates/biome_rule_options/src/use_import_extensions.rs @@ -1,4 +1,5 @@ use biome_deserialize_macros::{Deserializable, Merge}; +use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -6,8 +7,13 @@ use serde::{Deserialize, Serialize}; pub struct UseImportExtensionsOptions { /// If `true`, the suggested extension is always `.js` regardless of what /// extension the source file has in your project. - #[serde(skip_serializing_if = "Option::<_>::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub force_js_extensions: Option, + + /// A map of file extensions to their suggested replacements. + /// For example, `{"ts": "js"}` would suggest `.js` extensions for TypeScript imports. + #[serde(skip_serializing_if = "Option::is_none")] + pub extension_mappings: Option, Box>>, } impl UseImportExtensionsOptions { diff --git a/crates/biome_rule_options/src/use_iterable_callback_return.rs b/crates/biome_rule_options/src/use_iterable_callback_return.rs index 866a2b0db3b3..604fc9dab2b9 100644 --- a/crates/biome_rule_options/src/use_iterable_callback_return.rs +++ b/crates/biome_rule_options/src/use_iterable_callback_return.rs @@ -3,4 +3,19 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct UseIterableCallbackReturnOptions {} +pub struct UseIterableCallbackReturnOptions { + /// When `true`, the rule reports `forEach` callbacks that return a value (default behaviour). + /// When `false` or unset, such callbacks are ignored. + #[serde(skip_serializing_if = "Option::is_none")] + pub check_for_each: Option, +} + +impl UseIterableCallbackReturnOptions { + pub const DEFAULT_CHECK_FOR_EACH: bool = true; + + /// Returns [`Self::check_for_each`] if it is set. + /// Otherwise, returns [`Self::DEFAULT_CHECK_FOR_EACH`]. + pub fn check_for_each(&self) -> bool { + self.check_for_each.unwrap_or(Self::DEFAULT_CHECK_FOR_EACH) + } +} diff --git a/crates/biome_rule_options/src/use_sorted_interface_members.rs b/crates/biome_rule_options/src/use_sorted_interface_members.rs new file mode 100644 index 000000000000..3947d614ed74 --- /dev/null +++ b/crates/biome_rule_options/src/use_sorted_interface_members.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::{Deserializable, Merge}; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct UseSortedInterfaceMembersOptions {} diff --git a/crates/biome_rule_options/src/use_sorted_keys.rs b/crates/biome_rule_options/src/use_sorted_keys.rs index 9a80f7caecf0..47ad351b3859 100644 --- a/crates/biome_rule_options/src/use_sorted_keys.rs +++ b/crates/biome_rule_options/src/use_sorted_keys.rs @@ -8,4 +8,9 @@ use serde::{Deserialize, Serialize}; pub struct UseSortedKeysOptions { #[serde(skip_serializing_if = "Option::<_>::is_none")] pub sort_order: Option, + /// When enabled, groups object keys by their value's nesting depth before sorting. + /// Simple values (primitives, single-line arrays, single-line objects) are sorted first, + /// followed by nested values (multi-line objects, multi-line arrays). + #[serde(skip_serializing_if = "Option::<_>::is_none")] + pub group_by_nesting: Option, } diff --git a/crates/biome_rule_options/src/use_unified_type_signatures.rs b/crates/biome_rule_options/src/use_unified_type_signatures.rs index c25a7462ec96..cd2f19a79e4d 100644 --- a/crates/biome_rule_options/src/use_unified_type_signatures.rs +++ b/crates/biome_rule_options/src/use_unified_type_signatures.rs @@ -1,6 +1,26 @@ use biome_deserialize_macros::{Deserializable, Merge}; use serde::{Deserialize, Serialize}; -#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] + +#[derive( + Clone, Copy, Debug, Default, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize, +)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields, default)] -pub struct UseUnifiedTypeSignaturesOptions {} +pub struct UseUnifiedTypeSignaturesOptions { + /// Whether to ignore overloads with differently named parameters. + #[serde(skip_serializing_if = "Option::<_>::is_none")] + pub ignore_differently_named_parameters: Option, + + /// Whether to ignore overloads with different JSDoc comments. + #[serde(skip_serializing_if = "Option::<_>::is_none")] + pub ignore_different_js_doc: Option, +} + +impl UseUnifiedTypeSignaturesOptions { + pub fn ignore_differently_named_parameters(&self) -> bool { + self.ignore_differently_named_parameters.unwrap_or(false) + } + pub fn ignore_different_js_doc(&self) -> bool { + self.ignore_different_js_doc.unwrap_or(false) + } +} diff --git a/crates/biome_ruledoc_utils/src/codeblock.rs b/crates/biome_ruledoc_utils/src/codeblock.rs index 183eed86ea85..a97a9e791147 100644 --- a/crates/biome_ruledoc_utils/src/codeblock.rs +++ b/crates/biome_ruledoc_utils/src/codeblock.rs @@ -8,6 +8,7 @@ use biome_service::{ settings::{ServiceLanguage, Settings}, workspace::DocumentFileSource, }; +use camino::Utf8PathBuf; /// Represents a single code block used for evaluating doc tests. /// @@ -98,6 +99,13 @@ impl CodeBlock { DocumentFileSource::from_extension(&self.tag, false) } + pub fn document_file_source_from_path(&self) -> DocumentFileSource { + self.file_path.as_ref().map_or_else( + || DocumentFileSource::from_extension(&self.tag, false), + |file| DocumentFileSource::from_path(&Utf8PathBuf::from(file.as_str()), false), + ) + } + /// Returns the block's file path, but only if one was set explicitly using /// the `file=` attribute. pub fn explicit_file_path(&self) -> Option<&str> { @@ -122,7 +130,6 @@ impl FromStr for CodeBlock { .filter(|token| !token.is_empty()); let mut code_block = Self::default(); - for token in tokens { match token { "expect_diagnostic" => code_block.expect_diagnostic = true, @@ -136,7 +143,6 @@ impl FromStr for CodeBlock { if path.is_empty() { bail!("The 'file' attribute must be followed by a file path"); } - code_block.file_path = Some(normalize_file_path(path)); } else { if DocumentFileSource::from_extension(token, false) diff --git a/crates/biome_ruledoc_utils/src/lib.rs b/crates/biome_ruledoc_utils/src/lib.rs index 0f11eb909112..a251fc774f3d 100644 --- a/crates/biome_ruledoc_utils/src/lib.rs +++ b/crates/biome_ruledoc_utils/src/lib.rs @@ -10,7 +10,7 @@ use biome_js_parser::JsFileSource; use biome_json_parser::{JsonParserOptions, parse_json}; use biome_module_graph::ModuleGraph; use biome_project_layout::ProjectLayout; -use biome_test_utils::get_added_paths; +use biome_test_utils::get_added_js_paths; use camino::Utf8PathBuf; pub use codeblock::*; @@ -80,8 +80,8 @@ impl AnalyzerServicesBuilder { } let module_graph = ModuleGraph::default(); - let added_paths = get_added_paths(&fs, &added_paths); - module_graph.update_graph_for_js_paths(&fs, &layout, &added_paths); + let added_paths = get_added_js_paths(&fs, &added_paths); + module_graph.update_graph_for_js_paths(&fs, &layout, &added_paths, true); Self { module_graph: Arc::new(module_graph), diff --git a/crates/biome_service/Cargo.toml b/crates/biome_service/Cargo.toml index 4cec412be78d..be5660373c96 100644 --- a/crates/biome_service/Cargo.toml +++ b/crates/biome_service/Cargo.toml @@ -41,7 +41,7 @@ biome_html_formatter = { workspace = true, features = ["serde"] } biome_html_parser = { workspace = true } biome_html_syntax = { workspace = true } biome_js_analyze = { workspace = true } -biome_js_factory = { workspace = true, optional = true } +biome_js_factory = { workspace = true } biome_js_formatter = { workspace = true, features = ["serde"] } biome_js_parser = { workspace = true } biome_js_semantic = { workspace = true } @@ -61,6 +61,7 @@ biome_rowan = { workspace = true, features = ["serde"] } biome_string_case = { workspace = true } biome_text_edit = { workspace = true } boxcar = { workspace = true } +bpaf = { workspace = true } camino = { workspace = true } crossbeam = { workspace = true } either = { workspace = true } @@ -96,7 +97,6 @@ schema = [ "biome_html_formatter/schema", "biome_html_syntax/schema", "biome_js_analyze/schema", - "biome_js_factory", "biome_js_syntax/schema", "biome_json_syntax/schema", "biome_module_graph/schema", diff --git a/crates/biome_service/src/configuration.rs b/crates/biome_service/src/configuration.rs index 44e934e961fb..af05a98012c1 100644 --- a/crates/biome_service/src/configuration.rs +++ b/crates/biome_service/src/configuration.rs @@ -9,9 +9,7 @@ use biome_configuration::diagnostics::{ CantLoadExtendFile, CantResolve, EditorConfigDiagnostic, ParseFailedDiagnostic, }; use biome_configuration::editorconfig::EditorConfig; -use biome_configuration::{ - BiomeDiagnostic, ConfigurationPathHint, ConfigurationPayload, push_to_analyzer_rules, -}; +use biome_configuration::{BiomeDiagnostic, ConfigurationPathHint, push_to_analyzer_rules}; use biome_configuration::{Configuration, VERSION, push_to_analyzer_assist}; use biome_console::markup; use biome_css_analyze::METADATA as css_lint_metadata; @@ -23,6 +21,7 @@ use biome_fs::{AutoSearchResult, ConfigName, FileSystem, OpenOptions}; use biome_graphql_analyze::METADATA as graphql_lint_metadata; use biome_graphql_syntax::GraphqlLanguage; use biome_html_analyze::METADATA as html_lint_metadata; +use biome_html_syntax::HtmlLanguage; use biome_js_analyze::METADATA as js_lint_metadata; use biome_js_syntax::JsLanguage; use biome_json_analyze::METADATA as json_lint_metadata; @@ -56,6 +55,25 @@ pub struct LoadedConfiguration { pub diagnostics: Vec, /// The list of possible extended configuration files pub extended_configurations: Vec<(Utf8PathBuf, Configuration)>, + /// Where the configuration was loaded from. + pub loaded_location: LoadedLocation, +} + +#[derive(Default, Debug)] +pub enum LoadedLocation { + /// Loaded from inside the project + #[default] + InProject, + /// Loaded from a parent folder + ParentFolder, + /// Loaded from the user configuration folder + UserConfigFolder, +} + +impl LoadedLocation { + pub const fn is_in_project(&self) -> bool { + matches!(self, Self::InProject) + } } impl LoadedConfiguration { @@ -72,6 +90,7 @@ impl LoadedConfiguration { external_resolution_base_path, configuration_file_path, deserialized, + loaded_location, } = value; let (partial_configuration, mut diagnostics) = deserialized.consume(); @@ -117,6 +136,7 @@ impl LoadedConfiguration { directory_path: configuration_file_path.parent().map(Utf8PathBuf::from), file_path: Some(configuration_file_path), extended_configurations, + loaded_location, }) } @@ -185,6 +205,18 @@ pub fn load_configuration( LoadedConfiguration::try_from_payload(config, fs) } +#[derive(Debug)] +pub struct ConfigurationPayload { + /// The result of the deserialization + pub deserialized: Deserialized, + /// The path of where the `biome.json` or `biome.jsonc` file was found. This contains the file name. + pub configuration_file_path: Utf8PathBuf, + /// The base path where the external configuration in a package should be resolved from + pub external_resolution_base_path: Utf8PathBuf, + + pub loaded_location: LoadedLocation, +} + /// - [Result]: if an error occurred while loading the configuration file. /// - [Option]: sometimes not having a configuration file should not be an error, so we need this type. /// - [ConfigurationPayload]: The result of the operation @@ -265,20 +297,37 @@ pub fn read_config( is_found }; - let Some(auto_search_result) = fs.auto_search_files_with_predicate( - &configuration_directory, - &[ConfigName::biome_json(), ConfigName::biome_jsonc()], - &mut predicate, - ) else { + let Some((auto_search_result, loaded_location)) = fs + .auto_search_files_with_predicate( + &configuration_directory, + &ConfigName::file_names(), + &mut predicate, + ) + .map(|auto_search_result| { + if configuration_directory == auto_search_result.directory_path { + (auto_search_result, LoadedLocation::InProject) + } else { + (auto_search_result, LoadedLocation::ParentFolder) + } + }) + .or_else(|| { + let user_config_dir = fs.user_config_dir()?; + + let paths = ConfigName::file_names().map(|file_name| user_config_dir.join(file_name)); + fs.read_file_from_paths_with_predicate(paths.as_slice(), &mut predicate) + .map(|auto_search_result| (auto_search_result, LoadedLocation::UserConfigFolder)) + }) + else { return Ok(None); }; Ok(Some(ConfigurationPayload { - // Unwrapping is safe because the predicate in the search above would + // SAFETY: unwrapping is safe because the predicate in the search above would // only return `true` if it assigned `Some` value: deserialized: deserialized.unwrap(), configuration_file_path: auto_search_result.file_path, external_resolution_base_path, + loaded_location, })) } @@ -288,6 +337,7 @@ fn load_user_config( external_resolution_base_path: Utf8PathBuf, ) -> LoadConfig { // If the configuration path hint is a file path, we'll load it directly. + let working_directory = fs.working_directory(); if fs.path_is_file(config_file_path) { let content = fs.read_file_from_path(config_file_path)?; let parser_options = match config_file_path.extension() { @@ -298,33 +348,36 @@ fn load_user_config( _ => return Err(BiomeDiagnostic::invalid_configuration_file(config_file_path).into()), }; let deserialized = deserialize_from_json_str::(&content, parser_options, ""); + let loaded_location = working_directory.map_or(LoadedLocation::InProject, |wd| { + if config_file_path.starts_with(wd) { + LoadedLocation::InProject + } else { + LoadedLocation::ParentFolder + } + }); Ok(Some(ConfigurationPayload { deserialized, configuration_file_path: config_file_path.to_path_buf(), external_resolution_base_path, + loaded_location, })) } else { - let biome_json_path = config_file_path.join(ConfigName::biome_json()); - let biome_jsonc_path = config_file_path.join(ConfigName::biome_jsonc()); - let biome_json_exists = fs.path_exists(biome_json_path.as_path()); - let biome_jsonc_exists = fs.path_exists(biome_jsonc_path.as_path()); - - if !biome_json_exists && !biome_jsonc_exists { + let config_paths = + ConfigName::file_names().map(|file_name| config_file_path.join(file_name)); + let result = fs.read_files_from_paths(config_paths.as_slice()); + let Some(result) = result else { return Err(BiomeDiagnostic::no_configuration_file_found(config_file_path).into()); - } + }; - let (config_path, parser_options) = if biome_json_exists { - (biome_json_path, JsonParserOptions::default()) + let parser_options = if result.file_path.extension() == Some("json") { + JsonParserOptions::default() } else { - ( - biome_jsonc_path, - JsonParserOptions::default() - .with_allow_comments() - .with_allow_trailing_commas(), - ) + JsonParserOptions::default() + .with_allow_comments() + .with_allow_trailing_commas() }; - let content = fs.read_file_from_path(config_path.as_path())?; + let content = fs.read_file_from_path(result.file_path.as_path())?; let deserialized = deserialize_from_json_str::(&content, parser_options, ""); if deserialized .deserialized @@ -333,11 +386,18 @@ fn load_user_config( { return Err(BiomeDiagnostic::non_root_configuration(config_file_path).into()); } - + let loaded_location = working_directory.map_or(LoadedLocation::InProject, |wd| { + if config_file_path.starts_with(wd) { + LoadedLocation::InProject + } else { + LoadedLocation::ParentFolder + } + }); Ok(Some(ConfigurationPayload { deserialized, - configuration_file_path: config_path.to_path_buf(), + configuration_file_path: result.file_path.to_path_buf(), external_resolution_base_path, + loaded_location, })) } } @@ -729,6 +789,7 @@ mod test { /// on the current configuration pub struct ProjectScanComputer<'a> { requires_project_scan: bool, + requires_types: bool, enabled_rules: FxHashSet>, configuration: &'a Configuration, skip: &'a [AnalyzerSelector], @@ -741,6 +802,7 @@ impl<'a> ProjectScanComputer<'a> { Self { enabled_rules, requires_project_scan: false, + requires_types: false, configuration, skip: &[], only: &[], @@ -765,6 +827,11 @@ impl<'a> ProjectScanComputer<'a> { for (domain, value) in domains.iter() { if domain == &RuleDomain::Project && value != &RuleDomainValue::None { self.requires_project_scan = true; + } + if domain == &RuleDomain::Types && value != &RuleDomainValue::None { + self.requires_types = true; + self.requires_project_scan = true; + // requiring types is of higher order of project, so we can bail break; } } @@ -774,8 +841,11 @@ impl<'a> ProjectScanComputer<'a> { biome_css_analyze::visit_registry(&mut self); biome_json_analyze::visit_registry(&mut self); biome_js_analyze::visit_registry(&mut self); + biome_html_analyze::visit_registry(&mut self); - if self.requires_project_scan { + if self.requires_types { + ScanKind::TypeAware + } else if self.requires_project_scan { ScanKind::Project } else { // There's no need to scan further known files if the VCS isn't enabled @@ -799,6 +869,7 @@ impl<'a> ProjectScanComputer<'a> { if selector.match_rule::() { let domains = R::METADATA.domains; self.requires_project_scan |= domains.contains(&RuleDomain::Project); + self.requires_types |= domains.contains(&RuleDomain::Types); break; } } @@ -807,6 +878,7 @@ impl<'a> ProjectScanComputer<'a> { { let domains = R::METADATA.domains; self.requires_project_scan |= domains.contains(&RuleDomain::Project); + self.requires_types |= domains.contains(&RuleDomain::Types); } } } @@ -850,6 +922,15 @@ impl RegistryVisitor for ProjectScanComputer<'_> { } } +impl RegistryVisitor for ProjectScanComputer<'_> { + fn record_rule(&mut self) + where + R: Rule> + + 'static, + { + self.check_rule::(); + } +} #[cfg(test)] mod tests { use super::*; @@ -1001,4 +1082,67 @@ mod tests { ScanKind::NoScanner ); } + + #[test] + fn should_return_type_aware_if_type_aware_domain_is_enabled() { + let mut domains = FxHashMap::default(); + domains.insert(RuleDomain::Types, RuleDomainValue::Recommended); + + let configuration = Configuration { + linter: Some(LinterConfiguration { + domains: Some(RuleDomains(domains)), + ..Default::default() + }), + ..Default::default() + }; + + assert_eq!( + ProjectScanComputer::new(&configuration).compute(), + ScanKind::TypeAware + ); + } + + #[test] + fn should_return_type_aware_if_type_aware_domain_selector() { + let configuration = Configuration::default(); + + assert_eq!( + ProjectScanComputer::new(&configuration) + .with_rule_selectors(&[], &[DomainSelector("types").into()]) + .compute(), + ScanKind::TypeAware + ); + } + + #[test] + fn should_return_type_aware_when_both_type_aware_and_project_enabled() { + let mut domains = FxHashMap::default(); + domains.insert(RuleDomain::Project, RuleDomainValue::Recommended); + domains.insert(RuleDomain::Types, RuleDomainValue::Recommended); + + let configuration = Configuration { + linter: Some(LinterConfiguration { + domains: Some(RuleDomains(domains)), + ..Default::default() + }), + ..Default::default() + }; + + assert_eq!( + ProjectScanComputer::new(&configuration).compute(), + ScanKind::TypeAware + ); + } + + #[test] + fn should_not_return_type_aware_if_non_type_aware_domain() { + let configuration = Configuration::default(); + + assert_eq!( + ProjectScanComputer::new(&configuration) + .with_rule_selectors(&[], &[DomainSelector("react").into()]) + .compute(), + ScanKind::NoScanner + ); + } } diff --git a/crates/biome_service/src/diagnostics.rs b/crates/biome_service/src/diagnostics.rs index a0816195ffac..c1752c912061 100644 --- a/crates/biome_service/src/diagnostics.rs +++ b/crates/biome_service/src/diagnostics.rs @@ -690,6 +690,14 @@ pub struct WatchError { pub reason: String, } +#[derive(Debug, Default, Diagnostic, Serialize, Deserialize)] +#[diagnostic( + category = "project", + severity = Hint, + message = "Biome found a configuration file outside of the current working directory. If the configuration enables the scanner, Biome might scan the whole file system. This behaviour will be fixed in the next major version.", +)] +pub struct ConfigurationOutsideProject; + #[cfg(test)] mod test { use crate::diagnostics::{CantReadFile, FileIgnored, NotFound, SourceFileNotSupported}; diff --git a/crates/biome_service/src/file_handlers/astro.rs b/crates/biome_service/src/file_handlers/astro.rs index 19b1acb7ed30..f052bc4b1252 100644 --- a/crates/biome_service/src/file_handlers/astro.rs +++ b/crates/biome_service/src/file_handlers/astro.rs @@ -4,7 +4,7 @@ use crate::file_handlers::{ ExtensionHandler, FixAllParams, FormatterCapabilities, LintParams, LintResults, ParseResult, ParserCapabilities, javascript, }; -use crate::settings::Settings; +use crate::settings::SettingsWithEditor; use crate::workspace::{DocumentFileSource, FixFileResult, PullActionsResult}; use biome_formatter::Printed; use biome_fs::BiomePath; @@ -111,7 +111,7 @@ fn parse( _rome_path: &BiomePath, file_source: DocumentFileSource, text: &str, - _settings: &Settings, + _settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let frontmatter = AstroFileHandler::input(text); @@ -135,7 +135,7 @@ fn format( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { javascript::format(biome_path, document_file_source, parse, settings) } @@ -143,7 +143,7 @@ pub(crate) fn format_range( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { javascript::format_range(biome_path, document_file_source, parse, settings, range) @@ -153,7 +153,7 @@ pub(crate) fn format_on_type( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { javascript::format_on_type(biome_path, document_file_source, parse, settings, offset) diff --git a/crates/biome_service/src/file_handlers/css.rs b/crates/biome_service/src/file_handlers/css.rs index 27ed65783561..e8fd579c3bb8 100644 --- a/crates/biome_service/src/file_handlers/css.rs +++ b/crates/biome_service/src/file_handlers/css.rs @@ -10,7 +10,7 @@ use crate::file_handlers::{ }; use crate::settings::{ FormatSettings, LanguageListSettings, LanguageSettings, OverrideSettings, ServiceLanguage, - Settings, check_feature_activity, check_override_feature_activity, + Settings, SettingsWithEditor, check_feature_activity, check_override_feature_activity, }; use crate::workspace::{ CodeAction, DocumentFileSource, FixFileResult, GetSyntaxTreeResult, PullActionsResult, @@ -25,11 +25,12 @@ use biome_configuration::css::{ use biome_css_analyze::{CssAnalyzerServices, analyze}; use biome_css_formatter::context::CssFormatOptions; use biome_css_formatter::format_node; -use biome_css_parser::CssParserOptions; +use biome_css_parser::{CssModulesKind, CssParserOptions}; use biome_css_semantic::semantic_model; -use biome_css_syntax::{CssLanguage, CssRoot, CssSyntaxNode}; +use biome_css_syntax::{AnyCssRoot, CssLanguage, CssRoot, CssSyntaxNode}; use biome_formatter::{ FormatError, IndentStyle, IndentWidth, LineEnding, LineWidth, Printed, QuoteStyle, + TrailingNewline, }; use biome_fs::BiomePath; use biome_parser::AnyParse; @@ -49,6 +50,7 @@ pub struct CssFormatterSettings { pub indent_style: Option, pub quote_style: Option, pub enabled: Option, + pub trailing_newline: Option, } impl From for CssFormatterSettings { @@ -60,6 +62,7 @@ impl From for CssFormatterSettings { indent_style: configuration.indent_style, quote_style: configuration.quote_style, line_ending: configuration.line_ending, + trailing_newline: configuration.trailing_newline, } } } @@ -154,16 +157,34 @@ impl ServiceLanguage for CssLanguage { overrides: &OverrideSettings, language: &Self::ParserSettings, path: &BiomePath, - _file_source: &DocumentFileSource, + file_source: &DocumentFileSource, ) -> Self::ParserOptions { let mut options = CssParserOptions { allow_wrong_line_comments: language .allow_wrong_line_comments .unwrap_or_default() .into(), - css_modules: language.css_modules_enabled.unwrap_or_default().into(), + css_modules: language + .css_modules_enabled + .map(|bool| { + if bool.value() { + CssModulesKind::Classic + } else { + CssModulesKind::None + } + }) + .or_else(|| { + file_source.to_css_file_source().map(|files_source| { + if files_source.is_vue_embedded() { + CssModulesKind::Vue + } else { + CssModulesKind::Classic + } + }) + }) + .unwrap_or_default(), grit_metavariables: false, - tailwind_directives: language.tailwind_directives_enabled(), + tailwind_directives: language.tailwind_directives.unwrap_or_default().into(), }; overrides.apply_override_css_parser_options(path, &mut options); @@ -196,6 +217,11 @@ impl ServiceLanguage for CssLanguage { .or(global.line_ending) .unwrap_or_default(); + let trailing_newline = language + .trailing_newline + .or(global.trailing_newline) + .unwrap_or_default(); + let mut options = CssFormatOptions::new( document_file_source .to_css_file_source() @@ -205,7 +231,8 @@ impl ServiceLanguage for CssLanguage { .with_indent_width(indent_width) .with_line_width(line_width) .with_line_ending(line_ending) - .with_quote_style(language.quote_style.unwrap_or_default()); + .with_quote_style(language.quote_style.unwrap_or_default()) + .with_trailing_newline(trailing_newline); overrides.apply_override_css_format_options(path, &mut options); @@ -236,15 +263,7 @@ impl ServiceLanguage for CssLanguage { let configuration = AnalyzerConfiguration::default() .with_rules(to_analyzer_rules(global, file_path.as_path())) - .with_preferred_quote(preferred_quote) - .with_css_modules( - global - .languages - .css - .parser - .css_modules_enabled - .is_some_and(|css_modules_enabled| css_modules_enabled.into()), - ); + .with_preferred_quote(preferred_quote); AnalyzerOptions::default() .with_file_path(file_path.as_path()) @@ -379,59 +398,34 @@ impl ExtensionHandler for CssFileHandler { } } -fn formatter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn formatter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.formatter_enabled_for_file_path::(path) } -fn linter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn linter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.linter_enabled_for_file_path::(path) } -fn assist_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn assist_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.assist_enabled_for_file_path::(path) } -fn search_enabled(_path: &Utf8Path, _settings: &Settings) -> bool { +fn search_enabled(_path: &Utf8Path, _settings: &SettingsWithEditor) -> bool { true } fn parse( biome_path: &BiomePath, - _file_source: DocumentFileSource, + file_source: DocumentFileSource, text: &str, - settings: &Settings, + settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { - let mut options = CssParserOptions { - allow_wrong_line_comments: settings - .languages - .css - .parser - .allow_wrong_line_comments - .unwrap_or_default() - .into(), - css_modules: settings - .languages - .css - .parser - .css_modules_enabled - .unwrap_or_default() - .into(), - grit_metavariables: false, - tailwind_directives: settings - .languages - .css - .parser - .tailwind_directives - .unwrap_or_default() - .into(), - }; + let options = settings.parse_options::(biome_path, &file_source); - settings - .override_settings - .apply_override_css_parser_options(biome_path, &mut options); + let source_type = file_source.to_css_file_source().unwrap_or_default(); + let parse = biome_css_parser::parse_css_with_cache(text, source_type, cache, options); - let parse = biome_css_parser::parse_css_with_cache(text, cache, options); ParseResult { any_parse: parse.into(), language: None, @@ -451,7 +445,7 @@ fn debug_formatter_ir( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -463,7 +457,7 @@ fn debug_formatter_ir( } fn debug_semantic_model(_path: &BiomePath, parse: AnyParse) -> Result { - let tree: CssRoot = parse.tree(); + let tree: AnyCssRoot = parse.tree(); let model = semantic_model(&tree); Ok(model.to_string()) } @@ -473,7 +467,7 @@ fn format( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -490,7 +484,7 @@ fn format_range( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -504,7 +498,7 @@ fn format_on_type( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -555,7 +549,7 @@ fn lint(params: LintParams) -> LintResults { let tree = params.parse.tree(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(settings, analyzer_options) + AnalyzerVisitorBuilder::new(settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.path.as_path()) @@ -572,10 +566,10 @@ fn lint(params: LintParams) -> LintResults { let mut process_lint = ProcessLint::new(¶ms); let css_services = CssAnalyzerServices { - semantic_model: params - .document_services - .as_css_services() - .and_then(|services| services.semantic_model.as_ref()), + semantic_model: params.snippet_services.and_then(|s| { + s.as_css_services() + .and_then(|services| services.semantic_model.as_ref()) + }), file_source, }; let (_, analyze_diagnostics) = analyze( @@ -626,7 +620,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult { settings.analyzer_options::(path, &language, suppression_reason.as_deref()); let mut actions = Vec::new(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(settings, analyzer_options) + AnalyzerVisitorBuilder::new(settings.as_ref(), analyzer_options) .with_only(only) .with_skip(skip) .with_path(path.as_path()) @@ -676,20 +670,23 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult { /// Applies all the safe fixes to the given syntax tree. pub(crate) fn fix_all(params: FixAllParams) -> Result { - let mut tree: CssRoot = params.parse.tree(); + let mut tree: AnyCssRoot = params.parse.tree(); let Some(file_source) = params.document_file_source.to_css_file_source() else { error!("Could not determine the file source of the file"); return Ok(FixFileResult::default()); }; // Compute final rules (taking `overrides` into account) - let rules = params.settings.as_linter_rules(params.biome_path.as_path()); + let rules = params + .settings + .as_ref() + .as_linter_rules(params.biome_path.as_path()); let analyzer_options = params.settings.analyzer_options::( params.biome_path, ¶ms.document_file_source, params.suppression_reason.as_deref(), ); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.biome_path.as_path()) @@ -729,7 +726,7 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result tree, None => return None, }; diff --git a/crates/biome_service/src/file_handlers/graphql.rs b/crates/biome_service/src/file_handlers/graphql.rs index ed0507ab4bbd..ed1562bd5ec1 100644 --- a/crates/biome_service/src/file_handlers/graphql.rs +++ b/crates/biome_service/src/file_handlers/graphql.rs @@ -11,7 +11,7 @@ use crate::file_handlers::{ }; use crate::settings::{ FormatSettings, LanguageListSettings, LanguageSettings, OverrideSettings, ServiceLanguage, - Settings, check_feature_activity, check_override_feature_activity, + Settings, SettingsWithEditor, check_feature_activity, check_override_feature_activity, }; use crate::workspace::{CodeAction, FixFileResult, GetSyntaxTreeResult, PullActionsResult}; use biome_analyze::{AnalysisFilter, AnalyzerConfiguration, AnalyzerOptions, ControlFlow, Never}; @@ -21,7 +21,7 @@ use biome_configuration::graphql::{ }; use biome_formatter::{ BracketSpacing, FormatError, IndentStyle, IndentWidth, LineEnding, LineWidth, Printed, - QuoteStyle, + QuoteStyle, TrailingNewline, }; use biome_fs::BiomePath; use biome_graphql_analyze::analyze; @@ -46,6 +46,7 @@ pub struct GraphqlFormatterSettings { pub quote_style: Option, pub bracket_spacing: Option, pub enabled: Option, + pub trailing_newline: Option, } impl From for GraphqlFormatterSettings { @@ -58,6 +59,7 @@ impl From for GraphqlFormatterSettings { quote_style: configuration.quote_style, bracket_spacing: configuration.bracket_spacing, enabled: configuration.enabled, + trailing_newline: configuration.trailing_newline, } } } @@ -145,6 +147,10 @@ impl ServiceLanguage for GraphqlLanguage { .bracket_spacing .or(global.bracket_spacing) .unwrap_or_default(); + let trailing_newline = language + .trailing_newline + .or(global.trailing_newline) + .unwrap_or_default(); let mut options = GraphqlFormatOptions::new( document_file_source @@ -156,7 +162,8 @@ impl ServiceLanguage for GraphqlLanguage { .with_line_width(line_width) .with_line_ending(line_ending) .with_bracket_spacing(bracket_spacing) - .with_quote_style(language.quote_style.unwrap_or_default()); + .with_quote_style(language.quote_style.unwrap_or_default()) + .with_trailing_newline(trailing_newline); overrides.apply_override_graphql_format_options(path, &mut options); @@ -304,19 +311,19 @@ impl ExtensionHandler for GraphqlFileHandler { } } -fn formatter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn formatter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.formatter_enabled_for_file_path::(path) } -fn linter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn linter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.linter_enabled_for_file_path::(path) } -fn assist_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn assist_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.assist_enabled_for_file_path::(path) } -fn search_enabled(_path: &Utf8Path, _settings: &Settings) -> bool { +fn search_enabled(_path: &Utf8Path, _settings: &SettingsWithEditor) -> bool { true } @@ -324,7 +331,7 @@ fn parse( _biome_path: &BiomePath, file_source: DocumentFileSource, text: &str, - _settings: &Settings, + _settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let parse = parse_graphql_with_cache(text, cache); @@ -348,7 +355,7 @@ fn debug_formatter_ir( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -364,7 +371,7 @@ fn format( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -381,7 +388,7 @@ fn format_range( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -395,7 +402,7 @@ fn format_on_type( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -440,7 +447,7 @@ fn lint(params: LintParams) -> LintResults { let tree = params.parse.tree(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.path.as_path()) @@ -505,7 +512,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult { ); let mut actions = Vec::new(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(settings, analyzer_options) + AnalyzerVisitorBuilder::new(settings.as_ref(), analyzer_options) .with_only(only) .with_skip(skip) .with_path(path.as_path()) @@ -545,14 +552,17 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result( params.biome_path, ¶ms.document_file_source, params.suppression_reason.as_deref(), ); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.biome_path.as_path()) diff --git a/crates/biome_service/src/file_handlers/grit.rs b/crates/biome_service/src/file_handlers/grit.rs index 204b5a47b0c9..b1036229d31d 100644 --- a/crates/biome_service/src/file_handlers/grit.rs +++ b/crates/biome_service/src/file_handlers/grit.rs @@ -3,7 +3,9 @@ use super::{ ExtensionHandler, FixAllParams, FormatterCapabilities, LintParams, LintResults, ParseResult, ParserCapabilities, SearchCapabilities, }; -use crate::settings::{OverrideSettings, check_feature_activity, check_override_feature_activity}; +use crate::settings::{ + OverrideSettings, SettingsWithEditor, check_feature_activity, check_override_feature_activity, +}; use crate::workspace::{FixFileResult, GetSyntaxTreeResult}; use crate::{ WorkspaceError, @@ -15,7 +17,9 @@ use biome_configuration::grit::{ GritLinterConfiguration, GritLinterEnabled, }; use biome_diagnostics::{Diagnostic, Severity}; -use biome_formatter::{FormatError, IndentStyle, IndentWidth, LineEnding, LineWidth, Printed}; +use biome_formatter::{ + FormatError, IndentStyle, IndentWidth, LineEnding, LineWidth, Printed, TrailingNewline, +}; use biome_fs::BiomePath; use biome_grit_formatter::{context::GritFormatOptions, format_node, format_sub_tree}; use biome_grit_parser::parse_grit_with_cache; @@ -33,6 +37,7 @@ pub struct GritFormatterSettings { pub indent_width: Option, pub indent_style: Option, pub enabled: Option, + pub trailing_newline: Option, } impl From for GritFormatterSettings { @@ -43,6 +48,7 @@ impl From for GritFormatterSettings { indent_width: config.indent_width, indent_style: config.indent_style, enabled: config.enabled, + trailing_newline: config.trailing_newline, } } } @@ -127,12 +133,18 @@ impl ServiceLanguage for GritLanguage { .or(global.line_ending) .unwrap_or_default(); + let trailing_newline = language + .trailing_newline + .or(global.trailing_newline) + .unwrap_or_default(); + let mut options = GritFormatOptions::new(file_source.to_grit_file_source().unwrap_or_default()) .with_indent_style(indent_style) .with_indent_width(indent_width) .with_line_width(line_width) - .with_line_ending(line_ending); + .with_line_ending(line_ending) + .with_trailing_newline(trailing_newline); overrides.apply_override_grit_format_options(path, &mut options); @@ -277,19 +289,19 @@ impl ExtensionHandler for GritFileHandler { } } -fn formatter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn formatter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.formatter_enabled_for_file_path::(path) } -fn linter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn linter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.linter_enabled_for_file_path::(path) } -fn assist_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn assist_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.assist_enabled_for_file_path::(path) } -fn search_enabled(_path: &Utf8Path, _settings: &Settings) -> bool { +fn search_enabled(_path: &Utf8Path, _settings: &SettingsWithEditor) -> bool { true } @@ -297,7 +309,7 @@ fn parse( _biome_path: &BiomePath, file_source: DocumentFileSource, text: &str, - _settings: &Settings, + _settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let parse = parse_grit_with_cache(text, cache); @@ -321,7 +333,7 @@ fn debug_formatter_ir( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -337,7 +349,7 @@ fn format( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -355,7 +367,7 @@ fn format_range( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -370,7 +382,7 @@ fn format_on_type( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); diff --git a/crates/biome_service/src/file_handlers/html.rs b/crates/biome_service/src/file_handlers/html.rs index 741797e459ad..8dcf79865154 100644 --- a/crates/biome_service/src/file_handlers/html.rs +++ b/crates/biome_service/src/file_handlers/html.rs @@ -5,7 +5,10 @@ use super::{ ParserCapabilities, ProcessFixAll, ProcessLint, SearchCapabilities, UpdateSnippetsNodes, }; use crate::configuration::to_analyzer_rules; -use crate::settings::{OverrideSettings, check_feature_activity, check_override_feature_activity}; +use crate::settings::{ + OverrideSettings, SettingsWithEditor, check_feature_activity, check_override_feature_activity, +}; +use crate::workspace::document::services::embedded_bindings::EmbeddedBuilder; use crate::workspace::{CodeAction, CssDocumentServices, DocumentServices, EmbeddedSnippet}; use crate::workspace::{FixFileResult, PullActionsResult}; use crate::{ @@ -18,13 +21,13 @@ use biome_configuration::html::{ HtmlAssistConfiguration, HtmlAssistEnabled, HtmlFormatterConfiguration, HtmlFormatterEnabled, HtmlLinterConfiguration, HtmlLinterEnabled, HtmlParseInterpolation, HtmlParserConfiguration, }; -use biome_css_parser::parse_css_with_offset_and_cache; +use biome_css_parser::{CssModulesKind, parse_css_with_offset_and_cache}; use biome_css_syntax::{CssFileSource, CssLanguage}; use biome_formatter::format_element::{Interned, LineMode}; use biome_formatter::prelude::{Document, Tag}; use biome_formatter::{ AttributePosition, BracketSameLine, FormatElement, IndentStyle, IndentWidth, LineEnding, - LineWidth, Printed, + LineWidth, Printed, TrailingNewline, }; use biome_fs::BiomePath; use biome_html_analyze::analyze; @@ -38,14 +41,17 @@ use biome_html_formatter::{ use biome_html_parser::{HtmlParseOptions, parse_html_with_cache}; use biome_html_syntax::element_ext::AnyEmbeddedContent; use biome_html_syntax::{ - AstroEmbeddedContent, HtmlElement, HtmlLanguage, HtmlRoot, HtmlSyntaxNode, + AnySvelteDirective, AstroEmbeddedContent, HtmlAttributeInitializerClause, + HtmlDoubleTextExpression, HtmlElement, HtmlFileSource, HtmlLanguage, HtmlRoot, + HtmlSingleTextExpression, HtmlSyntaxNode, HtmlTextExpression, HtmlTextExpressions, HtmlVariant, + VueDirective, VueVBindShorthandDirective, VueVOnShorthandDirective, VueVSlotShorthandDirective, }; use biome_js_parser::parse_js_with_offset_and_cache; use biome_js_syntax::{EmbeddingKind, JsFileSource, JsLanguage}; use biome_json_parser::parse_json_with_offset_and_cache; use biome_json_syntax::{JsonFileSource, JsonLanguage}; use biome_parser::AnyParse; -use biome_rowan::{AstNode, AstNodeList, BatchMutation, NodeCache, SendNode}; +use biome_rowan::{AstNode, AstNodeList, BatchMutation, NodeCache, SendNode, TextSize}; use camino::Utf8Path; use either::Either; use std::borrow::Cow; @@ -79,6 +85,7 @@ pub struct HtmlFormatterSettings { pub whitespace_sensitivity: Option, pub indent_script_and_style: Option, pub self_close_void_elements: Option, + pub trailing_newline: Option, } impl From for HtmlFormatterSettings { @@ -94,6 +101,7 @@ impl From for HtmlFormatterSettings { whitespace_sensitivity: config.whitespace_sensitivity, indent_script_and_style: config.indent_script_and_style, self_close_void_elements: config.self_close_void_elements, + trailing_newline: config.trailing_newline, } } } @@ -174,6 +182,7 @@ impl ServiceLanguage for HtmlLanguage { let whitespace_sensitivity = language.whitespace_sensitivity.unwrap_or_default(); let indent_script_and_style = language.indent_script_and_style.unwrap_or_default(); let self_close_void_elements = language.self_close_void_elements.unwrap_or_default(); + let trailing_newline = language.trailing_newline.unwrap_or_default(); let mut options = HtmlFormatOptions::new(file_source.to_html_file_source().unwrap_or_default()) @@ -185,7 +194,8 @@ impl ServiceLanguage for HtmlLanguage { .with_bracket_same_line(bracket_same_line) .with_whitespace_sensitivity(whitespace_sensitivity) .with_indent_script_and_style(indent_script_and_style) - .with_self_close_void_elements(self_close_void_elements); + .with_self_close_void_elements(self_close_void_elements) + .with_trailing_newline(trailing_newline); overrides.apply_override_html_format_options(path, &mut options); @@ -356,19 +366,19 @@ impl ExtensionHandler for HtmlFileHandler { } } -fn formatter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn formatter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.formatter_enabled_for_file_path::(path) } -fn linter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn linter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.linter_enabled_for_file_path::(path) } -fn assist_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn assist_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.assist_enabled_for_file_path::(path) } -fn search_enabled(_path: &Utf8Path, _settings: &Settings) -> bool { +fn search_enabled(_path: &Utf8Path, _settings: &SettingsWithEditor) -> bool { true } @@ -376,7 +386,7 @@ fn parse( biome_path: &BiomePath, file_source: DocumentFileSource, text: &str, - settings: &Settings, + settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let options = settings.parse_options::(biome_path, &file_source); @@ -392,55 +402,391 @@ fn parse_embedded_nodes( root: &AnyParse, biome_path: &BiomePath, file_source: &DocumentFileSource, - settings: &Settings, + settings: &SettingsWithEditor, cache: &mut NodeCache, + builder: &mut EmbeddedBuilder, ) -> ParseEmbedResult { let mut nodes = Vec::new(); let html_root: HtmlRoot = root.tree(); + let Some(file_source) = file_source.to_html_file_source() else { + return ParseEmbedResult::default(); + }; - // Walk through all HTML elements looking for script tags and style tags - for element in html_root.syntax().descendants() { - if let Some(astro_embedded_content) = AstroEmbeddedContent::cast_ref(&element) { - let result = parse_astro_embedded_script( - astro_embedded_content.clone(), - cache, - biome_path, - settings, - ); - if let Some((content, file_source)) = result { - nodes.push((content.into(), file_source)); + match file_source.variant() { + HtmlVariant::Standard(text_expression) => { + for element in html_root.syntax().descendants() { + if let Some(html_element) = HtmlElement::cast_ref(&element) { + if let Some(script_type) = html_element.get_script_type() { + if script_type.is_javascript() { + let result = parse_embedded_script( + html_element.clone(), + cache, + biome_path, + &file_source, + settings, + builder, + ); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source.into())); + } + } else if script_type.is_json() { + let result = parse_embedded_json( + html_element.clone(), + cache, + biome_path, + settings, + ); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + } else if html_element.is_style_tag() { + let result = parse_embedded_style( + html_element.clone(), + cache, + biome_path, + &file_source, + settings, + ); + if let Some((content, services, file_source)) = result { + nodes.push(((content, services).into(), file_source)); + } + } + } + + match text_expression { + HtmlTextExpressions::Single => { + if let Some(text_expression) = HtmlSingleTextExpression::cast_ref(&element) + { + let result = parse_single_text_expression( + text_expression, + cache, + biome_path, + settings, + ); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + } + + HtmlTextExpressions::Double => { + if let Some(text_expression) = HtmlDoubleTextExpression::cast_ref(&element) + { + let result = parse_double_text_expression( + text_expression, + cache, + biome_path, + settings, + ); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + } + HtmlTextExpressions::None => {} + } } } - let Some(element) = HtmlElement::cast_ref(&element) else { - continue; - }; + HtmlVariant::Astro => { + for element in html_root.syntax().descendants() { + if let Some(astro_embedded_content) = AstroEmbeddedContent::cast_ref(&element) { + let result = parse_astro_embedded_script( + astro_embedded_content.clone(), + cache, + biome_path, + settings, + builder, + ); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + + if let Some(text_expression) = HtmlSingleTextExpression::cast_ref(&element) { + let result = + parse_astro_text_expression(text_expression, cache, biome_path, settings); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + + if let Some(html_element) = HtmlElement::cast_ref(&element) { + if let Some(script_type) = html_element.get_script_type() { + if script_type.is_javascript() { + let result = parse_embedded_script( + html_element.clone(), + cache, + biome_path, + &file_source, + settings, + builder, + ); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source.into())); + } + } else if script_type.is_json() { + let result = parse_embedded_json( + html_element.clone(), + cache, + biome_path, + settings, + ); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + } else if html_element.is_style_tag() { + let result = parse_embedded_style( + html_element.clone(), + cache, + biome_path, + &file_source, + settings, + ); + if let Some((content, services, file_source)) = result { + nodes.push(((content, services).into(), file_source)); + } + } + } + } + } + HtmlVariant::Vue => { + let mut elements = vec![]; + let mut snippet_expressions = vec![]; + for element in html_root.syntax().descendants() { + if let Some(text_expression) = HtmlDoubleTextExpression::cast_ref(&element) { + snippet_expressions.push(text_expression); + } + + if let Some(element) = HtmlElement::cast_ref(&element) { + elements.push(element); + } + } + + let mut embedded_file_source = JsFileSource::js_module(); + for element in elements { + if let Some(script_type) = element.get_script_type() { + if script_type.is_javascript() { + let result = parse_embedded_script( + element.clone(), + cache, + biome_path, + &file_source, + settings, + builder, + ); + if let Some((content, file_source)) = result { + embedded_file_source = file_source; + nodes.push((content.into(), file_source.into())); + } + } else if script_type.is_json() { + let result = + parse_embedded_json(element.clone(), cache, biome_path, settings); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + } else if element.is_style_tag() { + let result = parse_embedded_style( + element.clone(), + cache, + biome_path, + &file_source, + settings, + ); + if let Some((content, services, file_source)) = result { + nodes.push(((content, services).into(), file_source)); + } + } + } - if let Some(script_type) = element.get_script_type() { - if script_type.is_javascript() { - let result = parse_embedded_script( - element.clone(), + for snippet in snippet_expressions { + let result = parse_vue_text_expression( + snippet, cache, biome_path, - file_source, settings, + embedded_file_source, ); + if let Some((content, file_source)) = result { nodes.push((content.into(), file_source)); } - } else if script_type.is_json() { - let result = parse_embedded_json(element.clone(), cache, biome_path, settings); + } + + // Parse Vue directive attributes (v-on, v-bind, v-if, etc.) + for element in html_root.syntax().descendants() { + // Handle @click shorthand (VueVOnShorthandDirective) + if let Some(directive) = VueVOnShorthandDirective::cast_ref(&element) + && let Some(initializer) = directive.initializer() + { + let file_source = + embedded_file_source.with_embedding_kind(EmbeddingKind::Vue { + setup: false, + is_source: false, + }); + if let Some((content, doc_source)) = parse_directive_string_value( + &initializer, + cache, + biome_path, + settings, + file_source, + ) { + nodes.push((content.into(), doc_source)); + } + } + + // Handle :prop shorthand (VueVBindShorthandDirective) + if let Some(directive) = VueVBindShorthandDirective::cast_ref(&element) + && let Some(initializer) = directive.initializer() + { + let file_source = + embedded_file_source.with_embedding_kind(EmbeddingKind::Vue { + setup: false, + is_source: false, + }); + if let Some((content, doc_source)) = parse_directive_string_value( + &initializer, + cache, + biome_path, + settings, + file_source, + ) { + nodes.push((content.into(), doc_source)); + } + } + + // Handle #slot shorthand (VueVSlotShorthandDirective) + if let Some(directive) = VueVSlotShorthandDirective::cast_ref(&element) + && let Some(initializer) = directive.initializer() + { + let file_source = + embedded_file_source.with_embedding_kind(EmbeddingKind::Vue { + setup: false, + is_source: false, + }); + if let Some((content, doc_source)) = parse_directive_string_value( + &initializer, + cache, + biome_path, + settings, + file_source, + ) { + nodes.push((content.into(), doc_source)); + } + } + + // Handle full directives (v-on:, v-bind:, v-if, v-show, etc.) + if let Some(directive) = VueDirective::cast_ref(&element) + && let Some(initializer) = directive.initializer() + { + let file_source = + embedded_file_source.with_embedding_kind(EmbeddingKind::Vue { + setup: false, + is_source: false, + }); + if let Some((content, doc_source)) = parse_directive_string_value( + &initializer, + cache, + biome_path, + settings, + file_source, + ) { + nodes.push((content.into(), doc_source)); + } + } + } + } + HtmlVariant::Svelte => { + let mut elements = vec![]; + let mut snippet_expressions = vec![]; + for element in html_root.syntax().descendants() { + if let Some(text_expression) = HtmlSingleTextExpression::cast_ref(&element) { + snippet_expressions.push(text_expression); + } + + if let Some(element) = HtmlElement::cast_ref(&element) { + elements.push(element); + } + } + + let mut embedded_file_source = JsFileSource::js_module(); + for element in elements { + if let Some(script_type) = element.get_script_type() { + if script_type.is_javascript() { + let result = parse_embedded_script( + element.clone(), + cache, + biome_path, + &file_source, + settings, + builder, + ); + if let Some((content, file_source)) = result { + embedded_file_source = file_source; + nodes.push((content.into(), file_source.into())); + } + } else if script_type.is_json() { + let result = + parse_embedded_json(element.clone(), cache, biome_path, settings); + if let Some((content, file_source)) = result { + nodes.push((content.into(), file_source)); + } + } + } else if element.is_style_tag() { + let result = parse_embedded_style( + element.clone(), + cache, + biome_path, + &file_source, + settings, + ); + if let Some((content, services, file_source)) = result { + nodes.push(((content, services).into(), file_source)); + } + } + } + + for snippet in snippet_expressions { + let result = parse_svelte_text_expression( + snippet, + cache, + biome_path, + settings, + embedded_file_source, + ); + if let Some((content, file_source)) = result { nodes.push((content.into(), file_source)); } } - } else if element.is_style_tag() { - let result = parse_embedded_style(element.clone(), cache, biome_path, settings); - if let Some((content, services, file_source)) = result { - nodes.push(((content, services).into(), file_source)); + + // Parse Svelte directive attributes (bind:, class:, use:, etc.) + // Note: on: event handlers are legacy Svelte 3/4 syntax and not supported. + // Svelte 5 runes mode uses regular attributes for event handlers. + for element in html_root.syntax().descendants() { + // Handle special Svelte directives (bind:, class:, etc.) + if let Some(directive) = AnySvelteDirective::cast_ref(&element) + && let Some(initializer) = directive.initializer() + { + let file_source = embedded_file_source + .with_embedding_kind(EmbeddingKind::Svelte { is_source: false }); + if let Some((content, doc_source)) = parse_directive_text_expression( + &initializer, + cache, + biome_path, + settings, + file_source, + ) { + nodes.push((content.into(), doc_source)); + } + } } } } + ParseEmbedResult { nodes } } @@ -448,7 +794,8 @@ pub(crate) fn parse_astro_embedded_script( element: AstroEmbeddedContent, cache: &mut NodeCache, path: &BiomePath, - settings: &Settings, + settings: &SettingsWithEditor, + builder: &mut EmbeddedBuilder, ) -> Option<(EmbeddedSnippet, DocumentFileSource)> { let content = element.content_token()?; let file_source = @@ -462,6 +809,7 @@ pub(crate) fn parse_astro_embedded_script( options, cache, ); + builder.visit_js_source_snippet(&parse.tree()); Some(( EmbeddedSnippet::new( @@ -478,10 +826,10 @@ pub(crate) fn parse_embedded_script( element: HtmlElement, cache: &mut NodeCache, path: &BiomePath, - html_file_source: &DocumentFileSource, - settings: &Settings, -) -> Option<(EmbeddedSnippet, DocumentFileSource)> { - let html_file_source = html_file_source.to_html_file_source()?; + html_file_source: &HtmlFileSource, + settings: &SettingsWithEditor, + builder: &mut EmbeddedBuilder, +) -> Option<(EmbeddedSnippet, JsFileSource)> { if element.is_javascript_tag() { let file_source = if html_file_source.is_svelte() || html_file_source.is_vue() { let mut file_source = if element.is_typescript_lang() { @@ -494,15 +842,20 @@ pub(crate) fn parse_embedded_script( JsFileSource::js_module() }; if html_file_source.is_svelte() { - file_source = file_source.with_embedding_kind(EmbeddingKind::Svelte); + file_source = + file_source.with_embedding_kind(EmbeddingKind::Svelte { is_source: true }); } else if html_file_source.is_vue() { file_source = file_source.with_embedding_kind(EmbeddingKind::Vue { setup: element.is_script_with_setup_attribute(), + is_source: true, }); } file_source } else if html_file_source.is_astro() { - JsFileSource::ts().with_embedding_kind(EmbeddingKind::Astro { frontmatter: false }) + // Astro script tags are parsed as regular TypeScript/JavaScript modules + // They should not use EmbeddingKind::Astro because they are source code, + // not template expressions + JsFileSource::ts() } else { let is_module = element.is_javascript_module().unwrap_or_default(); if is_module { @@ -532,6 +885,8 @@ pub(crate) fn parse_embedded_script( cache, ); + builder.visit_js_source_snippet(&parse.tree()); + Some(EmbeddedSnippet::new( parse.into(), child.range(), @@ -539,7 +894,7 @@ pub(crate) fn parse_embedded_script( content.text_range().start(), )) })?; - Some((embedded_content, file_source.into())) + Some((embedded_content, file_source)) } else { None } @@ -550,7 +905,8 @@ pub(crate) fn parse_embedded_style( element: HtmlElement, cache: &mut NodeCache, biome_path: &BiomePath, - settings: &Settings, + html_file_source: &HtmlFileSource, + settings: &SettingsWithEditor, ) -> Option<( EmbeddedSnippet, DocumentServices, @@ -567,23 +923,34 @@ pub(crate) fn parse_embedded_style( return None; } - let file_source = DocumentFileSource::Css(CssFileSource::css()); + let file_source = if html_file_source.is_html() { + DocumentFileSource::Css(CssFileSource::css()) + } else { + DocumentFileSource::Css(CssFileSource::new_css_modules()) + }; let (snippet, services) = element.children().iter().next().and_then(|child| { let child = child.as_any_html_content()?; let child = child.as_html_embedded_content()?; - let options = settings.parse_options::(biome_path, &file_source); + let mut options = settings.parse_options::(biome_path, &file_source); + if html_file_source.is_vue() { + options.css_modules = CssModulesKind::Vue + } else if !html_file_source.is_html() { + options.css_modules = CssModulesKind::Classic + } let content = child.value_token().ok()?; let parse = parse_css_with_offset_and_cache( content.text(), + file_source.to_css_file_source().unwrap_or_default(), content.text_range().start(), cache, options, ); let mut services = CssDocumentServices::default(); - if settings.is_linter_enabled() || settings.is_assist_enabled() { + if settings.as_ref().is_linter_enabled() || settings.as_ref().is_assist_enabled() { services = services.with_css_semantic_model(&parse.tree()) } + Some(( EmbeddedSnippet::new( parse.into(), @@ -604,7 +971,7 @@ pub(crate) fn parse_embedded_json( element: HtmlElement, cache: &mut NodeCache, biome_path: &BiomePath, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Option<(EmbeddedSnippet, DocumentFileSource)> { // This is probably an error if element.children().len() > 1 { @@ -634,6 +1001,191 @@ pub(crate) fn parse_embedded_json( Some((script_children, file_source)) } +pub(crate) fn parse_text_expression( + expression: HtmlTextExpression, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, + file_source: JsFileSource, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + let content = expression.html_literal_token().ok()?; + let document_file_source = DocumentFileSource::Js(file_source); + let options = settings.parse_options::(biome_path, &document_file_source); + let parse = parse_js_with_offset_and_cache( + content.text(), + content.text_range().start(), + file_source, + options, + cache, + ); + + let snippet = EmbeddedSnippet::new( + parse.into(), + expression.range(), + content.text_range(), + content.text_range().start(), + ); + Some((snippet, document_file_source)) +} + +/// Parses Svelte single text expressions `{ expression }` +pub(crate) fn parse_svelte_text_expression( + element: HtmlSingleTextExpression, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, + file_source: JsFileSource, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + let expression = element.expression().ok()?; + let file_source = file_source.with_embedding_kind(EmbeddingKind::Svelte { is_source: false }); + parse_text_expression(expression, cache, biome_path, settings, file_source) +} + +pub(crate) fn parse_double_text_expression( + element: HtmlDoubleTextExpression, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + let expression = element.expression().ok()?; + let file_source = JsFileSource::js_module(); + parse_text_expression(expression, cache, biome_path, settings, file_source) +} + +pub(crate) fn parse_single_text_expression( + element: HtmlSingleTextExpression, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + let expression = element.expression().ok()?; + let file_source = JsFileSource::js_module(); + parse_text_expression(expression, cache, biome_path, settings, file_source) +} + +/// Parses Astro single text expressions `{ expression }` +pub(crate) fn parse_astro_text_expression( + element: HtmlSingleTextExpression, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + let expression = element.expression().ok()?; + let content = expression.html_literal_token().ok()?; + // Astro is kinda weird in its JSX-like expressions. They are JS, but they contain HTML, not JSX. + // That's because Astro doesn't parse what's inside the expressions, actually. In fact, their arrow function callbacks + // don't have curly brackets. + // + // As for now, we use the TSX parser, hoping it won't bite us back in the future. + let file_source = + JsFileSource::tsx().with_embedding_kind(EmbeddingKind::Astro { frontmatter: false }); + let document_file_source = DocumentFileSource::Js(file_source); + let options = settings.parse_options::(biome_path, &document_file_source); + let parse = parse_js_with_offset_and_cache( + content.text(), + content.text_range().start(), + file_source, + options, + cache, + ); + let snippet = EmbeddedSnippet::new( + parse.into(), + expression.range(), + content.text_range(), + content.text_range().start(), + ); + Some((snippet, document_file_source)) +} + +/// Parses Vue double text expressions `{{ expression }}` +pub(crate) fn parse_vue_text_expression( + element: HtmlDoubleTextExpression, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, + js_file_source: JsFileSource, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + let expression = element.expression().ok()?; + let file_source = js_file_source.with_embedding_kind(EmbeddingKind::Vue { + setup: false, + is_source: false, + }); + parse_text_expression(expression, cache, biome_path, settings, file_source) +} + +/// Parses a directive attribute's string value as JavaScript +/// +/// Extracts the JavaScript expression from a Vue/Svelte directive attribute value +/// (e.g., `@click="handler()"` -> `handler()`) and parses it as an embedded JavaScript snippet. +/// +/// The function: +/// 1. Extracts the attribute initializer clause (`="value"`) +/// 2. Gets the HTML string node from the initializer +/// 3. Uses `inner_string_text()` to extract the content without quotes +/// 4. Parses the content as JavaScript with the correct offset +/// 5. Returns an `EmbeddedSnippet` with proper range information +fn parse_directive_string_value( + value: &HtmlAttributeInitializerClause, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, + file_source: JsFileSource, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + // Get the HTML string from the initializer (e.g., `"handler()"`) + let value_node = value.value().ok()?; + let html_string = value_node.as_html_string()?; + + // Get the token and extract inner text (without quotes) + let content_token = html_string.value_token().ok()?; + let inner_text = html_string.inner_string_text().ok()?; + let text = inner_text.text(); + + // Calculate offset: start of token + 1 (for opening quote) + let token_range = content_token.text_trimmed_range(); + let inner_offset = token_range.start() + TextSize::from(1); + + // Parse as JavaScript + let document_file_source = DocumentFileSource::Js(file_source); + let options = settings.parse_options::(biome_path, &document_file_source); + let parse = parse_js_with_offset_and_cache(text, inner_offset, file_source, options, cache); + + // Create snippet with proper ranges + let snippet = EmbeddedSnippet::new( + parse.into(), + value.range(), // Full attribute range + token_range, // Token range (string with quotes) + inner_offset, // Offset where JS starts (after opening quote) + ); + + Some((snippet, document_file_source)) +} + +/// Parses a Svelte directive attribute's text expression value as JavaScript +/// +/// Extracts the JavaScript expression from a Svelte directive attribute value +/// (e.g., `on:click={handler}` -> `handler`) and parses it as an embedded JavaScript snippet. +/// +/// Unlike Vue which uses quoted strings, Svelte uses curly braces with text expressions. +fn parse_directive_text_expression( + value: &HtmlAttributeInitializerClause, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, + file_source: JsFileSource, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + // Get the text expression from the initializer (e.g., `{handler}`) + let value_node = value.value().ok()?; + let text_expression = value_node.as_html_attribute_single_text_expression()?; + + // Extract the expression inside the curly braces + let expression = text_expression.expression().ok()?; + + // Parse as JavaScript using the same logic as Svelte text expressions + let document_file_source = DocumentFileSource::Js(file_source); + parse_text_expression(expression, cache, biome_path, settings, file_source) + .map(|(snippet, _)| (snippet, document_file_source)) +} + fn debug_syntax_tree(_biome_path: &BiomePath, parse: AnyParse) -> GetSyntaxTreeResult { let syntax: HtmlSyntaxNode = parse.syntax(); let tree: HtmlRoot = parse.tree(); @@ -647,7 +1199,7 @@ fn debug_formatter_ir( path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(path, document_file_source); @@ -663,7 +1215,7 @@ fn format( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -680,7 +1232,7 @@ fn format_embedded( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, embedded_nodes: Vec, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -743,6 +1295,10 @@ fn format_embedded( } }); + // Propagate expand flags again after inserting embedded content, + // so that groups inside the embedded documents properly expand. + formatted.propagate_expand(); + match formatted.print() { Ok(printed) => Ok(printed), Err(error) => Err(WorkspaceError::FormatError(error.into())), @@ -760,7 +1316,7 @@ fn lint(params: LintParams) -> LintResults { let tree = params.parse.tree(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.path.as_path()) @@ -777,9 +1333,11 @@ fn lint(params: LintParams) -> LintResults { let mut process_lint = ProcessLint::new(¶ms); - let (_, analyze_diagnostics) = analyze(&tree, filter, &analyzer_options, |signal| { - process_lint.process_signal(signal) - }); + let source_type = params.language.to_html_file_source().unwrap_or_default(); + let (_, analyze_diagnostics) = + analyze(&tree, filter, &analyzer_options, source_type, |signal| { + process_lint.process_signal(signal) + }); process_lint.into_result( params @@ -810,7 +1368,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult { let _ = debug_span!("Code actions HTML", range =? range, path =? path).entered(); let tree = parse.tree(); let _ = trace_span!("Parsed file", tree =? tree).entered(); - let Some(_) = language.to_html_file_source() else { + let Some(source_type) = language.to_html_file_source() else { error!("Could not determine the HTML file source of the file"); return PullActionsResult { actions: Vec::new(), @@ -820,7 +1378,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult { settings.analyzer_options::(path, &language, suppression_reason.as_deref()); let mut actions = Vec::new(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(settings, analyzer_options) + AnalyzerVisitorBuilder::new(settings.as_ref(), analyzer_options) .with_only(only) .with_skip(skip) .with_path(path.as_path()) @@ -835,7 +1393,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult { range, }; - analyze(&tree, filter, &analyzer_options, |signal| { + analyze(&tree, filter, &analyzer_options, source_type, |signal| { actions.extend(signal.actions().into_code_action_iter().map(|item| { CodeAction { category: item.category.clone(), @@ -858,14 +1416,17 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result( params.biome_path, ¶ms.document_file_source, params.suppression_reason.as_deref(), ); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.biome_path.as_path()) @@ -886,11 +1447,14 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result tree, @@ -908,7 +1472,11 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result` containing the leading whitespace. If the entire string is whitespace, +/// returns the entire string. If there's no leading whitespace, returns an empty string. +/// +/// # Examples +/// ```ignore +/// assert_eq!(read_leading_trivia("\n\tconsole.log('Hi');"), "\n\t"); +/// assert_eq!(read_leading_trivia("console.log('Hi');"), ""); +/// assert_eq!(read_leading_trivia(" "), " "); +/// ``` +fn read_leading_trivia(value: &str) -> Cow<'_, str> { + let bytes = value.as_bytes(); + let count = bytes + .iter() + .take_while(|&&b| matches!(b, b' ' | b'\t' | b'\n' | b'\r')) + .count(); + + if count > 0 { + Cow::Borrowed(&value[..count]) + } else { + Cow::Borrowed("") + } +} + +/// Extracts all trailing whitespace (spaces, tabs, newlines, carriage returns) from a string. +/// +/// This function iterates backward through the string bytes to find where the actual content ends. +/// For HTML embedded content tokens, whitespace is part of the token text itself, not stored as trivia. +/// +/// # Arguments +/// * `value` - The string to extract trailing trivia from +/// +/// # Returns +/// A `Cow<'_, str>` containing the trailing whitespace. If the entire string is whitespace, +/// returns an empty string (because leading trivia would have consumed it all). If there's no +/// trailing whitespace, returns an empty string. +/// +/// # Examples +/// ```ignore +/// assert_eq!(read_trailing_trivia("console.log('Hi');\n"), "\n"); +/// assert_eq!(read_trailing_trivia("console.log('Hi');"), ""); +/// assert_eq!(read_trailing_trivia(" "), ""); +/// ``` +fn read_trailing_trivia(value: &str) -> Cow<'_, str> { + let bytes = value.as_bytes(); + let count = bytes + .iter() + .rev() + .take_while(|&&b| matches!(b, b' ' | b'\t' | b'\n' | b'\r')) + .count(); + + if count > 0 { + Cow::Borrowed(&value[value.len() - count..]) + } else { + Cow::Borrowed("") + } +} diff --git a/crates/biome_service/src/file_handlers/javascript.rs b/crates/biome_service/src/file_handlers/javascript.rs index b9986c522d32..b9cff37bdd31 100644 --- a/crates/biome_service/src/file_handlers/javascript.rs +++ b/crates/biome_service/src/file_handlers/javascript.rs @@ -1,16 +1,19 @@ use super::{ AnalyzerCapabilities, AnalyzerVisitorBuilder, CodeActionsParams, DebugCapabilities, - DiagnosticsAndActionsParams, EnabledForPath, ExtensionHandler, FormatterCapabilities, - LintParams, LintResults, ParseResult, ParserCapabilities, ProcessDiagnosticsAndActions, - ProcessFixAll, ProcessLint, SearchCapabilities, search, + DiagnosticsAndActionsParams, EnabledForPath, ExtensionHandler, FormatEmbedNode, + FormatterCapabilities, LintParams, LintResults, ParseEmbedResult, ParseResult, + ParserCapabilities, ProcessDiagnosticsAndActions, ProcessFixAll, ProcessLint, + SearchCapabilities, UpdateSnippetsNodes, search, }; use crate::configuration::to_analyzer_rules; use crate::diagnostics::extension_error; use crate::file_handlers::FixAllParams; use crate::settings::{ - OverrideSettings, Settings, check_feature_activity, check_override_feature_activity, + OverrideSettings, Settings, SettingsWithEditor, check_feature_activity, + check_override_feature_activity, }; -use crate::workspace::{DocumentFileSource, PullDiagnosticsAndActionsResult}; +use crate::workspace::document::services::embedded_bindings::EmbeddedBuilder; +use crate::workspace::{DocumentFileSource, EmbeddedSnippet, PullDiagnosticsAndActionsResult}; use crate::{ WorkspaceError, settings::{FormatSettings, LanguageListSettings, LanguageSettings, ServiceLanguage}, @@ -26,15 +29,21 @@ use biome_configuration::javascript::{ JsGritMetavariable, JsLinterConfiguration, JsLinterEnabled, JsParserConfiguration, JsxEverywhere, JsxRuntime, UnsafeParameterDecoratorsEnabled, }; +use biome_css_parser::parse_css_with_offset_and_cache; +use biome_css_syntax::{CssFileSource, CssLanguage, EmbeddingKind}; +use biome_formatter::prelude::{Document, Interned, LineMode, Tag}; use biome_formatter::{ - AttributePosition, BracketSameLine, BracketSpacing, Expand, FormatError, IndentStyle, - IndentWidth, LineEnding, LineWidth, Printed, QuoteStyle, + AttributePosition, BracketSameLine, BracketSpacing, Expand, FormatElement, FormatError, + IndentStyle, IndentWidth, LineEnding, LineWidth, Printed, QuoteStyle, TrailingNewline, }; use biome_fs::BiomePath; +use biome_graphql_parser::parse_graphql_with_offset_and_cache; +use biome_graphql_syntax::{GraphqlFileSource, GraphqlLanguage}; use biome_js_analyze::utils::rename::{RenameError, RenameSymbolExtensions}; use biome_js_analyze::{ ControlFlowGraph, JsAnalyzerServices, analyze, analyze_with_inspect_matcher, }; +use biome_js_factory::make::ident; use biome_js_formatter::context::trailing_commas::TrailingCommas; use biome_js_formatter::context::{ ArrowParentheses, JsFormatOptions, OperatorLinebreak, QuoteProperties, Semicolons, @@ -43,20 +52,25 @@ use biome_js_formatter::format_node; use biome_js_parser::JsParserOptions; use biome_js_semantic::{SemanticModelOptions, semantic_model}; use biome_js_syntax::{ - AnyJsRoot, JsClassDeclaration, JsClassExpression, JsFileSource, JsFunctionDeclaration, - JsLanguage, JsSyntaxNode, JsVariableDeclarator, TextRange, TextSize, TokenAtOffset, + AnyJsExpression, AnyJsRoot, AnyJsTemplateElement, JsCallArgumentList, JsCallArguments, + JsCallExpression, JsClassDeclaration, JsClassExpression, JsFileSource, JsFunctionDeclaration, + JsLanguage, JsSyntaxNode, JsTemplateChunkElement, JsTemplateExpression, JsVariableDeclarator, + TextRange, TextSize, TokenAtOffset, }; use biome_js_type_info::{GlobalsResolver, ScopeId, TypeData, TypeResolver}; use biome_module_graph::ModuleGraph; use biome_parser::AnyParse; -use biome_rowan::{AstNode, BatchMutationExt, Direction, NodeCache, WalkEvent}; +use biome_rowan::{ + AstNode, AstNodeList, BatchMutation, BatchMutationExt, Direction, NodeCache, SendNode, + WalkEvent, +}; use camino::Utf8Path; use either::Either; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::Debug; use std::sync::Arc; -use tracing::{debug, debug_span, error, trace_span}; +use tracing::{debug, debug_span, error, instrument, trace_span}; #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -77,6 +91,7 @@ pub struct JsFormatterSettings { pub attribute_position: Option, pub expand: Option, pub operator_linebreak: Option, + pub trailing_newline: Option, } impl From for JsFormatterSettings { @@ -98,6 +113,7 @@ impl From for JsFormatterSettings { line_ending: value.line_ending, expand: value.expand, operator_linebreak: value.operator_linebreak, + trailing_newline: value.trailing_newline, } } } @@ -265,6 +281,12 @@ impl ServiceLanguage for JsLanguage { .unwrap_or_default(), ) .with_expand(language.expand.or(global.expand).unwrap_or_default()) + .with_trailing_newline( + language + .trailing_newline + .or(global.trailing_newline) + .unwrap_or_default(), + ) .with_operator_linebreak(language.operator_linebreak.unwrap_or_default()); overrides.override_js_format_options(path, options) @@ -486,7 +508,7 @@ impl ExtensionHandler for JsFileHandler { }, parser: ParserCapabilities { parse: Some(parse), - parse_embedded_nodes: None, + parse_embedded_nodes: Some(parse_embedded_nodes), }, debug: DebugCapabilities { debug_syntax_tree: Some(debug_syntax_tree), @@ -501,14 +523,14 @@ impl ExtensionHandler for JsFileHandler { code_actions: Some(code_actions), fix_all: Some(fix_all), rename: Some(rename), - update_snippets: None, + update_snippets: Some(update_snippets), pull_diagnostics_and_actions: Some(pull_diagnostics_and_actions), }, formatter: FormatterCapabilities { format: Some(format), format_range: Some(format_range), format_on_type: Some(format_on_type), - format_embedded: None, + format_embedded: Some(format_embedded), }, search: SearchCapabilities { search: Some(search), @@ -517,19 +539,19 @@ impl ExtensionHandler for JsFileHandler { } } -pub fn formatter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +pub fn formatter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.formatter_enabled_for_file_path::(path) } -pub fn linter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +pub fn linter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.linter_enabled_for_file_path::(path) } -pub fn assist_enabled(path: &Utf8Path, settings: &Settings) -> bool { +pub fn assist_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.assist_enabled_for_file_path::(path) } -pub fn search_enabled(_path: &Utf8Path, _settings: &Settings) -> bool { +pub fn search_enabled(_path: &Utf8Path, _settings: &SettingsWithEditor) -> bool { true } @@ -537,7 +559,7 @@ fn parse( biome_path: &BiomePath, file_source: DocumentFileSource, text: &str, - settings: &Settings, + settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let options = settings.parse_options::(biome_path, &file_source); @@ -550,6 +572,148 @@ fn parse( } } +fn parse_embedded_nodes( + root: &AnyParse, + biome_path: &BiomePath, + _file_source: &DocumentFileSource, + settings: &SettingsWithEditor, + cache: &mut NodeCache, + _builder: &mut EmbeddedBuilder, +) -> ParseEmbedResult { + if !settings + .as_ref() + .experimental_js_embedded_snippets_enabled() + { + return ParseEmbedResult { nodes: vec![] }; + } + + let mut nodes = Vec::new(); + let js_root: AnyJsRoot = root.tree(); + + // Walk through all JS elements looking for template expressions + for node in js_root.syntax().descendants() { + let Some(expr) = JsTemplateExpression::cast_ref(&node) else { + continue; + }; + + if let Some((content, file_source)) = + parse_template_expression(expr, cache, biome_path, settings) + { + nodes.push((content.into(), file_source)) + } + } + + ParseEmbedResult { nodes } +} + +fn parse_template_expression( + expr: JsTemplateExpression, + cache: &mut NodeCache, + biome_path: &BiomePath, + settings: &SettingsWithEditor, +) -> Option<(EmbeddedSnippet, DocumentFileSource)> { + // TODO: Interpolations are not supported yet. + if expr.elements().len() != 1 { + return None; + } + + let Some(AnyJsTemplateElement::JsTemplateChunkElement(chunk)) = expr.elements().first() else { + return None; + }; + + let tag = expr.tag(); + if is_styled_tag(tag.as_ref()) { + let file_source = DocumentFileSource::Css( + CssFileSource::css().with_embedding_kind(EmbeddingKind::Styled), + ); + let options = settings.parse_options::(biome_path, &file_source); + let content = chunk.template_chunk_token().ok()?; + let parse = parse_css_with_offset_and_cache( + content.text(), + file_source.to_css_file_source().unwrap_or_default(), + content.text_range().start(), + cache, + options, + ); + + let snippet = EmbeddedSnippet::new( + parse.into(), + chunk.range(), + content.text_range(), + content.text_range().start(), + ); + + Some((snippet, file_source)) + } else if is_graphql_tag(tag.as_ref(), &expr) { + let file_source = DocumentFileSource::Graphql(GraphqlFileSource::graphql()); + let content = chunk.template_chunk_token().ok()?; + let parse = parse_graphql_with_offset_and_cache( + content.text(), + content.text_range().start(), + cache, + ); + + let snippet = EmbeddedSnippet::new( + parse.into(), + chunk.range(), + content.text_range(), + content.text_range().start(), + ); + + Some((snippet, file_source)) + } else { + None + } +} + +fn is_styled_tag(tag: Option<&AnyJsExpression>) -> bool { + // css`` + if let Some(AnyJsExpression::JsIdentifierExpression(ident)) = tag + && ident.name().is_ok_and(|name| name.has_name("css")) + { + return true; + } + + // styled.div`` + if let Some(AnyJsExpression::JsStaticMemberExpression(expr)) = tag + && let Ok(AnyJsExpression::JsIdentifierExpression(ident)) = expr.object() + && ident.name().is_ok_and(|name| name.has_name("styled")) + { + return true; + } + + // styled(Component)`` + if let Some(AnyJsExpression::JsCallExpression(expr)) = tag + && let Ok(AnyJsExpression::JsIdentifierExpression(ident)) = expr.callee() + && ident.name().is_ok_and(|name| name.has_name("styled")) + { + return true; + } + + false +} + +fn is_graphql_tag(tag: Option<&AnyJsExpression>, template: &JsTemplateExpression) -> bool { + // gql`` + if let Some(AnyJsExpression::JsIdentifierExpression(ident)) = tag + && ident.name().is_ok_and(|name| name.has_name("gql")) + { + return true; + } + + // graphql(``) + if let Some(list) = template.parent::() + && let Some(args) = list.parent::() + && let Some(call) = args.parent::() + && let Ok(AnyJsExpression::JsIdentifierExpression(ident)) = call.callee() + && ident.name().is_ok_and(|name| name.has_name("graphql")) + { + return true; + } + + false +} + fn debug_syntax_tree(_rome_path: &BiomePath, parse: AnyParse) -> GetSyntaxTreeResult { let syntax: JsSyntaxNode = parse.syntax(); let tree: AnyJsRoot = parse.tree(); @@ -607,12 +771,12 @@ fn debug_formatter_ir( path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(path, document_file_source); let tree = parse.syntax(); - let formatted = format_node(options, &tree)?; + let formatted = format_node(options, &tree, false)?; let root_element = formatted.into_document(); Ok(root_element.to_string()) @@ -741,7 +905,7 @@ pub(crate) fn lint(params: LintParams) -> LintResults { params.suppression_reason.as_deref(), ); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.path.as_path()) @@ -757,8 +921,18 @@ pub(crate) fn lint(params: LintParams) -> LintResults { }; let mut process_lint = ProcessLint::new(¶ms); - let services = + + let mut services = JsAnalyzerServices::from((params.module_graph, params.project_layout, file_source)); + + if let Some(embedded_bindings) = params.document_services.embedded_bindings() { + services.set_embedded_bindings(embedded_bindings.bindings) + } + + if let Some(value_refs) = params.document_services.embedded_value_references() { + services.set_embedded_value_references(value_refs.references) + } + let (_, analyze_diagnostics) = analyze( &tree, filter, @@ -802,7 +976,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult { settings.analyzer_options::(path, &language, suppression_reason.as_deref()); let mut actions = Vec::new(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(settings, analyzer_options) + AnalyzerVisitorBuilder::new(settings.as_ref(), analyzer_options) .with_only(only) .with_skip(skip) .with_path(path.as_path()) @@ -857,14 +1031,17 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result( params.biome_path, ¶ms.document_file_source, params.suppression_reason.as_deref(), ); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.biome_path.as_path()) @@ -894,12 +1071,20 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result Result Result { let options = settings.format_options::(biome_path, document_file_source); debug!("{:?}", &options); let tree = parse.syntax(); - let formatted = format_node(options, &tree)?; + let formatted = format_node(options, &tree, false)?; match formatted.print() { Ok(printed) => Ok(printed), Err(error) => { @@ -959,7 +1145,7 @@ pub(crate) fn format_range( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); @@ -974,7 +1160,7 @@ pub(crate) fn format_on_type( path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { let options = settings.format_options::(path, document_file_source); @@ -1007,6 +1193,60 @@ pub(crate) fn format_on_type( Ok(printed) } +fn format_embedded( + biome_path: &BiomePath, + document_file_source: &DocumentFileSource, + parse: AnyParse, + settings: &SettingsWithEditor, + embedded_nodes: Vec, +) -> Result { + let tree = parse.syntax(); + let options = settings.format_options::(biome_path, document_file_source); + let mut formatted = format_node(options, &tree, true)?; + + formatted.format_embedded(move |range| { + let mut iter = embedded_nodes.iter(); + let node = iter.find(|node| node.range == range)?; + + let wrap_document = |document: Document| { + // TODO: Option to disable indent here? + let elements = vec![ + FormatElement::Line(LineMode::Hard), + FormatElement::Tag(Tag::StartIndent), + FormatElement::Line(LineMode::Hard), + FormatElement::Interned(Interned::new(document.into_elements())), + FormatElement::Tag(Tag::EndIndent), + ]; + Document::new(elements) + }; + + match node.source { + DocumentFileSource::Css(_) => { + let css_options = settings.format_options::(biome_path, &node.source); + let node = node.node.clone().embedded_syntax::(); + let formatted = + biome_css_formatter::format_node_with_offset(css_options, &node).ok()?; + Some(wrap_document(formatted.into_document())) + } + DocumentFileSource::Graphql(_) => { + let graphql_options = + settings.format_options::(biome_path, &node.source); + let node = node.node.clone().embedded_syntax::(); + let formatted = + biome_graphql_formatter::format_node_with_offset(graphql_options, &node) + .ok()?; + Some(wrap_document(formatted.into_document())) + } + _ => None, + } + }); + + match formatted.print() { + Ok(printed) => Ok(printed), + Err(error) => Err(WorkspaceError::FormatError(error.into())), + } +} + pub(crate) fn pull_diagnostics_and_actions( params: DiagnosticsAndActionsParams, ) -> PullDiagnosticsAndActionsResult { @@ -1030,7 +1270,7 @@ pub(crate) fn pull_diagnostics_and_actions( let analyzer_options = settings.analyzer_options::(path, &language, suppression_reason.as_deref()); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(settings, analyzer_options) + AnalyzerVisitorBuilder::new(settings.as_ref(), analyzer_options) .with_only(only) .with_skip(skip) .with_path(path.as_path()) @@ -1106,3 +1346,34 @@ fn rename( )) } } + +#[instrument(level = "debug", skip_all)] +fn update_snippets( + root: AnyParse, + new_snippets: Vec, +) -> Result { + let tree: AnyJsRoot = root.tree(); + let mut mutation = BatchMutation::new(tree.syntax().clone()); + let iterator = tree + .syntax() + .descendants() + .filter_map(JsTemplateChunkElement::cast); + + for element in iterator { + let Some(snippet) = new_snippets + .iter() + .find(|snippet| snippet.range == element.range()) + else { + continue; + }; + + if let Ok(value_token) = element.template_chunk_token() { + let new_token = ident(snippet.new_code.as_str()); + mutation.replace_token(value_token, new_token); + } + } + + let root = mutation.commit(); + + Ok(root.as_send().unwrap()) +} diff --git a/crates/biome_service/src/file_handlers/json.rs b/crates/biome_service/src/file_handlers/json.rs index ac4e8aa56743..1846c20263ed 100644 --- a/crates/biome_service/src/file_handlers/json.rs +++ b/crates/biome_service/src/file_handlers/json.rs @@ -1,6 +1,6 @@ use super::{ AnalyzerVisitorBuilder, CodeActionsParams, DocumentFileSource, EnabledForPath, - ExtensionHandler, ParseResult, ProcessFixAll, ProcessLint, SearchCapabilities, + ExtensionHandler, ParseResult, ProcessFixAll, ProcessLint, SearchCapabilities, search, }; use crate::configuration::to_analyzer_rules; use crate::file_handlers::DebugCapabilities; @@ -10,7 +10,7 @@ use crate::file_handlers::{ }; use crate::settings::{ FormatSettings, LanguageListSettings, LanguageSettings, OverrideSettings, ServiceLanguage, - Settings, check_feature_activity, check_override_feature_activity, + Settings, SettingsWithEditor, check_feature_activity, check_override_feature_activity, }; use crate::workspace::{CodeAction, FixFileResult, GetSyntaxTreeResult, PullActionsResult}; use crate::{WorkspaceError, extension_error}; @@ -25,6 +25,7 @@ use biome_configuration::json::{ use biome_deserialize::json::deserialize_from_json_ast; use biome_formatter::{ BracketSpacing, Expand, FormatError, IndentStyle, IndentWidth, LineEnding, LineWidth, Printed, + TrailingNewline, }; use biome_fs::{BiomePath, ConfigName}; use biome_json_analyze::{ExtendedConfigurationProvider, JsonAnalyzeServices, analyze}; @@ -51,6 +52,7 @@ pub struct JsonFormatterSettings { pub expand: Option, pub bracket_spacing: Option, pub enabled: Option, + pub trailing_newline: Option, } impl From for JsonFormatterSettings { @@ -64,6 +66,7 @@ impl From for JsonFormatterSettings { expand: configuration.expand, bracket_spacing: configuration.bracket_spacing, enabled: configuration.enabled, + trailing_newline: configuration.trailing_newline, } } } @@ -150,6 +153,7 @@ impl ServiceLanguage for JsonLanguage { || optional_json_file_source.is_some_and(|x| x.allow_trailing_commas()), |value| value.value(), ), + allow_metavariables: false, }; overrides.apply_override_json_parser_options(path, &mut options); @@ -178,6 +182,11 @@ impl ServiceLanguage for JsonLanguage { .or(global.indent_width) .unwrap_or_default(); + let trailing_newline = language + .trailing_newline + .or(global.trailing_newline) + .unwrap_or_default(); + let line_ending = language .line_ending .or(global.line_ending) @@ -214,7 +223,8 @@ impl ServiceLanguage for JsonLanguage { .with_line_width(line_width) .with_trailing_commas(trailing_commas) .with_expand(expand_lists) - .with_bracket_spacing(bracket_spacing); + .with_bracket_spacing(bracket_spacing) + .with_trailing_newline(trailing_newline); overrides.apply_override_json_format_options(path, &mut options); @@ -358,24 +368,26 @@ impl ExtensionHandler for JsonFileHandler { format_on_type: Some(format_on_type), format_embedded: None, }, - search: SearchCapabilities { search: None }, + search: SearchCapabilities { + search: Some(search), + }, } } } -fn formatter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn formatter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.formatter_enabled_for_file_path::(path) } -fn linter_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn linter_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.linter_enabled_for_file_path::(path) } -fn assist_enabled(path: &Utf8Path, settings: &Settings) -> bool { +fn assist_enabled(path: &Utf8Path, settings: &SettingsWithEditor) -> bool { settings.assist_enabled_for_file_path::(path) } -fn search_enabled(_path: &Utf8Path, _settings: &Settings) -> bool { +fn search_enabled(_path: &Utf8Path, _settings: &SettingsWithEditor) -> bool { true } @@ -383,7 +395,7 @@ fn parse( biome_path: &BiomePath, file_source: DocumentFileSource, text: &str, - settings: &Settings, + settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let options = settings.parse_options::(biome_path, &file_source); @@ -409,7 +421,7 @@ fn debug_formatter_ir( path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(path, document_file_source); @@ -425,7 +437,7 @@ fn format( path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(path, document_file_source); @@ -442,7 +454,7 @@ fn format_range( path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { let options = settings.format_options::(path, document_file_source); @@ -456,7 +468,7 @@ fn format_on_type( path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { let options = settings.format_options::(path, document_file_source); @@ -512,7 +524,7 @@ fn lint(params: LintParams) -> LintResults { ); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.path.as_path()) @@ -535,9 +547,14 @@ fn lint(params: LintParams) -> LintResults { .full_source() .map(|s| s as std::sync::Arc), }; - let (_, analyze_diagnostics) = analyze(&root, filter, &analyzer_options, services, |signal| { - process_lint.process_signal(signal) - }); + let (_, analyze_diagnostics) = analyze( + &root, + filter, + &analyzer_options, + services, + ¶ms.plugins, + |signal| process_lint.process_signal(signal), + ); let mut diagnostics = params .parse @@ -573,7 +590,7 @@ fn code_actions(params: CodeActionsParams) -> PullActionsResult { only, enabled_rules: rules, suppression_reason, - plugins: _, + plugins, categories, action_offset, document_services: _, @@ -588,7 +605,7 @@ fn code_actions(params: CodeActionsParams) -> PullActionsResult { ); let mut actions = Vec::new(); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(only) .with_skip(skip) .with_path(path.as_path()) @@ -613,20 +630,27 @@ fn code_actions(params: CodeActionsParams) -> PullActionsResult { .full_source() .map(|s| s as std::sync::Arc), }; - analyze(&tree, filter, &analyzer_options, services, |signal| { - actions.extend(signal.actions().into_code_action_iter().map(|item| { - CodeAction { - category: item.category.clone(), - rule_name: item - .rule_name - .map(|(group, name)| (Cow::Borrowed(group), Cow::Borrowed(name))), - suggestion: item.suggestion, - offset: action_offset, - } - })); - - ControlFlow::::Continue(()) - }); + analyze( + &tree, + filter, + &analyzer_options, + services, + &plugins, + |signal| { + actions.extend(signal.actions().into_code_action_iter().map(|item| { + CodeAction { + category: item.category.clone(), + rule_name: item + .rule_name + .map(|(group, name)| (Cow::Borrowed(group), Cow::Borrowed(name))), + suggestion: item.suggestion, + offset: action_offset, + } + })); + + ControlFlow::::Continue(()) + }, + ); PullActionsResult { actions } } @@ -636,14 +660,17 @@ fn fix_all(params: FixAllParams) -> Result { let mut tree: JsonRoot = params.parse.tree(); // Compute final rules (taking `overrides` into account) - let rules = params.settings.as_linter_rules(params.biome_path.as_path()); + let rules = params + .settings + .as_ref() + .as_linter_rules(params.biome_path.as_path()); let analyzer_options = params.settings.analyzer_options::( params.biome_path, ¶ms.document_file_source, params.suppression_reason.as_deref(), ); let (enabled_rules, disabled_rules, analyzer_options) = - AnalyzerVisitorBuilder::new(params.settings, analyzer_options) + AnalyzerVisitorBuilder::new(params.settings.as_ref(), analyzer_options) .with_only(params.only) .with_skip(params.skip) .with_path(params.biome_path.as_path()) @@ -679,9 +706,14 @@ fn fix_all(params: FixAllParams) -> Result { .full_source() .map(|s| s as std::sync::Arc), }; - let (action, _) = analyze(&tree, filter, &analyzer_options, services, |signal| { - process_fix_all.process_signal(signal) - }); + let (action, _) = analyze( + &tree, + filter, + &analyzer_options, + services, + ¶ms.plugins, + |signal| process_fix_all.process_signal(signal), + ); let result = process_fix_all.process_action(action, |root| { tree = match JsonRoot::cast(root) { diff --git a/crates/biome_service/src/file_handlers/mod.rs b/crates/biome_service/src/file_handlers/mod.rs index e52362672211..a6e0048f810b 100644 --- a/crates/biome_service/src/file_handlers/mod.rs +++ b/crates/biome_service/src/file_handlers/mod.rs @@ -9,8 +9,9 @@ use crate::file_handlers::graphql::GraphqlFileHandler; use crate::file_handlers::ignore::IgnoreFileHandler; pub use crate::file_handlers::svelte::SvelteFileHandler; pub use crate::file_handlers::vue::VueFileHandler; -use crate::settings::Settings; +use crate::settings::{Settings, SettingsWithEditor}; use crate::utils::growth_guard::GrowthGuard; +use crate::workspace::document::services::embedded_bindings::EmbeddedBuilder; use crate::workspace::{ AnyEmbeddedSnippet, CodeAction, DocumentServices, FixAction, FixFileMode, FixFileResult, GetSyntaxTreeResult, PullActionsResult, PullDiagnosticsAndActionsResult, RenameResult, @@ -404,9 +405,8 @@ impl DocumentFileSource { pub fn can_contain_embeds(path: &Utf8Path, experimental_full_html_support: bool) -> bool { let file_source = Self::from_path(path, experimental_full_html_support); match file_source { - Self::Html(_) => true, - Self::Js(_) - | Self::Css(_) + Self::Html(_) | Self::Js(_) => true, + Self::Css(_) | Self::Graphql(_) | Self::Json(_) | Self::Grit(_) @@ -459,7 +459,7 @@ impl biome_console::fmt::Display for DocumentFileSource { pub struct FixAllParams<'a> { pub(crate) parse: AnyParse, pub(crate) fix_file_mode: FixFileMode, - pub(crate) settings: &'a Settings, + pub(crate) settings: &'a SettingsWithEditor<'a>, /// Whether it should format the code action pub(crate) should_format: bool, pub(crate) biome_path: &'a BiomePath, @@ -492,13 +492,21 @@ pub struct ParseResult { pub(crate) language: Option, } +#[derive(Default)] pub struct ParseEmbedResult { pub(crate) nodes: Vec<(AnyEmbeddedSnippet, DocumentFileSource)>, } -type Parse = fn(&BiomePath, DocumentFileSource, &str, &Settings, &mut NodeCache) -> ParseResult; -type ParseEmbeddedNodes = - fn(&AnyParse, &BiomePath, &DocumentFileSource, &Settings, &mut NodeCache) -> ParseEmbedResult; +type Parse = + fn(&BiomePath, DocumentFileSource, &str, &SettingsWithEditor, &mut NodeCache) -> ParseResult; +type ParseEmbeddedNodes = fn( + &AnyParse, + &BiomePath, + &DocumentFileSource, + &SettingsWithEditor, + &mut NodeCache, + &mut EmbeddedBuilder, +) -> ParseEmbedResult; #[derive(Default)] pub struct ParserCapabilities { /// Parse a file @@ -509,8 +517,12 @@ pub struct ParserCapabilities { type DebugSyntaxTree = fn(&BiomePath, AnyParse) -> GetSyntaxTreeResult; type DebugControlFlow = fn(AnyParse, TextSize) -> String; -type DebugFormatterIR = - fn(&BiomePath, &DocumentFileSource, AnyParse, &Settings) -> Result; +type DebugFormatterIR = fn( + &BiomePath, + &DocumentFileSource, + AnyParse, + &SettingsWithEditor, +) -> Result; type DebugTypeInfo = fn(&BiomePath, Option, Arc) -> Result; type DebugRegisteredTypes = fn(&BiomePath, AnyParse) -> Result; @@ -535,7 +547,7 @@ pub struct DebugCapabilities { #[derive(Debug)] pub(crate) struct LintParams<'a> { pub(crate) parse: AnyParse, - pub(crate) settings: &'a Settings, + pub(crate) settings: &'a SettingsWithEditor<'a>, pub(crate) language: DocumentFileSource, pub(crate) path: &'a BiomePath, pub(crate) only: &'a [AnalyzerSelector], @@ -549,11 +561,12 @@ pub(crate) struct LintParams<'a> { pub(crate) pull_code_actions: bool, pub(crate) diagnostic_offset: Option, pub(crate) document_services: &'a DocumentServices, + pub(crate) snippet_services: Option<&'a DocumentServices>, } pub(crate) struct DiagnosticsAndActionsParams<'a> { pub(crate) parse: AnyParse, - pub(crate) settings: &'a Settings, + pub(crate) settings: &'a SettingsWithEditor<'a>, pub(crate) language: DocumentFileSource, pub(crate) path: &'a BiomePath, pub(crate) only: &'a [AnalyzerSelector], @@ -596,7 +609,10 @@ impl<'a> ProcessLint<'a> { // - if a single rule is run. ignores_suppression_comment: !params.categories.contains(RuleCategory::Lint) || !params.only.is_empty(), - rules: params.settings.as_linter_rules(params.path.as_path()), + rules: params + .settings + .as_ref() + .as_linter_rules(params.path.as_path()), pull_code_actions: params.pull_code_actions, diagnostic_offset: params.diagnostic_offset, } @@ -899,7 +915,7 @@ impl ProcessDiagnosticsAndActions { pub(crate) struct CodeActionsParams<'a> { pub(crate) parse: AnyParse, pub(crate) range: Option, - pub(crate) settings: &'a Settings, + pub(crate) settings: &'a SettingsWithEditor<'a>, pub(crate) path: &'a BiomePath, pub(crate) module_graph: Arc, pub(crate) project_layout: Arc, @@ -942,20 +958,24 @@ pub struct AnalyzerCapabilities { pub(crate) pull_diagnostics_and_actions: Option, } -type Format = - fn(&BiomePath, &DocumentFileSource, AnyParse, &Settings) -> Result; +type Format = fn( + &BiomePath, + &DocumentFileSource, + AnyParse, + &SettingsWithEditor, +) -> Result; type FormatRange = fn( &BiomePath, &DocumentFileSource, AnyParse, - &Settings, + &SettingsWithEditor, TextRange, ) -> Result; type FormatOnType = fn( &BiomePath, &DocumentFileSource, AnyParse, - &Settings, + &SettingsWithEditor, TextSize, ) -> Result; @@ -963,7 +983,7 @@ type FormatEmbedded = fn( &BiomePath, &DocumentFileSource, AnyParse, - &Settings, + &SettingsWithEditor, Vec, ) -> Result; @@ -986,14 +1006,14 @@ pub(crate) struct FormatterCapabilities { pub(crate) format_embedded: Option, } -type Enabled = fn(&Utf8Path, &Settings) -> bool; +type Enabled = fn(&Utf8Path, &SettingsWithEditor) -> bool; type Search = fn( &BiomePath, &DocumentFileSource, AnyParse, &GritQuery, - &Settings, + &SettingsWithEditor, ) -> Result, WorkspaceError>; #[derive(Default)] @@ -1057,7 +1077,7 @@ impl Features { DocumentFileSource::Js(source) => match source.as_embedding_kind() { EmbeddingKind::Astro { .. } => self.astro.capabilities(), EmbeddingKind::Vue { .. } => self.vue.capabilities(), - EmbeddingKind::Svelte => self.svelte.capabilities(), + EmbeddingKind::Svelte { .. } => self.svelte.capabilities(), EmbeddingKind::None => self.js.capabilities(), }, DocumentFileSource::Json(_) => self.json.capabilities(), @@ -1186,7 +1206,7 @@ pub(crate) fn search( _file_source: &DocumentFileSource, parse: AnyParse, query: &GritQuery, - _settings: &Settings, + _settings: &SettingsWithEditor, ) -> Result, WorkspaceError> { let result = query .execute(GritTargetFile::new(path.as_path(), parse)) @@ -1901,6 +1921,34 @@ impl<'b> AnalyzerVisitorBuilder<'b> { .and_then(|path| self.project_layout.find_node_manifest_for_path(path)) .map(|(_, manifest)| manifest); + // Query tsconfig.json for JSX factory settings if jsx_runtime is ReactClassic + // and the factory settings are not already set + if let Some(path) = self.path + && analyzer_options.jsx_runtime() + == Some(biome_analyze::options::JsxRuntime::ReactClassic) + { + if analyzer_options.jsx_factory().is_none() { + let factory = self + .project_layout + .query_tsconfig_for_path(path, |tsconfig| { + tsconfig.jsx_factory_identifier().map(|s| s.to_string()) + }) + .flatten(); + analyzer_options.set_jsx_factory(factory.map(|s| s.into())); + } + if analyzer_options.jsx_fragment_factory().is_none() { + let fragment_factory = self + .project_layout + .query_tsconfig_for_path(path, |tsconfig| { + tsconfig + .jsx_fragment_factory_identifier() + .map(|s| s.to_string()) + }) + .flatten(); + analyzer_options.set_jsx_fragment_factory(fragment_factory.map(|s| s.into())); + } + } + let mut lint = LintVisitor::new( self.only, self.skip, diff --git a/crates/biome_service/src/file_handlers/svelte.rs b/crates/biome_service/src/file_handlers/svelte.rs index 2152c5d4e2e7..fcffd31765d6 100644 --- a/crates/biome_service/src/file_handlers/svelte.rs +++ b/crates/biome_service/src/file_handlers/svelte.rs @@ -5,7 +5,7 @@ use crate::file_handlers::{ ExtensionHandler, FixAllParams, FormatterCapabilities, LintParams, LintResults, ParseResult, ParserCapabilities, javascript, }; -use crate::settings::Settings; +use crate::settings::SettingsWithEditor; use crate::workspace::{DocumentFileSource, FixFileResult, PullActionsResult}; use biome_formatter::{Printed, SourceMapGeneration}; use biome_fs::BiomePath; @@ -76,7 +76,7 @@ impl SvelteFileHandler { Some( JsFileSource::from(language) .with_variant(variant) - .with_embedding_kind(EmbeddingKind::Svelte), + .with_embedding_kind(EmbeddingKind::Svelte { is_source: true }), ) }) .map_or(JsFileSource::js_module(), |fs| fs) @@ -128,7 +128,7 @@ fn parse( _rome_path: &BiomePath, _file_source: DocumentFileSource, text: &str, - _settings: &Settings, + _settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let script = SvelteFileHandler::input(text); @@ -149,7 +149,7 @@ fn format( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); let html_options = settings.format_options::(biome_path, document_file_source); @@ -159,7 +159,7 @@ fn format( 0 }; let tree = parse.syntax(); - let formatted = format_node(options, &tree)?; + let formatted = format_node(options, &tree, false)?; match formatted.print_with_indent(indent_amount, SourceMapGeneration::Disabled) { Ok(printed) => Ok(printed), Err(error) => { @@ -172,7 +172,7 @@ pub(crate) fn format_range( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { javascript::format_range(biome_path, document_file_source, parse, settings, range) @@ -182,7 +182,7 @@ pub(crate) fn format_on_type( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { javascript::format_on_type(biome_path, document_file_source, parse, settings, offset) diff --git a/crates/biome_service/src/file_handlers/vue.rs b/crates/biome_service/src/file_handlers/vue.rs index ba367a31d043..dfed45bdf803 100644 --- a/crates/biome_service/src/file_handlers/vue.rs +++ b/crates/biome_service/src/file_handlers/vue.rs @@ -5,7 +5,7 @@ use crate::file_handlers::{ ExtensionHandler, FixAllParams, FormatterCapabilities, LintParams, LintResults, ParseResult, ParserCapabilities, javascript, }; -use crate::settings::Settings; +use crate::settings::SettingsWithEditor; use crate::workspace::{DocumentFileSource, FixFileResult, PullActionsResult}; use biome_formatter::{Printed, SourceMapGeneration}; use biome_fs::BiomePath; @@ -78,7 +78,10 @@ impl VueFileHandler { Some( JsFileSource::from(language) .with_variant(variant) - .with_embedding_kind(EmbeddingKind::Vue { setup }), + .with_embedding_kind(EmbeddingKind::Vue { + setup, + is_source: true, + }), ) }) .map_or(JsFileSource::js_module(), |fs| fs) @@ -130,7 +133,7 @@ fn parse( _rome_path: &BiomePath, _file_source: DocumentFileSource, text: &str, - _settings: &Settings, + _settings: &SettingsWithEditor, cache: &mut NodeCache, ) -> ParseResult { let script = VueFileHandler::input(text); @@ -151,7 +154,7 @@ fn format( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, ) -> Result { let options = settings.format_options::(biome_path, document_file_source); let html_options = settings.format_options::(biome_path, document_file_source); @@ -161,7 +164,7 @@ fn format( 0 }; let tree = parse.syntax(); - let formatted = format_node(options, &tree)?; + let formatted = format_node(options, &tree, false)?; match formatted.print_with_indent(indent_amount, SourceMapGeneration::Disabled) { Ok(printed) => Ok(printed), Err(error) => { @@ -175,7 +178,7 @@ pub(crate) fn format_range( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, range: TextRange, ) -> Result { javascript::format_range(biome_path, document_file_source, parse, settings, range) @@ -185,7 +188,7 @@ pub(crate) fn format_on_type( biome_path: &BiomePath, document_file_source: &DocumentFileSource, parse: AnyParse, - settings: &Settings, + settings: &SettingsWithEditor, offset: TextSize, ) -> Result { javascript::format_on_type(biome_path, document_file_source, parse, settings, offset) diff --git a/crates/biome_service/src/lib.rs b/crates/biome_service/src/lib.rs index f076a62a8a60..3d5c166cd940 100644 --- a/crates/biome_service/src/lib.rs +++ b/crates/biome_service/src/lib.rs @@ -27,7 +27,7 @@ use biome_resolver::FsWithResolverProxy; pub use diagnostics::{TransportError, WorkspaceError, extension_error}; pub use file_handlers::JsFormatterSettings; -pub use scanner::{Watcher, WatcherInstruction}; +pub use scanner::{Watcher, WatcherInstruction, WatcherKind, WatcherOptions, watcher_options}; pub use workspace::{Workspace, WorkspaceServer}; /// This is the main entrypoint of the application. diff --git a/crates/biome_service/src/projects.rs b/crates/biome_service/src/projects.rs index a7df2d66d6d9..3edcd17dd4da 100644 --- a/crates/biome_service/src/projects.rs +++ b/crates/biome_service/src/projects.rs @@ -1,6 +1,6 @@ use crate::WorkspaceError; use crate::file_handlers::Capabilities; -use crate::settings::Settings; +use crate::settings::{Settings, SettingsWithEditor}; use crate::workspace::{ DocumentFileSource, FeatureName, FeaturesSupported, FileFeaturesResult, IgnoreKind, }; @@ -19,10 +19,12 @@ pub struct GetFileFeaturesParams<'a> { pub fs: &'a dyn FileSystem, pub project_key: ProjectKey, pub path: &'a Utf8Path, - pub features: FeatureName, + pub requested_features: FeatureName, pub language: DocumentFileSource, pub capabilities: &'a Capabilities, + pub handle: &'a SettingsWithEditor<'a>, pub skip_ignore_check: bool, + pub not_requested_features: FeatureName, } /// The information tracked for each project. @@ -215,26 +217,24 @@ impl Projects { fs, project_key, path, - features, + requested_features, language, capabilities, + handle, skip_ignore_check, + not_requested_features: denied_features, }: GetFileFeaturesParams<'_>, ) -> Result { let data = self.0.pin(); let project_data = data .get(&project_key) .ok_or_else(WorkspaceError::no_project)?; + let settings = handle.as_ref(); + let mut file_features = FeaturesSupported::default() + .with_capabilities(capabilities) + .with_not_requested_features(denied_features) + .with_settings_and_language(handle, path, capabilities); - let settings = project_data - .nested_settings - .iter() - .find(|(project_path, _)| path.starts_with(project_path)) - .map_or(&project_data.root_settings, |(_, settings)| settings); - - let mut file_features = FeaturesSupported::default(); - file_features = file_features.with_capabilities(capabilities); - file_features = file_features.with_settings_and_language(settings, path, capabilities); if settings.ignore_unknown_enabled() && language == DocumentFileSource::Unknown { file_features.ignore_not_supported(); } else if path.file_name().is_some_and(|file_name| { @@ -251,8 +251,8 @@ impl Projects { // If there are specific features enabled, but all of them ignore the // path, then we treat the path as ignored too. - let is_ignored_by_features = !features.is_empty() - && features.iter().all(|feature| { + let is_ignored_by_features = !requested_features.is_empty() + && requested_features.iter().all(|feature| { project_data .root_settings .is_path_ignored_for_feature(path, feature) @@ -264,7 +264,7 @@ impl Projects { if is_ignored { file_features.set_ignored_for_all_features(); } else { - for feature in features.iter() { + for feature in requested_features.iter() { if project_data .root_settings .is_path_ignored_for_feature(path, feature) diff --git a/crates/biome_service/src/scanner.rs b/crates/biome_service/src/scanner.rs index 7c933a74e6f1..5742841ff668 100644 --- a/crates/biome_service/src/scanner.rs +++ b/crates/biome_service/src/scanner.rs @@ -10,6 +10,9 @@ mod workspace_bridges; #[cfg(test)] mod test_utils; +use crate::diagnostics::Panic; +use crate::projects::ProjectKey; +use crate::workspace::{ScanProjectResult, ServiceNotification, WorkspaceError}; use biome_diagnostics::serde::Diagnostic; use biome_diagnostics::{Diagnostic as _, DiagnosticExt, Error, Severity}; use biome_fs::{BiomePath, PathInterner, PathKind, TraversalContext, TraversalScope}; @@ -26,11 +29,7 @@ use std::time::Duration; use std::{mem, thread}; use tracing::instrument; -use crate::diagnostics::Panic; -use crate::projects::ProjectKey; -use crate::workspace::{ScanProjectResult, ServiceNotification, WorkspaceError}; - -pub use watcher::{Watcher, WatcherInstruction}; +pub use watcher::{Watcher, WatcherInstruction, WatcherKind, WatcherOptions, watcher_options}; pub(crate) use workspace_bridges::{ ScannerWatcherBridge, WorkspaceScannerBridge, WorkspaceWatcherBridge, }; @@ -711,6 +710,9 @@ pub enum ScanKind { }, /// Scans the entire repository, indexing all files to enable project rules. Project, + + /// Scans the entire repository, indexing all files to enable type inference rules + TypeAware, } impl ScanKind { @@ -724,6 +726,7 @@ impl ScanKind { Self::KnownFiles => Self::KnownFiles, Self::Project => Self::Project, Self::TargetedKnownFiles { .. } => Self::KnownFiles, + Self::TypeAware => Self::TypeAware, } } @@ -731,6 +734,10 @@ impl ScanKind { matches!(self, Self::Project) } + pub const fn is_type_aware(&self) -> bool { + matches!(self, Self::TypeAware) + } + pub const fn is_known_files(&self) -> bool { matches!(self, Self::KnownFiles) } diff --git a/crates/biome_service/src/scanner.tests.rs b/crates/biome_service/src/scanner.tests.rs index 9f11bb53d5bd..febe77445581 100644 --- a/crates/biome_service/src/scanner.tests.rs +++ b/crates/biome_service/src/scanner.tests.rs @@ -6,6 +6,7 @@ use camino::Utf8PathBuf; use crate::Workspace; use crate::scanner::WorkspaceScannerBridge; +use crate::settings::ModuleGraphResolutionKind; use crate::test_utils::setup_workspace_and_open_project; use crate::workspace::{GetFileContentParams, ScanKind, ScanProjectParams, UpdateSettingsParams}; @@ -63,6 +64,7 @@ fn scanner_only_loads_used_type_definitions_from_node_modules() { configuration: Default::default(), workspace_directory: Some(fixtures_path.clone().into()), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .unwrap(); @@ -143,6 +145,7 @@ fn scanner_ignored_files_are_not_loaded() { configuration, workspace_directory: Some(fixtures_path.clone().into()), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .unwrap(); @@ -196,6 +199,7 @@ fn scanner_required_files_are_only_ignored_in_ignored_directories() { configuration, workspace_directory: Some(fixtures_path.clone().into()), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .unwrap(); diff --git a/crates/biome_service/src/scanner/test_utils.rs b/crates/biome_service/src/scanner/test_utils.rs index 7ed0db4c7887..063aa9645d93 100644 --- a/crates/biome_service/src/scanner/test_utils.rs +++ b/crates/biome_service/src/scanner/test_utils.rs @@ -128,14 +128,22 @@ impl WorkspaceWatcherBridge for MockWorkspaceWatcherBridge<'_> { self.tx.send(()).expect("can send notification"); } - fn unload_file(&self, path: &Utf8Path) -> Result, WorkspaceError> { + fn unload_file( + &self, + path: &Utf8Path, + _project_key: ProjectKey, + ) -> Result, WorkspaceError> { self.indexed_files.pin().remove(path); self.tx.send(()).expect("can send notification"); Ok(vec![]) } - fn unload_path(&self, path: &Utf8Path) -> Result, WorkspaceError> { + fn unload_path( + &self, + path: &Utf8Path, + _project_key: ProjectKey, + ) -> Result, WorkspaceError> { self.indexed_files.pin().remove(path); self.indexed_folders.pin().remove(path); self.tx.send(()).expect("can send notification"); diff --git a/crates/biome_service/src/scanner/watcher.rs b/crates/biome_service/src/scanner/watcher.rs index 9fc12c2a83b0..c46af54a135f 100644 --- a/crates/biome_service/src/scanner/watcher.rs +++ b/crates/biome_service/src/scanner/watcher.rs @@ -1,20 +1,90 @@ -use std::path::PathBuf; - use super::WorkspaceWatcherBridge; use crate::WorkspaceError; use crate::diagnostics::WatchError; use biome_diagnostics::PrintDescription; use biome_diagnostics::serde::Diagnostic; +use bpaf::Bpaf; use camino::{Utf8Path, Utf8PathBuf}; use crossbeam::channel::{Receiver, Sender, bounded, unbounded}; use notify::event::{CreateKind, ModifyKind, RemoveKind, RenameMode}; use notify::recommended_watcher; use notify::{ - Error as NotifyError, Event as NotifyEvent, EventKind, RecursiveMode, Result as NotifyResult, - Watcher as NotifyWatcher, + Config, Error as NotifyError, Event as NotifyEvent, EventKind, PollWatcher, RecursiveMode, + Result as NotifyResult, Watcher as NotifyWatcher, }; use rustc_hash::FxHashSet; -use tracing::{debug, error, warn}; +use std::fmt::{Display, Formatter}; +use std::path::PathBuf; +use std::str::FromStr; +use std::time::Duration; +use tracing::{debug, error, info, warn}; + +/// Controls various aspects of the Biome Daemon. +#[derive(Clone, Debug, Eq, PartialEq, Bpaf)] +pub struct WatcherOptions { + /// Controls how the Biome file watcher should behave. + #[bpaf( + env("BIOME_WATCHER_KIND"), + long("watcher-kind"), + argument("polling|recommended|none"), + fallback(WatcherKind::Recommended), + display_fallback + )] + pub watcher_kind: WatcherKind, + + /// The polling interval in milliseconds. This is only applicable when using the polling watcher. + #[bpaf( + env("BIOME_WATCHER_POLLING_INTERVAL"), + long("watcher-polling-interval"), + argument("NUMBER"), + fallback(2000), + display_fallback + )] + pub polling_interval: u32, +} + +impl Default for WatcherOptions { + fn default() -> Self { + Self { + watcher_kind: WatcherKind::Recommended, + polling_interval: 2000, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Bpaf, Default)] +pub enum WatcherKind { + /// It uses the polling strategy. It's slower than the recommended, by it avoids locking folders in Windows systems. + Polling, + /// It uses the recommended strategy of the current OS. It's faster than polling in some cases, but it might behaves differently based on the OS. + #[default] + Recommended, + /// The file watcher is disabled, which means that changes aren't tracked, causing linting not working properly inside editors or + /// when using the Biome daemon. It's useful for testing purposes or when the file watcher isn't needed. + None, +} + +impl Display for WatcherKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Polling => write!(f, "polling"), + Self::Recommended => write!(f, "recommended"), + Self::None => write!(f, "none"), + } + } +} + +impl FromStr for WatcherKind { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "polling" => Ok(Self::Polling), + "recommended" => Ok(Self::Recommended), + "none" => Ok(Self::None), + _ => Err("Invalid watcherKind. Valid values are: polling, recommended, none"), + } + } +} /// Instructions to let the watcher either watch or unwatch a given folder. #[derive(Debug, Eq, PartialEq)] @@ -63,7 +133,7 @@ pub struct Watcher { /// Internal [`notify::Watcher`] instance. // Note: The `Watcher` trait doesn't require its implementations to be // `Send`, but it appears all platform implementations are. - watcher: Box, + watcher: Option>, /// Channel receiver for the events from our /// [internal watcher](Self::watcher). @@ -78,7 +148,9 @@ impl Watcher { /// /// Returns the watcher as well as a channel for sending instructions to the /// watcher. - pub fn new() -> Result<(Self, WatcherInstructionChannel), WorkspaceError> { + pub fn new( + watcher_configuration: WatcherOptions, + ) -> Result<(Self, WatcherInstructionChannel), WorkspaceError> { // We use a bounded channel, because watchers are // [intrinsically unreliable](https://docs.rs/notify/latest/notify/#watching-large-directories). // If we block the sender, some events may get dropped, but that was @@ -89,14 +161,35 @@ impl Watcher { // tweak if we find a need for it. let (tx, rx) = bounded::>(128); - let watcher = recommended_watcher(tx)?; + info!( + "Starting file watcher with kind \"{}\" with interval \"{}\"", + watcher_configuration.watcher_kind, watcher_configuration.polling_interval + ); + + let watcher: Option> = + match &watcher_configuration.watcher_kind { + WatcherKind::Polling => { + let watcher = PollWatcher::new( + tx.clone(), + Config::default().with_poll_interval(Duration::from_millis( + watcher_configuration.polling_interval as u64, + )), + )?; + Some(Box::new(watcher)) + } + WatcherKind::Recommended => { + let watcher = recommended_watcher(tx)?; + Some(Box::new(watcher)) + } + WatcherKind::None => None, + }; let (instruction_tx, instruction_rx) = unbounded(); let instruction_channel = WatcherInstructionChannel { sender: instruction_tx, }; let watcher = Self { - watcher: Box::new(watcher), + watcher, notify_rx: rx, instruction_rx, }; @@ -113,6 +206,9 @@ impl Watcher { /// terminates. #[tracing::instrument(level = "debug", skip(self, workspace))] pub fn run(&mut self, workspace: &impl WorkspaceWatcherBridge) { + if self.watcher.is_none() { + return; + } loop { crossbeam::channel::select! { recv(self.notify_rx) -> event => match event { @@ -294,7 +390,10 @@ impl Watcher { ) -> Result, WorkspaceError> { let mut diagnostics = vec![]; for path in paths { - diagnostics.extend(workspace.unload_file(&path)?); + let Some(project_key) = workspace.find_project_for_path(&path) else { + return Ok(vec![]); + }; + diagnostics.extend(workspace.unload_file(&path, project_key)?); } Ok(diagnostics) @@ -310,7 +409,10 @@ impl Watcher { ) -> Result, WorkspaceError> { let mut diagnostics = vec![]; for path in &paths { - let result = workspace.unload_path(path)?; + let Some(project_key) = workspace.find_project_for_path(path) else { + return Ok(vec![]); + }; + let result = workspace.unload_path(path, project_key)?; diagnostics.extend(result); } @@ -323,10 +425,13 @@ impl Watcher { to: &Utf8Path, ) -> Result, WorkspaceError> { let mut diagnostics = vec![]; + let Some(project_key) = workspace.find_project_for_path(from) else { + return Ok(vec![]); + }; if workspace.fs().path_is_file(from) { - diagnostics.extend(workspace.unload_file(from)?); + diagnostics.extend(workspace.unload_file(from, project_key)?); } else { - diagnostics.extend(workspace.unload_path(from)?); + diagnostics.extend(workspace.unload_path(from, project_key)?); } diagnostics.extend(Self::index_path(workspace, to)?); Ok(diagnostics) @@ -351,45 +456,45 @@ impl Watcher { workspace: &impl WorkspaceWatcherBridge, paths: FxHashSet, ) { - let mut watcher_paths = self.watcher.paths_mut(); + if let Some(mut watcher_paths) = self.watcher.as_mut().map(|w| w.paths_mut()) { + for path in paths { + let std_path = path.as_std_path(); + if !workspace.insert_watched_folder(path.clone()) { + continue; // Already watching. + } - for path in paths { - let std_path = path.as_std_path(); - if !workspace.insert_watched_folder(path.clone()) { - continue; // Already watching. + if let Err(error) = watcher_paths.add(std_path, RecursiveMode::NonRecursive) { + // TODO: Improve error propagation. + warn!("Error watching path {path}: {error}"); + } } - if let Err(error) = watcher_paths.add(std_path, RecursiveMode::NonRecursive) { + if let Err(error) = watcher_paths.commit() { // TODO: Improve error propagation. - warn!("Error watching path {path}: {error}"); + warn!("Error committing the watched paths: {error}"); } } - - if let Err(error) = watcher_paths.commit() { - // TODO: Improve error propagation. - warn!("Error committing the watched paths: {error}"); - } } #[tracing::instrument(level = "debug", skip(self, workspace))] fn unwatch_folder(&mut self, workspace: &impl WorkspaceWatcherBridge, path: Utf8PathBuf) { - let mut watcher_paths = self.watcher.paths_mut(); - - workspace.remove_watched_folders(|watched_path| { - if watched_path.starts_with(path.as_std_path()) { - if let Err(error) = watcher_paths.remove(watched_path.as_std_path()) { - // TODO: Improve error propagation. - warn!("Error unwatching path {}: {error}", watched_path); + if let Some(mut watcher_paths) = self.watcher.as_mut().map(|w| w.paths_mut()) { + workspace.remove_watched_folders(|watched_path| { + if watched_path.starts_with(path.as_std_path()) { + if let Err(error) = watcher_paths.remove(watched_path.as_std_path()) { + // TODO: Improve error propagation. + warn!("Error unwatching path {}: {error}", watched_path); + } + true + } else { + false } - true - } else { - false - } - }); + }); - if let Err(error) = watcher_paths.commit() { - // TODO: Improve error propagation. - warn!("Error committing the watched paths: {error}"); + if let Err(error) = watcher_paths.commit() { + // TODO: Improve error propagation. + warn!("Error committing the watched paths: {error}"); + } } } } diff --git a/crates/biome_service/src/scanner/watcher.tests.rs b/crates/biome_service/src/scanner/watcher.tests.rs index f5b89484ea82..801a98a06cc0 100644 --- a/crates/biome_service/src/scanner/watcher.tests.rs +++ b/crates/biome_service/src/scanner/watcher.tests.rs @@ -38,7 +38,8 @@ fn should_index_on_write_but_not_on_read() { let (mock_bridge, bridge_rx) = MockWorkspaceWatcherBridge::new(&os_fs, ProjectKey::new(), scan_kind.clone()); - let (mut watcher, instruction_channel) = Watcher::new().expect("can create watcher"); + let (mut watcher, instruction_channel) = + Watcher::new(WatcherOptions::default()).expect("can create watcher"); thread::scope(|s| { s.spawn(|| watcher.run(&mock_bridge)); @@ -108,7 +109,8 @@ fn should_index_on_create_and_unload_on_delete() { let (mock_bridge, bridge_rx) = MockWorkspaceWatcherBridge::new(&os_fs, ProjectKey::new(), scan_kind.clone()); - let (mut watcher, instruction_channel) = Watcher::new().expect("can create watcher"); + let (mut watcher, instruction_channel) = + Watcher::new(WatcherOptions::default()).expect("can create watcher"); thread::scope(|s| { s.spawn(|| watcher.run(&mock_bridge)); diff --git a/crates/biome_service/src/scanner/workspace_bridges.rs b/crates/biome_service/src/scanner/workspace_bridges.rs index e85cd175e463..884b26927dc0 100644 --- a/crates/biome_service/src/scanner/workspace_bridges.rs +++ b/crates/biome_service/src/scanner/workspace_bridges.rs @@ -86,7 +86,11 @@ pub(crate) trait WorkspaceScannerBridge: Send + Sync + RefUnwindSafe { /// Unloads the index of the file with the given `path` within the /// workspace. - fn unload_file(&self, path: &Utf8Path) -> Result, WorkspaceError>; + fn unload_file( + &self, + path: &Utf8Path, + project_key: ProjectKey, + ) -> Result, WorkspaceError>; /// Unloads the given `path` from the workspace index. /// @@ -97,7 +101,11 @@ pub(crate) trait WorkspaceScannerBridge: Send + Sync + RefUnwindSafe { /// /// If you already know the path is a file, you should use /// [`WorkspaceWatcherBridge::unload_file()`] directly instead. - fn unload_path(&self, path: &Utf8Path) -> Result, WorkspaceError>; + fn unload_path( + &self, + path: &Utf8Path, + project_key: ProjectKey, + ) -> Result, WorkspaceError>; } /// Trait used to give access to workspace functionality required by the @@ -157,7 +165,11 @@ pub trait WorkspaceWatcherBridge { /// Unloads the index of the file with the given `path` within the /// workspace. - fn unload_file(&self, path: &Utf8Path) -> Result, WorkspaceError>; + fn unload_file( + &self, + path: &Utf8Path, + project_key: ProjectKey, + ) -> Result, WorkspaceError>; /// Unloads the given `path` from the workspace index. /// @@ -168,7 +180,11 @@ pub trait WorkspaceWatcherBridge { /// /// If you already know the path is a file, you should use /// [`WorkspaceWatcherBridge::unload_file()`] directly instead. - fn unload_path(&self, path: &Utf8Path) -> Result, WorkspaceError>; + fn unload_path( + &self, + path: &Utf8Path, + project_key: ProjectKey, + ) -> Result, WorkspaceError>; /// Notifies service notification listeners that the watcher has stopped. fn notify_stopped(&self); @@ -270,13 +286,21 @@ where } #[inline] - fn unload_file(&self, path: &Utf8Path) -> Result, WorkspaceError> { - self.workspace.unload_file(path) + fn unload_file( + &self, + path: &Utf8Path, + project_key: ProjectKey, + ) -> Result, WorkspaceError> { + self.workspace.unload_file(path, project_key) } #[inline] - fn unload_path(&self, path: &Utf8Path) -> Result, WorkspaceError> { - self.workspace.unload_path(path) + fn unload_path( + &self, + path: &Utf8Path, + project_key: ProjectKey, + ) -> Result, WorkspaceError> { + self.workspace.unload_path(path, project_key) } #[inline] diff --git a/crates/biome_service/src/scanner/workspace_scanner_bridge.tests.rs b/crates/biome_service/src/scanner/workspace_scanner_bridge.tests.rs index 1277dfd2af83..86ee9eeca33b 100644 --- a/crates/biome_service/src/scanner/workspace_scanner_bridge.tests.rs +++ b/crates/biome_service/src/scanner/workspace_scanner_bridge.tests.rs @@ -15,7 +15,9 @@ use biome_fs::{BiomePath, MemoryFileSystem, TemporaryFs}; use biome_glob::NormalizedGlob; use camino::{Utf8Path, Utf8PathBuf}; +use super::{ScanKind, WorkspaceScannerBridge}; use crate::scanner::IndexTrigger; +use crate::settings::ModuleGraphResolutionKind; use crate::test_utils::setup_workspace_and_open_project; use crate::workspace::{ CloseFileParams, FileContent, GetFileContentParams, OpenFileParams, ScanProjectParams, @@ -23,8 +25,6 @@ use crate::workspace::{ }; use crate::{Workspace, WorkspaceError}; -use super::{ScanKind, WorkspaceScannerBridge}; - #[test] fn close_file_through_watcher_before_client() { const FILE_CONTENT: &str = "import 'foo';"; @@ -52,6 +52,7 @@ fn close_file_through_watcher_before_client() { }, document_file_source: None, persist_node_cache: true, + inline_config: None, }) .expect("can also open from client"); @@ -61,7 +62,7 @@ fn close_file_through_watcher_before_client() { ); workspace - .unload_file(&file_path) + .unload_file(&file_path, project_key) .expect("can unload indexed file"); assert!( @@ -116,6 +117,7 @@ fn close_file_from_client_before_watcher() { }, document_file_source: None, persist_node_cache: true, + inline_config: None, }) .expect("can open from client"); @@ -145,7 +147,7 @@ fn close_file_from_client_before_watcher() { ); workspace - .unload_file(&file_path) + .unload_file(&file_path, project_key) .expect("can unload file from index"); assert!( @@ -200,6 +202,7 @@ fn should_not_index_an_ignored_file_inside_vcs_ignore_file() { ..Default::default() }, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .expect("can update settings"); @@ -240,6 +243,7 @@ fn should_not_index_an_ignored_file_inside_file_includes() { ..Default::default() }, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .expect("can update settings"); @@ -281,6 +285,7 @@ fn should_index_an_ignored_file_if_it_is_a_dependency_of_a_non_ignored_file() { ..Default::default() }, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .expect("can update settings"); @@ -329,6 +334,7 @@ fn should_not_index_a_force_ignored_file_even_if_it_is_a_dependency() { ..Default::default() }, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .expect("can update settings"); @@ -372,6 +378,7 @@ fn should_not_index_dependency_with_scan_kind_known_files() { ..Default::default() }, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .expect("can update settings"); @@ -413,6 +420,7 @@ fn should_not_index_inside_an_ignored_folder_inside_file_includes() { ..Default::default() }, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .expect("can update settings"); @@ -455,6 +463,7 @@ fn should_not_index_inside_an_ignored_folder_inside_vcs_ignore_file() { ..Default::default() }, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::ModulesAndTypes, }) .expect("can update settings"); diff --git a/crates/biome_service/src/scanner/workspace_watcher_bridge.tests.rs b/crates/biome_service/src/scanner/workspace_watcher_bridge.tests.rs index 24851167f490..ff0a5d7c309d 100644 --- a/crates/biome_service/src/scanner/workspace_watcher_bridge.tests.rs +++ b/crates/biome_service/src/scanner/workspace_watcher_bridge.tests.rs @@ -43,6 +43,7 @@ fn close_modified_file_from_client_before_watcher() { }, document_file_source: None, persist_node_cache: true, + inline_config: None, }) .expect("can open from client"); @@ -56,6 +57,7 @@ fn close_modified_file_from_client_before_watcher() { path: BiomePath::new(&file_path), content: FILE_CONTENT_MODIFIED.to_string(), version: 2, + inline_config: None, }) .expect("can change file"); diff --git a/crates/biome_service/src/settings.rs b/crates/biome_service/src/settings.rs index 932930a8d7fc..547f216552aa 100644 --- a/crates/biome_service/src/settings.rs +++ b/crates/biome_service/src/settings.rs @@ -1,4 +1,4 @@ -use crate::workspace::{DocumentFileSource, FeatureKind}; +use crate::workspace::{DocumentFileSource, FeatureKind, ScanKind}; use crate::{WorkspaceError, is_dir}; use biome_analyze::{AnalyzerOptions, AnalyzerRules}; use biome_configuration::analyzer::assist::{Actions, AssistConfiguration, AssistEnabled}; @@ -7,7 +7,7 @@ use biome_configuration::bool::Bool; use biome_configuration::diagnostics::InvalidIgnorePattern; use biome_configuration::formatter::{FormatWithErrorsEnabled, FormatterEnabled}; use biome_configuration::html::{ExperimentalFullSupportEnabled, HtmlConfiguration}; -use biome_configuration::javascript::JsxRuntime; +use biome_configuration::javascript::{ExperimentalEmbeddedSnippetsEnabled, JsxRuntime}; use biome_configuration::max_size::MaxSize; use biome_configuration::vcs::{VcsClientKind, VcsConfiguration, VcsEnabled, VcsUseIgnoreFile}; use biome_configuration::{ @@ -19,12 +19,12 @@ use biome_configuration::{ push_to_analyzer_assist, push_to_analyzer_rules, }; use biome_css_formatter::context::CssFormatOptions; -use biome_css_parser::CssParserOptions; +use biome_css_parser::{CssModulesKind, CssParserOptions}; use biome_css_syntax::CssLanguage; use biome_deserialize::Merge; use biome_formatter::{ AttributePosition, BracketSameLine, BracketSpacing, Expand, IndentStyle, IndentWidth, - LineEnding, LineWidth, + LineEnding, LineWidth, TrailingNewline, }; use biome_fs::BiomePath; use biome_graphql_formatter::context::GraphqlFormatOptions; @@ -45,8 +45,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use ignore::gitignore::{Gitignore, GitignoreBuilder}; use std::borrow::Cow; use std::ops::Deref; -use std::sync::Arc; -use tracing::instrument; +use std::sync::{Arc, RwLock}; /// Settings active in a project. /// @@ -56,6 +55,8 @@ pub struct Settings { /// The configuration that originated this setting, if applicable. source: Option>, + pub(crate) module_graph_resolution_kind: ModuleGraphResolutionKind, + /// Formatter settings applied to all files in the project. pub formatter: FormatSettings, /// Linter settings applied to all files in the project. @@ -75,6 +76,9 @@ pub struct Settings { // TODO: remove once HTML full support is stable pub experimental_full_html_support: Option, + + // TODO: remove once embedded snippets support is stable + pub experimental_js_embedded_snippets_enabled: Option, } impl Settings { @@ -84,6 +88,12 @@ impl Settings { .value() } + pub fn experimental_js_embedded_snippets_enabled(&self) -> bool { + self.experimental_js_embedded_snippets_enabled + .unwrap_or_default() + .value() + } + pub fn source(&self) -> Option { self.source.as_ref()?.source() } @@ -156,6 +166,8 @@ impl Settings { // javascript settings if let Some(javascript) = configuration.javascript { + self.experimental_js_embedded_snippets_enabled = + javascript.experimental_embedded_snippets_enabled; self.languages.javascript = javascript.into() } // json settings @@ -354,6 +366,166 @@ impl Settings { } } +#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub enum ModuleGraphResolutionKind { + #[default] + None, + Modules, + ModulesAndTypes, +} + +impl ModuleGraphResolutionKind { + pub const fn is_modules_and_types(&self) -> bool { + matches!(self, Self::ModulesAndTypes) + } +} + +impl From<&ScanKind> for ModuleGraphResolutionKind { + fn from(value: &ScanKind) -> Self { + match value { + ScanKind::NoScanner | ScanKind::KnownFiles | ScanKind::TargetedKnownFiles { .. } => { + Self::None + } + ScanKind::Project => Self::Modules, + ScanKind::TypeAware => Self::ModulesAndTypes, + } + } +} + +pub type SettingsWithEditor<'a> = SettingsHandle<'a, Option>; + +/// Handle object holding a temporary lock on the workspace settings until +/// the deferred language-specific options resolution is called +#[derive(Debug)] +pub struct SettingsHandle<'a, E> { + inner: RwLock<&'a Settings>, + + /// Additional per-request state injected by the editor + editor: E, +} + +impl<'a, E> SettingsHandle<'a, E> { + pub fn new(settings: &'a Settings, editor: E) -> Self { + let inner = RwLock::new(settings); + Self { inner, editor } + } +} + +impl<'a, E> AsRef for SettingsHandle<'a, E> { + fn as_ref(&self) -> &Settings { + &self.inner.read().unwrap() + } +} + +impl<'a> SettingsHandle<'a, Option> { + pub(crate) fn full_source(&self) -> Option> { + self.as_ref().source.clone() + } + + fn as_merged_settings(&self) -> Settings { + self.editor + .as_ref() + .map(|editor| { + let mut settings = self.inner.read().unwrap().clone(); + let workspace_directory = self.as_ref().source.as_ref().and_then(|source| { + source + .as_ref() + .source + .as_ref() + .and_then(|source| source.1.clone()) + }); + + // TODO handle error + let _ = + settings.merge_with_configuration(editor.clone(), workspace_directory, vec![]); + + settings + }) + .unwrap_or(self.as_ref().clone()) + } + /// Resolve the formatting options for the given language + pub fn format_options( + &self, + path: &BiomePath, + file_source: &DocumentFileSource, + ) -> L::FormatOptions + where + L: ServiceLanguage, + { + let settings = self.as_merged_settings(); + let formatter = &settings.formatter; + let overrides = &settings.override_settings; + let language_settings = &L::lookup_settings(&settings.languages).formatter; + L::resolve_format_options(formatter, overrides, language_settings, path, file_source) + } + + pub fn parse_options( + &self, + path: &BiomePath, + file_source: &DocumentFileSource, + ) -> L::ParserOptions + where + L: ServiceLanguage, + { + let settings = self.as_merged_settings(); + let overrides = &settings.override_settings; + let language_settings = &L::lookup_settings(&settings.languages).parser; + + L::resolve_parse_options(overrides, language_settings, path, file_source) + } + + pub fn analyzer_options( + &self, + path: &BiomePath, + file_source: &DocumentFileSource, + suppression_reason: Option<&str>, + ) -> AnalyzerOptions + where + L: ServiceLanguage, + { + let settings = self.as_merged_settings(); + let linter_settings = &L::lookup_settings(&settings.languages).linter; + let environment = L::resolve_environment(&settings); + L::resolve_analyzer_options( + &settings, + linter_settings, + environment, + path, + file_source, + suppression_reason, + ) + } + + /// Whether the linter is enabled for this file path + pub fn linter_enabled_for_file_path(&self, path: &Utf8Path) -> bool + where + L: ServiceLanguage, + { + let settings = self.as_merged_settings(); + L::linter_enabled_for_file_path(&settings, path) + } + + /// Whether the formatter is enabled for this file path + pub fn formatter_enabled_for_file_path(&self, path: &Utf8Path) -> bool + where + L: ServiceLanguage, + { + let settings = self.as_merged_settings(); + L::formatter_enabled_for_file_path(&settings, path) + } + + /// Whether the assist is enabled for this file path + pub fn assist_enabled_for_file_path(&self, path: &Utf8Path) -> bool + where + L: ServiceLanguage, + { + let settings = self.as_merged_settings(); + L::assist_enabled_for_file_path(&settings, path) + } +} + /// Formatter settings for the entire workspace #[derive(Clone, Debug, Default)] pub struct FormatSettings { @@ -369,6 +541,7 @@ pub struct FormatSettings { pub attribute_position: Option, pub bracket_same_line: Option, pub bracket_spacing: Option, + pub trailing_newline: Option, pub expand: Option, /// List of included paths/files pub includes: Includes, @@ -396,6 +569,7 @@ pub struct OverrideFormatSettings { pub bracket_same_line: Option, pub attribute_position: Option, pub expand: Option, + pub trailing_newline: Option, } impl From for OverrideFormatSettings { @@ -411,6 +585,7 @@ impl From for OverrideFormatSettings { bracket_same_line: conf.bracket_same_line, attribute_position: conf.attribute_position, expand: conf.expand, + trailing_newline: conf.trailing_newline, } } } @@ -525,7 +700,7 @@ impl From for LanguageSettings { } if let Some(jsx_runtime) = javascript.jsx_runtime { - language_setting.environment = jsx_runtime.into(); + language_setting.environment.jsx_runtime = Some(jsx_runtime); } if let Some(globals) = javascript.globals { @@ -1106,81 +1281,6 @@ fn to_vcs_settings(config: VcsConfiguration) -> Result( - &self, - path: &BiomePath, - file_source: &DocumentFileSource, - ) -> L::FormatOptions - where - L: ServiceLanguage, - { - let formatter = &self.formatter; - let overrides = &self.override_settings; - let editor_settings = &L::lookup_settings(&self.languages).formatter; - L::resolve_format_options(formatter, overrides, editor_settings, path, file_source) - } - - pub fn parse_options( - &self, - path: &BiomePath, - file_source: &DocumentFileSource, - ) -> L::ParserOptions - where - L: ServiceLanguage, - { - let overrides = &self.override_settings; - let editor_settings = &L::lookup_settings(&self.languages).parser; - L::resolve_parse_options(overrides, editor_settings, path, file_source) - } - - pub fn analyzer_options( - &self, - path: &BiomePath, - file_source: &DocumentFileSource, - suppression_reason: Option<&str>, - ) -> AnalyzerOptions - where - L: ServiceLanguage, - { - let linter_settings = &L::lookup_settings(&self.languages).linter; - - let environment = L::resolve_environment(self); - L::resolve_analyzer_options( - self, - linter_settings, - environment, - path, - file_source, - suppression_reason, - ) - } - - /// Whether the linter is enabled for this file path - pub fn linter_enabled_for_file_path(&self, path: &Utf8Path) -> bool - where - L: ServiceLanguage, - { - L::linter_enabled_for_file_path(self, path) - } - - /// Whether the formatter is enabled for this file path - pub fn formatter_enabled_for_file_path(&self, path: &Utf8Path) -> bool - where - L: ServiceLanguage, - { - L::formatter_enabled_for_file_path(self, path) - } - - /// Whether the assist is enabled for this file path - pub fn assist_enabled_for_file_path(&self, path: &Utf8Path) -> bool - where - L: ServiceLanguage, - { - L::assist_enabled_for_file_path(self, path) - } - /// Whether the formatter should format with parsing errors, for this file path pub fn format_with_errors_enabled_for_this_file_path(&self, path: &Utf8Path) -> bool { self.override_settings @@ -1544,6 +1644,10 @@ impl OverrideSettingPattern { if let Some(operator_line_break) = js_formatter.operator_linebreak { options.set_operator_linebreak(operator_line_break); } + if let Some(trailing_newline) = js_formatter.trailing_newline.or(formatter.trailing_newline) + { + options.set_trailing_newline(trailing_newline); + } } fn apply_overrides_to_json_format_options(&self, options: &mut JsonFormatOptions) { @@ -1572,6 +1676,12 @@ impl OverrideSettingPattern { { options.set_bracket_spacing(bracket_spacing); } + if let Some(trailing_newline) = json_formatter + .trailing_newline + .or(formatter.trailing_newline) + { + options.set_trailing_newline(trailing_newline); + } } fn apply_overrides_to_css_format_options(&self, options: &mut CssFormatOptions) { @@ -1593,6 +1703,12 @@ impl OverrideSettingPattern { if let Some(quote_style) = css_formatter.quote_style { options.set_quote_style(quote_style); } + if let Some(trailing_newline) = css_formatter + .trailing_newline + .or(formatter.trailing_newline) + { + options.set_trailing_newline(trailing_newline); + } } fn apply_overrides_to_graphql_format_options(&self, options: &mut GraphqlFormatOptions) { @@ -1620,6 +1736,12 @@ impl OverrideSettingPattern { if let Some(quote_style) = graphql_formatter.quote_style { options.set_quote_style(quote_style); } + if let Some(trailing_newline) = graphql_formatter + .trailing_newline + .or(formatter.trailing_newline) + { + options.set_trailing_newline(trailing_newline); + } } fn apply_overrides_to_grit_format_options(&self, options: &mut GritFormatOptions) { @@ -1638,6 +1760,12 @@ impl OverrideSettingPattern { if let Some(line_width) = grit_formatter.line_width.or(formatter.line_width) { options.set_line_width(line_width); } + if let Some(trailing_newline) = grit_formatter + .trailing_newline + .or(formatter.trailing_newline) + { + options.set_trailing_newline(trailing_newline); + } } fn apply_overrides_to_html_format_options(&self, options: &mut HtmlFormatOptions) { @@ -1689,6 +1817,13 @@ impl OverrideSettingPattern { } // #endregion + + if let Some(trailing_newline) = html_formatter + .trailing_newline + .or(formatter.trailing_newline) + { + options.set_trailing_newline(trailing_newline); + } } fn apply_overrides_to_js_parser_options(&self, options: &mut JsParserOptions) { @@ -1727,7 +1862,11 @@ impl OverrideSettingPattern { options.allow_wrong_line_comments = allow_wrong_line_comments.value(); } if let Some(css_modules) = css_parser.css_modules_enabled { - options.css_modules = css_modules.value(); + options.css_modules = if css_modules.value() { + CssModulesKind::Classic + } else { + CssModulesKind::None + }; } if let Some(tailwind_directives) = css_parser.tailwind_directives { options.tailwind_directives = tailwind_directives.value(); @@ -1762,6 +1901,7 @@ pub fn to_override_settings( bracket_same_line: formatter.bracket_same_line, attribute_position: formatter.attribute_position, expand: formatter.expand, + trailing_newline: formatter.trailing_newline, }) .unwrap_or_default(); let linter = pattern @@ -1964,6 +2104,7 @@ pub fn to_format_settings( bracket_same_line: conf.bracket_same_line, bracket_spacing: conf.bracket_spacing, expand: conf.expand, + trailing_newline: conf.trailing_newline, includes: Includes::new(working_directory, conf.includes), }) } @@ -1990,6 +2131,7 @@ impl TryFrom for FormatSettings { bracket_spacing: Some(BracketSpacing::default()), expand: conf.expand, format_with_errors: conf.format_with_errors, + trailing_newline: conf.trailing_newline, includes: Default::default(), }) } diff --git a/crates/biome_service/src/settings.tests.rs b/crates/biome_service/src/settings.tests.rs index 0b9da0103e7d..5dbf4bf91877 100644 --- a/crates/biome_service/src/settings.tests.rs +++ b/crates/biome_service/src/settings.tests.rs @@ -1,7 +1,11 @@ -use crate::settings::{LanguageSettings, ServiceLanguage, Settings, to_json_language_settings}; +use crate::scanner::ScanKind; +use crate::settings::{ + LanguageSettings, ModuleGraphResolutionKind, ServiceLanguage, Settings, + to_json_language_settings, +}; use crate::workspace::DocumentFileSource; use biome_analyze::RuleFilter; -use biome_configuration::analyzer::{GroupPlainConfiguration, Nursery, SeverityOrGroup}; +use biome_configuration::analyzer::{GroupPlainConfiguration, SeverityOrGroup, Style}; use biome_configuration::javascript::JsxRuntime; use biome_configuration::json::{JsonAssistConfiguration, JsonLinterConfiguration}; use biome_configuration::max_size::MaxSize; @@ -101,11 +105,11 @@ fn merge_override_linter_group_rule() { ]))), linter: Some(OverrideLinterConfiguration { rules: Some(Rules { - nursery: Some(SeverityOrGroup::Group(Nursery { - use_explicit_type: Some(RuleConfiguration::Plain( + style: Some(SeverityOrGroup::Group(Style { + no_default_export: Some(RuleConfiguration::Plain( RulePlainConfiguration::Off, )), - ..Nursery::default() + ..Style::default() })), ..Rules::default() }), @@ -129,7 +133,7 @@ fn merge_override_linter_group_rule() { assert_eq!( disabled_rules, - FxHashSet::from_iter([RuleFilter::Rule("nursery", "useExplicitType")]) + FxHashSet::from_iter([RuleFilter::Rule("style", "noDefaultExport")]) ); } @@ -231,3 +235,63 @@ fn override_inherits_global_formatter_when_not_specified() { "Formatter should be enabled for .js files" ); } + +#[test] +fn test_module_graph_resolution_kind_from_scan_kind() { + // Test all ScanKind variants map to correct ModuleGraphResolutionKind + assert_eq!( + ModuleGraphResolutionKind::from(&ScanKind::NoScanner), + ModuleGraphResolutionKind::None + ); + + assert_eq!( + ModuleGraphResolutionKind::from(&ScanKind::KnownFiles), + ModuleGraphResolutionKind::None + ); + + assert_eq!( + ModuleGraphResolutionKind::from(&ScanKind::TargetedKnownFiles { + target_paths: vec![], + descend_from_targets: false, + }), + ModuleGraphResolutionKind::None + ); + + assert_eq!( + ModuleGraphResolutionKind::from(&ScanKind::Project), + ModuleGraphResolutionKind::Modules + ); + + assert_eq!( + ModuleGraphResolutionKind::from(&ScanKind::TypeAware), + ModuleGraphResolutionKind::ModulesAndTypes + ); +} + +#[test] +fn test_module_graph_resolution_kind_is_modules_and_types() { + // Test is_modules_and_types predicate + assert!(!ModuleGraphResolutionKind::None.is_modules_and_types()); + assert!(!ModuleGraphResolutionKind::Modules.is_modules_and_types()); + assert!(ModuleGraphResolutionKind::ModulesAndTypes.is_modules_and_types()); +} + +#[test] +fn test_type_aware_scan_enables_module_graph_type_inference() { + // This test verifies that TypeAware scan kind results in type inference being enabled + let type_aware_kind = ModuleGraphResolutionKind::from(&ScanKind::TypeAware); + assert!( + type_aware_kind.is_modules_and_types(), + "TypeAware scan should enable type inference" + ); +} + +#[test] +fn test_project_scan_disables_module_graph_type_inference() { + // This test verifies that Project scan kind does NOT enable type inference + let project_kind = ModuleGraphResolutionKind::from(&ScanKind::Project); + assert!( + !project_kind.is_modules_and_types(), + "Project scan should NOT enable type inference" + ); +} diff --git a/crates/biome_service/src/snapshots/biome_service__workspace__tests__debug_registered_types.snap b/crates/biome_service/src/snapshots/biome_service__workspace__tests__debug_registered_types.snap index 96159df116b1..33df132e5b56 100644 --- a/crates/biome_service/src/snapshots/biome_service__workspace__tests__debug_registered_types.snap +++ b/crates/biome_service/src/snapshots/biome_service__workspace__tests__debug_registered_types.snap @@ -2,6 +2,7 @@ source: crates/biome_service/src/workspace.tests.rs expression: result.unwrap() --- + TypeId(0) => instanceof unresolved reference "Person" (scope ID: 0) TypeId(1) => Constructor { diff --git a/crates/biome_service/src/snapshots/not_found.snap b/crates/biome_service/src/snapshots/not_found.snap index 372d4a6985a1..1fb8e76aff7e 100644 --- a/crates/biome_service/src/snapshots/not_found.snap +++ b/crates/biome_service/src/snapshots/not_found.snap @@ -7,6 +7,3 @@ not_found.js internalError/fs INTERNAL ━━━━━━━━━━━━━ × The file does not exist in the workspace. ! This diagnostic was derived from an internal Biome error. Potential bug, please report it if necessary. - - - diff --git a/crates/biome_service/src/snapshots/source_file_not_supported.snap b/crates/biome_service/src/snapshots/source_file_not_supported.snap index 6314f52bf6c9..630de4bc57c0 100644 --- a/crates/biome_service/src/snapshots/source_file_not_supported.snap +++ b/crates/biome_service/src/snapshots/source_file_not_supported.snap @@ -9,6 +9,3 @@ not_supported.toml files/missingHandler ━━━━━━━━━━━━━ Verbose advice i If you want to turn off this diagnostic, consider using --files-ignore-unknown from the CLI, or files.ignoreUnknown from the configuration file. - - - diff --git a/crates/biome_service/src/snapshots/transport_channel_closed.snap b/crates/biome_service/src/snapshots/transport_channel_closed.snap index aeea29e7c882..9d2f6613b052 100644 --- a/crates/biome_service/src/snapshots/transport_channel_closed.snap +++ b/crates/biome_service/src/snapshots/transport_channel_closed.snap @@ -7,6 +7,3 @@ internalError/io INTERNAL ━━━━━━━━━━━━━━━━━ × a request to the remote workspace failed because the connection was interrupted ! This diagnostic was derived from an internal Biome error. Potential bug, please report it if necessary. - - - diff --git a/crates/biome_service/src/snapshots/transport_rpc_error.snap b/crates/biome_service/src/snapshots/transport_rpc_error.snap index 7975e4654f36..c9f61a6abe7d 100644 --- a/crates/biome_service/src/snapshots/transport_rpc_error.snap +++ b/crates/biome_service/src/snapshots/transport_rpc_error.snap @@ -7,6 +7,3 @@ internalError/io INTERNAL ━━━━━━━━━━━━━━━━━ × Some generic error ! This diagnostic was derived from an internal Biome error. Potential bug, please report it if necessary. - - - diff --git a/crates/biome_service/src/snapshots/transport_serde_error.snap b/crates/biome_service/src/snapshots/transport_serde_error.snap index bcc3885e16c2..b890358028e4 100644 --- a/crates/biome_service/src/snapshots/transport_serde_error.snap +++ b/crates/biome_service/src/snapshots/transport_serde_error.snap @@ -7,6 +7,3 @@ internalError/io INTERNAL ━━━━━━━━━━━━━━━━━ × serialization error: Some serialization/deserialization error ! This diagnostic was derived from an internal Biome error. Potential bug, please report it if necessary. - - - diff --git a/crates/biome_service/src/snapshots/transport_timeout.snap b/crates/biome_service/src/snapshots/transport_timeout.snap index b5008f3c515b..0010acfdce36 100644 --- a/crates/biome_service/src/snapshots/transport_timeout.snap +++ b/crates/biome_service/src/snapshots/transport_timeout.snap @@ -7,6 +7,3 @@ internalError/io INTERNAL ━━━━━━━━━━━━━━━━━ × the request to the remote workspace timed out ! This diagnostic was derived from an internal Biome error. Potential bug, please report it if necessary. - - - diff --git a/crates/biome_service/src/workspace.rs b/crates/biome_service/src/workspace.rs index 805a4b99e446..9aa4118053de 100644 --- a/crates/biome_service/src/workspace.rs +++ b/crates/biome_service/src/workspace.rs @@ -52,7 +52,7 @@ //! format a file with a language that does not have a formatter mod client; -mod document; +pub(crate) mod document; mod server; use biome_analyze::{ActionCategory, RuleCategories}; @@ -95,6 +95,7 @@ pub use crate::{ #[cfg(feature = "schema")] use schemars::{Schema, SchemaGenerator}; +use crate::settings::{ModuleGraphResolutionKind, SettingsWithEditor}; pub use client::{TransportRequest, WorkspaceClient, WorkspaceTransport}; pub use server::OpenFileReason; @@ -121,8 +122,15 @@ pub struct SupportsFeatureParams { pub path: BiomePath, pub features: FeatureName, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, + #[serde(default, skip_serializing_if = "is_false")] pub skip_ignore_check: bool, + + /// Features that shouldn't be enabled + #[serde(default, skip_serializing_if = "FeatureName::is_empty")] + pub not_requested_features: FeatureName, } #[derive(Debug, serde::Serialize, serde::Deserialize, Default)] @@ -188,7 +196,7 @@ impl FeaturesSupported { #[inline] pub(crate) fn with_settings_and_language( mut self, - settings: &Settings, + settings: &SettingsWithEditor, path: &Utf8Path, capabilities: &Capabilities, ) -> Self { @@ -228,7 +236,8 @@ impl FeaturesSupported { } } - if let Some(experimental_full_html_support) = settings.experimental_full_html_support + if let Some(experimental_full_html_support) = + settings.as_ref().experimental_full_html_support && experimental_full_html_support.value() { self.insert(FeatureKind::HtmlFullSupport, SupportKind::Supported); @@ -239,6 +248,14 @@ impl FeaturesSupported { self } + #[inline] + pub fn with_not_requested_features(mut self, feature_name: FeatureName) -> Self { + for feature in feature_name.iter() { + self.insert(feature, SupportKind::NotRequested); + } + self + } + /// The file will be ignored for all features #[inline] pub fn set_ignored_for_all_features(&mut self) { @@ -267,6 +284,12 @@ impl FeaturesSupported { matches!(support_kind, SupportKind::Supported) } + #[inline] + pub fn feature_is_not_enabled(&self, feature: FeatureKind) -> bool { + let support_kind = self.0[feature.index()]; + matches!(support_kind, SupportKind::FeatureNotEnabled) + } + pub fn supports_lint(&self) -> bool { self.supports(FeatureKind::Lint) } @@ -325,7 +348,10 @@ impl FeaturesSupported { /// The file is ignored only if all the features marked it as ignored pub fn is_ignored(&self) -> bool { - self.0.iter().all(|support_kind| support_kind.is_ignored()) + self.0 + .iter() + .filter(|support_kind| !support_kind.is_not_requested()) + .all(|support_kind| support_kind.is_ignored()) } /// The file is protected only if all the features marked it as protected @@ -339,6 +365,7 @@ impl FeaturesSupported { pub fn is_not_supported(&self) -> bool { self.0 .iter() + .filter(|support_kind| !support_kind.is_not_requested()) .all(|support_kind| support_kind.is_not_supported()) } @@ -346,6 +373,7 @@ impl FeaturesSupported { pub fn is_not_enabled(&self) -> bool { self.0 .iter() + .filter(|support_kind| !support_kind.is_not_requested()) .all(|support_kind| support_kind.is_not_enabled()) } @@ -516,6 +544,9 @@ pub enum SupportKind { FeatureNotEnabled, /// The file is not capable of having this feature FileNotSupported, + /// Particular state used when a client (e.g. CLI) doesn't require a particular feature. + /// It's very much ignore [SupportKind::Ignored], but it's silent + NotRequested, } impl Display for SupportKind { @@ -526,6 +557,7 @@ impl Display for SupportKind { Self::Protected => write!(f, "Protected"), Self::FeatureNotEnabled => write!(f, "FeatureNotEnabled"), Self::FileNotSupported => write!(f, "FileNotSupported"), + Self::NotRequested => write!(f, "NotRequested"), } } } @@ -546,6 +578,9 @@ impl SupportKind { pub const fn is_protected(&self) -> bool { matches!(self, Self::Protected) } + pub const fn is_not_requested(&self) -> bool { + matches!(self, Self::NotRequested) + } } #[repr(u8)] @@ -619,6 +654,12 @@ impl FeatureKind { )] pub struct FeatureName(BitFlags); +impl Default for FeatureName { + fn default() -> Self { + Self::empty() + } +} + impl Display for FeatureName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Debug::fmt(self, f) @@ -708,6 +749,11 @@ impl FeaturesBuilder { self } + pub fn without_formatter(mut self) -> Self { + self.0.remove(FeatureKind::Format); + self + } + pub fn with_linter(mut self) -> Self { self.0.insert(FeatureKind::Lint); self @@ -731,6 +777,11 @@ impl FeaturesBuilder { self } + pub fn without_search(mut self) -> Self { + self.0.remove(FeatureKind::Search); + self + } + pub fn build(self) -> FeatureName { FeatureName(self.0) } @@ -745,6 +796,8 @@ pub struct UpdateSettingsParams { pub workspace_directory: Option, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub extended_configurations: Vec<(BiomePath, Configuration)>, + #[serde(default)] + pub module_graph_resolution_kind: ModuleGraphResolutionKind, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -781,6 +834,9 @@ pub struct OpenFileParams { /// the file is opened through the LSP Proxy. #[serde(default)] pub persist_node_cache: bool, + + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -918,6 +974,9 @@ pub struct ChangeFileParams { pub path: BiomePath, pub content: String, pub version: i32, + + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -938,6 +997,7 @@ pub struct CloseFileParams { #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase")] pub struct UpdateModuleGraphParams { + pub project_key: ProjectKey, pub path: BiomePath, /// The kind of update to apply to the module graph pub update_kind: UpdateKind, @@ -967,6 +1027,8 @@ pub struct PullDiagnosticsParams { pub enabled_rules: Vec, /// When `false` the diagnostics, don't have code frames of the code actions (fixes, suppressions, etc.) pub pull_code_actions: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -994,6 +1056,8 @@ pub struct PullActionsParams { pub enabled_rules: Vec, #[serde(default)] pub categories: RuleCategories, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -1010,6 +1074,8 @@ pub struct PullDiagnosticsAndActionsParams { pub enabled_rules: Vec, #[serde(default)] pub categories: RuleCategories, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -1042,6 +1108,8 @@ pub struct CodeAction { pub struct FormatFileParams { pub project_key: ProjectKey, pub path: BiomePath, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -1051,6 +1119,8 @@ pub struct FormatRangeParams { pub project_key: ProjectKey, pub path: BiomePath, pub range: TextRange, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -1060,6 +1130,8 @@ pub struct FormatOnTypeParams { pub project_key: ProjectKey, pub path: BiomePath, pub offset: TextSize, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)] @@ -1093,6 +1165,8 @@ pub struct FixFileParams { pub rule_categories: RuleCategories, #[serde(default)] pub suppression_reason: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub inline_config: Option, } #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] @@ -1695,6 +1769,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { path: self.path.clone(), version, content, + inline_config: None, }) } @@ -1720,6 +1795,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { skip, enabled_rules: vec![], pull_code_actions, + inline_config: None, }) } @@ -1741,6 +1817,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { suppression_reason, enabled_rules, categories, + inline_config: None, }) } @@ -1748,6 +1825,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { self.workspace.format_file(FormatFileParams { project_key: self.project_key, path: self.path.clone(), + inline_config: None, }) } @@ -1763,6 +1841,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { project_key: self.project_key, path: self.path.clone(), range, + inline_config: None, }) } @@ -1771,6 +1850,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { project_key: self.project_key, path: self.path.clone(), offset, + inline_config: None, }) } @@ -1793,6 +1873,7 @@ impl<'app, W: Workspace + ?Sized> FileGuard<'app, W> { rule_categories, suppression_reason, enabled_rules: vec![], + inline_config: None, }) } diff --git a/crates/biome_service/src/workspace.tests.rs b/crates/biome_service/src/workspace.tests.rs index b4844d278321..c4bbccc1a440 100644 --- a/crates/biome_service/src/workspace.tests.rs +++ b/crates/biome_service/src/workspace.tests.rs @@ -15,16 +15,16 @@ use biome_plugin_loader::{PluginConfiguration, Plugins}; use camino::Utf8PathBuf; use insta::{assert_debug_snapshot, assert_snapshot}; -use crate::file_handlers::DocumentFileSource; -use crate::projects::ProjectKey; -use crate::{Workspace, WorkspaceError}; - use super::{ CloseFileParams, CloseProjectParams, FileContent, FileFeaturesResult, FileGuard, GetModuleGraphParams, GetSyntaxTreeParams, OpenFileParams, OpenProjectParams, OpenProjectResult, PullDiagnosticsParams, ScanKind, ScanProjectParams, UpdateKind, UpdateModuleGraphParams, UpdateSettingsParams, server, }; +use crate::file_handlers::DocumentFileSource; +use crate::projects::ProjectKey; +use crate::settings::ModuleGraphResolutionKind; +use crate::{Workspace, WorkspaceError}; fn create_server() -> (Box, ProjectKey) { let workspace = server(Arc::new(MemoryFileSystem::default()), None); @@ -52,6 +52,7 @@ fn debug_control_flow() { content: FileContent::from_client(SOURCE), document_file_source: Some(DocumentFileSource::from(JsFileSource::default())), persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -74,6 +75,7 @@ fn recognize_typescript_definition_file() { content: FileContent::from_client("export const foo: number"), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -95,6 +97,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42}"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -110,6 +113,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42}//comment"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -125,6 +129,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42,}"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -140,6 +145,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42}//comment"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -155,6 +161,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42,}"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -170,6 +177,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42}//comment"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -189,6 +197,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42}//comment"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -208,6 +217,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42,}"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -231,6 +241,7 @@ fn correctly_handle_json_files() { content: FileContent::from_client(r#"{"a": 42,}//comment"#), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -267,6 +278,7 @@ type User { ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -298,6 +310,7 @@ fn correctly_pulls_lint_diagnostics() { ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -333,6 +346,7 @@ fn pull_grit_debug_info() { ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -404,6 +418,7 @@ fn files_loaded_by_the_scanner_are_only_unloaded_when_the_project_is_unregistere content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -456,6 +471,7 @@ fn too_large_files_are_tracked_but_not_parsed() { }, workspace_directory: None, extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::None, }) .unwrap(); @@ -466,6 +482,7 @@ fn too_large_files_are_tracked_but_not_parsed() { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -515,6 +532,7 @@ fn plugins_are_loaded_and_used_during_analysis() { }, workspace_directory: Some(BiomePath::new("/project")), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::None, }) .unwrap(); @@ -525,6 +543,7 @@ fn plugins_are_loaded_and_used_during_analysis() { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -537,6 +556,7 @@ fn plugins_are_loaded_and_used_during_analysis() { skip: Vec::new(), enabled_rules: Vec::new(), pull_code_actions: true, + inline_config: None, }) .unwrap(); assert_debug_snapshot!(result.diagnostics); @@ -583,6 +603,7 @@ language css; }, workspace_directory: Some(BiomePath::new("/project")), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::None, }) .unwrap(); @@ -593,6 +614,7 @@ language css; content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -605,6 +627,7 @@ language css; skip: Vec::new(), enabled_rules: Vec::new(), pull_code_actions: true, + inline_config: None, }) .unwrap(); assert_debug_snapshot!(result.diagnostics); @@ -647,6 +670,7 @@ fn plugins_may_use_invalid_span() { }, workspace_directory: Some(BiomePath::new("/project")), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::None, }) .unwrap(); @@ -657,6 +681,7 @@ fn plugins_may_use_invalid_span() { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -669,6 +694,7 @@ fn plugins_may_use_invalid_span() { skip: Vec::new(), enabled_rules: Vec::new(), pull_code_actions: true, + inline_config: None, }) .unwrap(); assert_debug_snapshot!(result.diagnostics); @@ -765,6 +791,7 @@ const hasOwn = Object.hasOwn({ foo: 'bar' }, 'foo');"#, }, workspace_directory: Some(BiomePath::new("/project")), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::None, }) .unwrap(); @@ -786,6 +813,7 @@ const hasOwn = Object.hasOwn({ foo: 'bar' }, 'foo');"#, content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -798,6 +826,7 @@ const hasOwn = Object.hasOwn({ foo: 'bar' }, 'foo');"#, skip: Vec::new(), enabled_rules: Vec::new(), pull_code_actions: true, + inline_config: None, }) .unwrap(); // Filter only diagnostics with category name "plugin" @@ -844,6 +873,7 @@ class Person { ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -878,6 +908,7 @@ class Person { ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -912,6 +943,7 @@ class Person { ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -948,6 +980,7 @@ async function test() { ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -965,6 +998,7 @@ export const debounce = function debounce() {}; ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -980,6 +1014,7 @@ export const squash = function squash() {}; ), document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -987,12 +1022,14 @@ export const squash = function squash() {}; .update_module_graph(UpdateModuleGraphParams { path: BiomePath::new("/project/file.js"), update_kind: UpdateKind::AddOrUpdate, + project_key, }) .unwrap(); workspace .update_module_graph(UpdateModuleGraphParams { path: BiomePath::new("/project/utils.js"), update_kind: UpdateKind::AddOrUpdate, + project_key, }) .unwrap(); @@ -1000,6 +1037,7 @@ export const squash = function squash() {}; .update_module_graph(UpdateModuleGraphParams { path: BiomePath::new("/project/dynamic.js"), update_kind: UpdateKind::AddOrUpdate, + project_key, }) .unwrap(); diff --git a/crates/biome_service/src/workspace/document.rs b/crates/biome_service/src/workspace/document/mod.rs similarity index 83% rename from crates/biome_service/src/workspace/document.rs rename to crates/biome_service/src/workspace/document/mod.rs index 1073ca1dfad6..759ff272ec64 100644 --- a/crates/biome_service/src/workspace/document.rs +++ b/crates/biome_service/src/workspace/document/mod.rs @@ -1,14 +1,18 @@ +pub(crate) mod services; + use crate::diagnostics::FileTooLarge; use crate::file_handlers::FormatEmbedNode; use crate::settings::ServiceLanguage; use crate::workspace::DocumentFileSource; -use biome_css_syntax::{CssLanguage, CssRoot}; +use crate::workspace::document::services::embedded_bindings::EmbeddedExportedBindings; +use crate::workspace::document::services::embedded_value_references::EmbeddedValueReferences; +use biome_css_syntax::{AnyCssRoot, CssLanguage}; use biome_diagnostics::Error; use biome_diagnostics::serde::Diagnostic as SerdeDiagnostic; use biome_js_syntax::JsLanguage; use biome_json_syntax::JsonLanguage; use biome_parser::AnyParse; -use biome_rowan::{SyntaxNodeWithOffset, TextRange, TextSize}; +use biome_rowan::{AstNode, SyntaxNodeWithOffset, TextRange, TextSize}; use std::marker::PhantomData; #[derive(Debug, Clone)] @@ -184,6 +188,14 @@ impl EmbeddedSnippet { self.parse.unwrap_as_embedded_syntax_node().into_node::() } + pub fn tree(&self) -> N + where + N: AstNode, + N::Language: 'static, + { + self.parse.tree() + } + /// This function transforms diagnostics coming from the parser into serializable diagnostics pub fn into_serde_diagnostics(self) -> Vec { self.parse @@ -256,37 +268,68 @@ impl Document { /// stored with [biome_rowan::AstPtr]. The service needs to accept the language root so /// the pointer can be retrieved with its typed counter part. #[derive(Clone, Debug)] -pub enum DocumentServices { +pub struct DocumentServices { + /// Service to track bindings exported by the document + exported_bindings: Option, + + /// Service to track value references from non-source snippets + value_references: Option, + /// The document doesn't have any services - None, - Css(CssDocumentServices), + language: LanguageServices, } -impl From for DocumentServices { - fn from(services: CssDocumentServices) -> Self { - Self::Css(services) +impl DocumentServices { + pub fn none() -> Self { + Self { + exported_bindings: None, + value_references: None, + language: LanguageServices::None, + } } -} -impl DocumentServices { - pub fn new_css() -> Self { - Self::Css(CssDocumentServices { - semantic_model: None, - }) + pub(crate) fn set_embedded_bindings(&mut self, bindings: EmbeddedExportedBindings) { + self.exported_bindings = Some(bindings); } - pub fn none() -> Self { - Self::None + pub(crate) fn set_embedded_value_references(&mut self, value_refs: EmbeddedValueReferences) { + self.value_references = Some(value_refs); } pub fn as_css_services(&self) -> Option<&CssDocumentServices> { - if let Self::Css(services) = self { + if let LanguageServices::Css(services) = &self.language { Some(services) } else { None } } + + pub fn embedded_bindings(&self) -> Option { + self.exported_bindings.clone() + } + + pub fn embedded_value_references(&self) -> Option { + self.value_references.clone() + } +} + +#[derive(Clone, Debug)] +pub enum LanguageServices { + /// The document doesn't have any services + None, + Css(CssDocumentServices), +} + +impl From for DocumentServices { + fn from(services: CssDocumentServices) -> Self { + Self { + exported_bindings: None, + value_references: None, + language: LanguageServices::Css(services), + } + } } + #[derive(Clone, Default, Debug)] pub struct CssDocumentServices { /// Semantic model that belongs to the file @@ -294,7 +337,7 @@ pub struct CssDocumentServices { } impl CssDocumentServices { - pub fn with_css_semantic_model(mut self, root: &CssRoot) -> Self { + pub fn with_css_semantic_model(mut self, root: &AnyCssRoot) -> Self { self.semantic_model = Some(biome_css_semantic::semantic_model(root)); self } diff --git a/crates/biome_service/src/workspace/document/services/embedded_bindings.rs b/crates/biome_service/src/workspace/document/services/embedded_bindings.rs new file mode 100644 index 000000000000..1361e343f68e --- /dev/null +++ b/crates/biome_service/src/workspace/document/services/embedded_bindings.rs @@ -0,0 +1,315 @@ +use biome_js_syntax::{ + AnyJsArrayBindingPatternElement, AnyJsBindingPattern, AnyJsImportClause, AnyJsModuleItem, + AnyJsObjectBindingPatternMember, AnyJsRoot, JsImport, JsModuleItemList, JsVariableStatement, +}; +use biome_rowan::{AstNode, AstSeparatedList, TextRange, TokenText, WalkEvent}; +use rustc_hash::FxHashMap; +use std::collections::VecDeque; + +#[derive(Debug, Clone, Default)] +pub struct EmbeddedExportedBindings { + pub bindings: Vec>, +} + +#[derive(Debug)] +pub(crate) struct EmbeddedBuilder { + /// Bindings tracked inside JavaScript snippets. + js_bindings: FxHashMap, +} + +impl EmbeddedExportedBindings { + pub(crate) fn builder(&self) -> EmbeddedBuilder { + EmbeddedBuilder::new() + } + + pub(crate) fn finish(&mut self, builder: EmbeddedBuilder) { + self.bindings.push(builder.js_bindings); + } +} +impl EmbeddedBuilder { + fn new() -> Self { + Self { + js_bindings: FxHashMap::default(), + } + } + + /// To call when visiting a source snippet, where bindings are defined. + pub(crate) fn visit_js_source_snippet(&mut self, root: &AnyJsRoot) { + let preorder = root.syntax().preorder(); + + for event in preorder { + match event { + WalkEvent::Enter(node) => { + if let Some(module_item) = JsModuleItemList::cast(node) { + self.visit_module_item_list(module_item); + } + } + WalkEvent::Leave(_) => {} + } + } + } + + fn visit_module_item_list(&mut self, list: JsModuleItemList) { + for item in list { + match item { + AnyJsModuleItem::AnyJsStatement(statement) => { + if let Some(variable_statement) = statement.as_js_variable_statement() { + self.visit_js_variable_statement(variable_statement.clone()); + } + } + AnyJsModuleItem::JsExport(_) => {} + AnyJsModuleItem::JsImport(import) => { + self.visit_js_import(import); + } + } + } + } + + fn visit_js_import(&mut self, import: JsImport) -> Option<()> { + let clause = import.import_clause().ok()?; + if let Some(named_specifiers) = clause.named_specifiers() { + let imported_names = named_specifiers + .specifiers() + .iter() + .flatten() + .map(|specifier| specifier.imported_name()); + + for imported_name in imported_names { + let Some(imported_name) = imported_name else { + continue; + }; + self.js_bindings.insert( + imported_name.text_trimmed_range(), + imported_name.token_text_trimmed(), + ); + } + } + + if let AnyJsImportClause::JsImportDefaultClause(import) = clause { + let name = import.default_specifier().ok()?; + let name = name.local_name().ok()?; + let name = name.as_js_identifier_binding()?; + let name = name.name_token().ok()?; + self.js_bindings + .insert(name.text_trimmed_range(), name.token_text_trimmed()); + } + + Some(()) + } + + fn visit_js_variable_statement(&mut self, statement: JsVariableStatement) -> Option<()> { + let declaration = statement.declaration().ok()?; + for declarator in declaration.declarators().iter().flatten() { + let id = declarator.id().ok()?; + + match id { + AnyJsBindingPattern::AnyJsBinding(binding) => { + let identifier = binding.as_js_identifier_binding()?; + let token = identifier.name_token().ok()?; + self.js_bindings + .insert(token.text_trimmed_range(), token.token_text_trimmed()); + } + AnyJsBindingPattern::JsArrayBindingPattern(array_binding_pattern) => { + for element in array_binding_pattern.elements().iter().flatten() { + match element { + AnyJsArrayBindingPatternElement::JsArrayBindingPatternElement( + element, + ) => { + self.visit_any_js_binding_pattern(VecDeque::from([element + .pattern() + .ok()?]))?; + } + AnyJsArrayBindingPatternElement::JsArrayBindingPatternRestElement( + rest, + ) => { + self.visit_any_js_binding_pattern(VecDeque::from([rest + .pattern() + .ok()?]))?; + } + AnyJsArrayBindingPatternElement::JsArrayHole(_) => {} + } + } + } + AnyJsBindingPattern::JsObjectBindingPattern(object_binding_pattern) => { + for property in object_binding_pattern.properties().iter().flatten() { + self.visit_object_binding_pattern_member(property)?; + } + } + } + } + + Some(()) + } + + fn visit_object_binding_pattern_member( + &mut self, + property: AnyJsObjectBindingPatternMember, + ) -> Option<()> { + match property { + AnyJsObjectBindingPatternMember::JsBogusBinding(_) => {} + AnyJsObjectBindingPatternMember::JsMetavariable(_) => {} + AnyJsObjectBindingPatternMember::JsObjectBindingPatternProperty(property) => { + self.visit_any_js_binding_pattern(VecDeque::from([property.pattern().ok()?]))?; + } + AnyJsObjectBindingPatternMember::JsObjectBindingPatternRest(rest) => { + let binding = rest.binding().ok()?; + let binding = binding.as_js_identifier_binding()?; + let token = binding.name_token().ok()?; + self.js_bindings + .insert(token.text_trimmed_range(), token.token_text_trimmed()); + } + AnyJsObjectBindingPatternMember::JsObjectBindingPatternShorthandProperty(property) => { + let identifier = property.identifier().ok()?; + let identifier = identifier.as_js_identifier_binding()?; + let token = identifier.name_token().ok()?; + self.js_bindings + .insert(token.text_trimmed_range(), token.token_text_trimmed()); + } + } + + Some(()) + } + + fn visit_any_js_binding_pattern( + &mut self, + mut queue: VecDeque, + ) -> Option<()> { + while let Some(pattern) = queue.pop_front() { + match pattern { + AnyJsBindingPattern::AnyJsBinding(binding) => { + let identifier = binding.as_js_identifier_binding()?; + let token = identifier.name_token().ok()?; + self.js_bindings + .insert(token.text_trimmed_range(), token.token_text_trimmed()); + } + AnyJsBindingPattern::JsArrayBindingPattern(binding_pattern) => { + for element in binding_pattern.elements().iter().flatten() { + match element { + AnyJsArrayBindingPatternElement::JsArrayBindingPatternElement( + element, + ) => { + let pattern = element.pattern().ok()?; + queue.push_back(pattern); + } + AnyJsArrayBindingPatternElement::JsArrayBindingPatternRestElement( + rest_element, + ) => { + let pattern = rest_element.pattern().ok()?; + queue.push_back(pattern); + } + AnyJsArrayBindingPatternElement::JsArrayHole(_) => {} + } + } + } + AnyJsBindingPattern::JsObjectBindingPattern(object) => { + for property in object.properties().iter().flatten() { + self.visit_object_binding_pattern_member(property)?; + } + } + } + } + + Some(()) + } +} + +#[cfg(test)] +mod tests { + use crate::workspace::document::services::embedded_bindings::{ + EmbeddedBuilder, EmbeddedExportedBindings, + }; + use biome_js_parser::JsParserOptions; + use biome_js_syntax::{AnyJsRoot, JsFileSource}; + + fn parse_js(source: &str) -> AnyJsRoot { + let result = biome_js_parser::parse(source, JsFileSource::ts(), JsParserOptions::default()); + result.tree() + } + + fn visit_js_root(service: &mut EmbeddedBuilder, root: &AnyJsRoot) { + service.visit_js_source_snippet(root); + } + + fn contains_binding(service: &EmbeddedExportedBindings, binding: &str) -> bool { + for bindings in service.bindings.iter() { + if bindings.values().any(|token| token.text() == binding) { + return true; + } + } + false + } + + #[test] + fn tracks_import_and_let_js_bindings() { + let source = r#"import { Component } from "somewhere"; +import Component2 from "component.astro" + +let variable = "salut"; + "#; + + let mut service = EmbeddedExportedBindings::default(); + let mut builder = service.builder(); + visit_js_root(&mut builder, &parse_js(source)); + + service.finish(builder); + + assert!(contains_binding(&service, "Component")); + assert!(contains_binding(&service, "Component2")); + assert!(contains_binding(&service, "variable")); + } + + #[test] + fn tracks_import_and_binding_patterns() { + let source = r#"import { Component } from "somewhere"; +import Component2 from "component.astro" + +let {variable, foo: bar} = {}; +let [arr, ...rest] = []; + + "#; + let mut service = EmbeddedExportedBindings::default(); + let mut builder = service.builder(); + visit_js_root(&mut builder, &parse_js(source)); + service.finish(builder); + + assert!(contains_binding(&service, "Component")); + assert!(contains_binding(&service, "Component2")); + assert!(contains_binding(&service, "variable")); + assert!(contains_binding(&service, "bar")); + assert!(contains_binding(&service, "arr")); + assert!(contains_binding(&service, "rest")); + } + + #[test] + fn tracks_multiple_snippets() { + let source = r#"import { Component } from "somewhere"; +import Component2 from "component.astro" + +let {variable, foo: bar} = {}; +let [arr, ...rest] = []; + + "#; + + let source_2 = r#"import { Alas } from "somewhere"; +import Alas2 from "component.astro" + +let lorem = ""; + "#; + + let mut service = EmbeddedExportedBindings::default(); + let mut builder = service.builder(); + visit_js_root(&mut builder, &parse_js(source)); + visit_js_root(&mut builder, &parse_js(source_2)); + service.finish(builder); + + assert!(contains_binding(&service, "Component")); + assert!(contains_binding(&service, "Component2")); + assert!(contains_binding(&service, "variable")); + assert!(contains_binding(&service, "bar")); + assert!(contains_binding(&service, "arr")); + assert!(contains_binding(&service, "rest")); + assert!(contains_binding(&service, "Alas")); + assert!(contains_binding(&service, "Alas2")); + assert!(contains_binding(&service, "lorem")); + } +} diff --git a/crates/biome_service/src/workspace/document/services/embedded_value_references.rs b/crates/biome_service/src/workspace/document/services/embedded_value_references.rs new file mode 100644 index 000000000000..4cfc1db1c5ad --- /dev/null +++ b/crates/biome_service/src/workspace/document/services/embedded_value_references.rs @@ -0,0 +1,252 @@ +use biome_html_syntax::{ + AnyHtmlComponentObjectName, AnyHtmlTagName, HtmlElement, HtmlRoot, HtmlSelfClosingElement, +}; +use biome_js_syntax::{ + AnyJsIdentifierUsage, AnyJsRoot, JsReferenceIdentifier, JsStaticMemberExpression, +}; +use biome_rowan::{AstNode, TextRange, TokenText, WalkEvent}; +use rustc_hash::FxHashMap; + +#[derive(Debug, Clone, Default)] +pub struct EmbeddedValueReferences { + pub references: Vec>, +} + +#[derive(Debug)] +pub(crate) struct EmbeddedValueReferencesBuilder { + references: FxHashMap, +} + +impl EmbeddedValueReferences { + pub(crate) fn builder(&self) -> EmbeddedValueReferencesBuilder { + EmbeddedValueReferencesBuilder::new() + } + + pub(crate) fn finish(&mut self, builder: EmbeddedValueReferencesBuilder) { + self.references.push(builder.references); + } +} + +impl EmbeddedValueReferencesBuilder { + fn new() -> Self { + Self { + references: FxHashMap::default(), + } + } + + /// Visit a non-source snippet to track value references + pub(crate) fn visit_non_source_snippet(&mut self, root: &AnyJsRoot) { + let preorder = root.syntax().preorder(); + + for event in preorder { + match event { + WalkEvent::Enter(node) => { + if let Some(reference) = JsReferenceIdentifier::cast_ref(&node) { + self.visit_reference_identifier(reference); + } else if let Some(member) = JsStaticMemberExpression::cast_ref(&node) { + self.visit_static_member_expression(member); + } + } + WalkEvent::Leave(_) => {} + } + } + } + + /// Visit an HTML root to track component element names as value references. + /// + /// This extracts component names from Vue/Svelte templates like: + /// - `` → tracks `Component` + /// - `` → tracks `AvatarPrimitive` + pub(crate) fn visit_html_root(&mut self, root: &HtmlRoot) { + for node in root.syntax().descendants() { + // Check HtmlElement: ... + if let Some(element) = HtmlElement::cast_ref(&node) { + self.visit_html_element(&element); + } + + // Check HtmlSelfClosingElement: + if let Some(element) = HtmlSelfClosingElement::cast_ref(&node) { + self.visit_html_self_closing_element(&element); + } + } + } + + fn visit_html_element(&mut self, element: &HtmlElement) -> Option<()> { + // Skip script and style tags - these are not component references + if element.is_script_tag() || element.is_style_tag() { + return None; + } + + let opening = element.opening_element().ok()?; + let name = opening.name().ok()?; + + self.track_component_reference(&name); + + Some(()) + } + + fn visit_html_self_closing_element(&mut self, element: &HtmlSelfClosingElement) -> Option<()> { + let name = element.name().ok()?; + + self.track_component_reference(&name); + + Some(()) + } + + /// Track a component name as a value reference + fn track_component_reference(&mut self, name: &AnyHtmlTagName) { + match name { + AnyHtmlTagName::HtmlComponentName(component) => { + // Track simple component: + if let Ok(token) = component.value_token() { + self.references + .insert(token.text_trimmed_range(), token.token_text_trimmed()); + } + } + AnyHtmlTagName::HtmlMemberName(member) => { + // Track the base component from member expression: + if let Ok(object) = member.object() { + self.track_component_object(&object); + } + } + AnyHtmlTagName::HtmlTagName(_) => { + // Regular HTML tag, don't track + } + } + } + + /// Track the object part of a member expression + fn track_component_object(&mut self, object: &AnyHtmlComponentObjectName) { + match object { + AnyHtmlComponentObjectName::HtmlTagName(tag) => { + // For member expressions starting with lowercase (unusual but possible) + if let Ok(token) = tag.value_token() { + self.references + .insert(token.text_trimmed_range(), token.token_text_trimmed()); + } + } + AnyHtmlComponentObjectName::HtmlComponentName(component) => { + // Track the component name + if let Ok(token) = component.value_token() { + self.references + .insert(token.text_trimmed_range(), token.token_text_trimmed()); + } + } + AnyHtmlComponentObjectName::HtmlMemberName(member) => { + // Nested member - track its object recursively + // e.g., A.B.C - track A + if let Ok(object) = member.object() { + self.track_component_object(&object); + } + } + } + } + + fn visit_reference_identifier(&mut self, reference: JsReferenceIdentifier) -> Option<()> { + let usage = AnyJsIdentifierUsage::from(reference.clone()); + if usage.is_only_type() { + return None; + } + let name_token = reference.value_token().ok()?; + self.references.insert( + name_token.text_trimmed_range(), + name_token.token_text_trimmed(), + ); + Some(()) + } + + fn visit_static_member_expression(&mut self, member: JsStaticMemberExpression) -> Option<()> { + let object = member.object().ok()?; + if let Some(reference) = object.as_js_reference_identifier() { + self.visit_reference_identifier(reference.clone())?; + } + Some(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use biome_js_parser::JsParserOptions; + use biome_js_syntax::JsFileSource; + + fn parse_js(source: &str) -> AnyJsRoot { + let result = biome_js_parser::parse(source, JsFileSource::ts(), JsParserOptions::default()); + result.tree() + } + + fn contains_reference(service: &EmbeddedValueReferences, reference: &str) -> bool { + for refs in service.references.iter() { + if refs.values().any(|token| { + let text = token.text(); + text == reference + }) { + return true; + } + } + false + } + + #[test] + fn tracks_value_references() { + let source = r#"Component; FooEnum.Foo;"#; + + let mut service = EmbeddedValueReferences::default(); + let mut builder = service.builder(); + builder.visit_non_source_snippet(&parse_js(source)); + service.finish(builder); + + assert!(contains_reference(&service, "Component")); + assert!(contains_reference(&service, "FooEnum")); + } + + #[test] + fn tracks_static_member_expressions() { + let source = r#"FooEnum.Bar; obj.property.nested;"#; + + let mut service = EmbeddedValueReferences::default(); + let mut builder = service.builder(); + builder.visit_non_source_snippet(&parse_js(source)); + service.finish(builder); + + assert!(contains_reference(&service, "FooEnum")); + assert!(contains_reference(&service, "obj")); + } + + #[test] + fn tracks_multiple_snippets() { + let source_1 = r#"Component; FooEnum.Foo;"#; + let source_2 = r#"AnotherComponent; BarEnum.Bar;"#; + + let mut service = EmbeddedValueReferences::default(); + let mut builder = service.builder(); + builder.visit_non_source_snippet(&parse_js(source_1)); + builder.visit_non_source_snippet(&parse_js(source_2)); + service.finish(builder); + + assert!(contains_reference(&service, "Component")); + assert!(contains_reference(&service, "FooEnum")); + assert!(contains_reference(&service, "AnotherComponent")); + assert!(contains_reference(&service, "BarEnum")); + } + + #[test] + fn tracks_html_element_names() { + use biome_html_parser::{HtmlParseOptions, parse_html}; + + let source = r#""#; + // Enable Vue parsing so component names are parsed correctly + let parsed = parse_html(source, HtmlParseOptions::default().with_vue()); + + println!("Diagnostics: {:?}", parsed.diagnostics()); + println!("Has errors: {}", !parsed.diagnostics().is_empty()); + + let mut service = EmbeddedValueReferences::default(); + let mut builder = service.builder(); + builder.visit_html_root(&parsed.tree()); + service.finish(builder); + + assert!(contains_reference(&service, "Component")); + assert!(contains_reference(&service, "AvatarPrimitive")); + } +} diff --git a/crates/biome_service/src/workspace/document/services/mod.rs b/crates/biome_service/src/workspace/document/services/mod.rs new file mode 100644 index 000000000000..cc32dd601409 --- /dev/null +++ b/crates/biome_service/src/workspace/document/services/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod embedded_bindings; +pub(crate) mod embedded_value_references; diff --git a/crates/biome_service/src/workspace/server.rs b/crates/biome_service/src/workspace/server.rs index 6506e8bbe215..b3c0a1b5bf8d 100644 --- a/crates/biome_service/src/workspace/server.rs +++ b/crates/biome_service/src/workspace/server.rs @@ -1,27 +1,43 @@ -use std::panic::RefUnwindSafe; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; - -use super::{document::Document, *}; -use crate::Watcher; -use crate::configuration::{LoadedConfiguration, read_config}; +use crate::configuration::{LoadedConfiguration, ProjectScanComputer, read_config}; use crate::diagnostics::{FileTooLarge, NoIgnoreFileFound, VcsDiagnostic}; use crate::file_handlers::{ Capabilities, CodeActionsParams, DiagnosticsAndActionsParams, DocumentFileSource, Features, FixAllParams, FormatEmbedNode, LintParams, LintResults, ParseResult, UpdateSnippetsNodes, }; -use crate::projects::{GetFileFeaturesParams, Projects}; +use crate::projects::{GetFileFeaturesParams, ProjectKey, Projects}; use crate::scanner::{ IndexRequestKind, IndexTrigger, ScanOptions, Scanner, ScannerWatcherBridge, WatcherInstruction, WorkspaceScannerBridge, }; +use crate::settings::{ModuleGraphResolutionKind, SettingsHandle, SettingsWithEditor}; +use crate::workspace::document::services::embedded_bindings::{ + EmbeddedBuilder, EmbeddedExportedBindings, +}; +use crate::workspace::document::services::embedded_value_references::EmbeddedValueReferences; +use crate::workspace::document::*; use crate::workspace::document::{AnyEmbeddedSnippet, DocumentServices}; +use crate::workspace::{ + ChangeFileParams, ChangeFileResult, CheckFileSizeParams, CheckFileSizeResult, CloseFileParams, + CloseProjectParams, CssDocumentServices, DropPatternParams, FeaturesBuilder, FileContent, + FileExitsParams, FileFeaturesResult, FixFileParams, FixFileResult, FormatFileParams, + FormatOnTypeParams, FormatRangeParams, GetControlFlowGraphParams, GetFileContentParams, + GetFormatterIRParams, GetModuleGraphParams, GetModuleGraphResult, GetRegisteredTypesParams, + GetSemanticModelParams, GetSyntaxTreeParams, GetSyntaxTreeResult, GetTypeInfoParams, + IgnoreKind, OpenFileParams, OpenFileResult, OpenProjectParams, OpenProjectResult, + ParsePatternParams, ParsePatternResult, PathIsIgnoredParams, PatternId, PullActionsParams, + PullActionsResult, PullDiagnosticsAndActionsParams, PullDiagnosticsAndActionsResult, + PullDiagnosticsParams, PullDiagnosticsResult, RageEntry, RageParams, RageResult, RenameParams, + RenameResult, ScanKind, ScanProjectParams, ScanProjectResult, SearchPatternParams, + SearchResults, ServerInfo, ServiceNotification, Settings, SupportsFeatureParams, + UpdateModuleGraphParams, UpdateSettingsParams, UpdateSettingsResult, +}; +use crate::{Watcher, Workspace, WorkspaceError}; use biome_analyze::{AnalyzerPluginVec, RuleCategory}; use biome_configuration::bool::Bool; use biome_configuration::max_size::MaxSize; use biome_configuration::vcs::VcsClientKind; use biome_configuration::{BiomeDiagnostic, Configuration, ConfigurationPathHint}; -use biome_css_syntax::{CssRoot, CssVariant}; +use biome_css_syntax::{AnyCssRoot, CssVariant}; use biome_deserialize::json::deserialize_from_json_str; use biome_deserialize::{Deserialized, Merge}; use biome_diagnostics::print_diagnostic_to_string; @@ -31,6 +47,7 @@ use biome_diagnostics::{ use biome_formatter::Printed; use biome_fs::{BiomePath, ConfigName, PathKind}; use biome_grit_patterns::{CompilePatternOptions, GritQuery, compile_pattern_with_options}; +use biome_html_syntax::HtmlRoot; use biome_js_syntax::{AnyJsRoot, LanguageVariant, ModuleKind}; use biome_json_parser::JsonParserOptions; use biome_json_syntax::JsonFileSource; @@ -46,6 +63,10 @@ use camino::{Utf8Path, Utf8PathBuf}; use crossbeam::channel::Sender; use papaya::HashMap; use rustc_hash::{FxBuildHasher, FxHashMap}; +use std::fmt::Debug; +use std::panic::RefUnwindSafe; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::sync::watch; use tracing::{info, instrument, warn}; @@ -140,6 +161,14 @@ impl WorkspaceServer { } } + fn settings_handle<'a>( + &self, + settings: &'a Settings, + editor: Option, + ) -> SettingsWithEditor<'a> { + SettingsHandle::new(settings, editor) + } + /// Starts the watcher. /// /// This method will not return until the watcher stops. @@ -289,6 +318,7 @@ impl WorkspaceServer { content, document_file_source, persist_node_cache, + inline_config, } = params; let path: Utf8PathBuf = biome_path.clone().into(); @@ -379,8 +409,9 @@ impl WorkspaceServer { let size = content.len(); let limit = settings.get_max_file_size(&path); + let settings_handle = self.settings_handle(&settings, inline_config); - let (syntax, services) = if size > limit { + let (syntax, mut services) = if size > limit { ( Some(Err(FileTooLarge { size, limit })), DocumentServices::none(), @@ -392,7 +423,7 @@ impl WorkspaceServer { let parsed = self.parse( &path, &content, - &settings, + &settings_handle, file_source_index, &mut node_cache, )?; @@ -424,6 +455,10 @@ impl WorkspaceServer { (Some(Ok(any_parse)), services) }; + + let mut exported_bindings = EmbeddedExportedBindings::default(); + let mut builder = exported_bindings.builder(); + // Second-pass parsing for HTML files with embedded JavaScript and CSS // content. let embedded_snippets = if DocumentFileSource::can_contain_embeds( @@ -432,19 +467,52 @@ impl WorkspaceServer { ) && let Some(Ok(any_parse)) = &syntax { // Second-pass parsing for HTML files with embedded JavaScript and CSS content - - debug!("{:#?}", &any_parse); let mut node_cache = NodeCache::default(); self.parse_embedded_language_snippets( &biome_path, &source, any_parse, &mut node_cache, - &settings, + &settings_handle, + &mut builder, )? } else { Default::default() }; + exported_bindings.finish(builder); + services.set_embedded_bindings(exported_bindings); + + // Track value references from non-source snippets (templates) + let mut value_references = EmbeddedValueReferences::default(); + for snippet in &embedded_snippets { + if let Some(js_snippet) = snippet.as_js_embedded_snippet() { + let Some(file_source) = self.get_source(snippet.file_source_index()) else { + continue; + }; + let Some(js_file_source) = file_source.to_js_file_source() else { + continue; + }; + // Only process non-source snippets (templates) + if !js_file_source.is_embedded_source() { + let mut builder = value_references.builder(); + builder.visit_non_source_snippet(&js_snippet.parse.tree()); + value_references.finish(builder); + } + } + } + + // Also track component element names from HTML templates (Vue/Svelte) + if let Some(html_file_source) = source.to_html_file_source() + && html_file_source.supports_components() + && let Some(Ok(any_parse)) = &syntax + { + let html_root: HtmlRoot = any_parse.tree(); + let mut builder = value_references.builder(); + builder.visit_html_root(&html_root); + value_references.finish(builder); + } + + services.set_embedded_value_references(value_references); let is_indexed = if // Dependency files can be skipped altoghether @@ -521,8 +589,11 @@ impl WorkspaceServer { .and_then(Result::ok) .map(|node| node.unwrap_as_send_node()) { - let (dependencies, diagnostics) = self - .update_service_data(&path, UpdateKind::AddedOrChanged(reason, root, services))?; + let (dependencies, diagnostics) = self.update_service_data( + &path, + UpdateKind::AddedOrChanged(reason, root, services), + project_key, + )?; Ok(InternalOpenFileResult { dependencies, @@ -628,15 +699,18 @@ impl WorkspaceServer { source: &DocumentFileSource, root: &AnyParse, cache: &mut NodeCache, - settings: &Settings, + settings: &SettingsWithEditor, + builder: &mut EmbeddedBuilder, ) -> Result, WorkspaceError> { let mut embedded_nodes = Vec::new(); - let capabilities = - self.get_file_capabilities(path, settings.experimental_full_html_support_enabled()); - let Some(parse_embedded) = capabilities.parser.parse_embedded_nodes else { + let capabilities = self.get_file_capabilities( + path, + settings.as_ref().experimental_full_html_support_enabled(), + ); + let Some(parse_embedded_nodes) = capabilities.parser.parse_embedded_nodes else { return Ok(Default::default()); }; - let result = parse_embedded(root, path, source, settings, cache); + let result = parse_embedded_nodes(root, path, source, settings, cache, builder); for (mut content, file_source) in result.nodes { let index = self.insert_source(file_source); @@ -651,7 +725,7 @@ impl WorkspaceServer { &self, path: &Utf8Path, content: &str, - settings: &Settings, + settings: &SettingsWithEditor, file_source_index: usize, node_cache: &mut NodeCache, ) -> Result { @@ -769,7 +843,7 @@ impl WorkspaceServer { PathKind::Directory { .. } => { if path.is_dependency() { // Every mode ignores dependencies, except project mode. - return Ok(!scan_kind.is_project()); + return Ok(!scan_kind.is_project() && !scan_kind.is_type_aware()); } if self.projects.is_ignored_by_top_level_config( @@ -813,7 +887,7 @@ impl WorkspaceServer { ) }), }, - ScanKind::Project => { + ScanKind::Project | ScanKind::TypeAware => { if path.is_dependency() { // During the initial scan, we only care about // `package.json` files inside `node_modules`, so that @@ -929,6 +1003,7 @@ impl WorkspaceServer { &self, path: &BiomePath, update_kind: &UpdateKind, + infer_types: bool, ) -> (ModuleDependencies, Vec) { match update_kind { UpdateKind::AddedOrChanged(_, root, services) => { @@ -938,9 +1013,10 @@ impl WorkspaceServer { self.fs.as_ref(), &self.project_layout, &[(path, js_root)], + infer_types, ) } else if let (Some(css_root), Some(services)) = ( - SendNode::into_language_root::(root.clone()), + SendNode::into_language_root::(root.clone()), services.as_css_services(), ) { self.module_graph.update_graph_for_css_paths( @@ -969,13 +1045,22 @@ impl WorkspaceServer { &self, path: &Utf8Path, update_kind: UpdateKind, + project_key: ProjectKey, ) -> Result<(ModuleDependencies, Vec), WorkspaceError> { let path = BiomePath::from(path); if path.is_manifest() { self.update_project_layout(&path, &update_kind)?; } + let settings = self + .projects + .get_settings_based_on_path(project_key, &path) + .ok_or_else(WorkspaceError::no_project)?; - let result = self.update_module_graph_internal(&path, &update_kind); + let result = self.update_module_graph_internal( + &path, + &update_kind, + settings.module_graph_resolution_kind.is_modules_and_types(), + ); match update_kind { UpdateKind::AddedOrChanged(OpenFileReason::Index(IndexTrigger::InitialScan), _, _) => { @@ -1070,6 +1155,7 @@ impl Workspace for WorkspaceServer { configuration, project_key, extended_configurations, + module_graph_resolution_kind, } = params; let mut diagnostics: Vec = vec![]; let workspace_directory = workspace_directory.map(|p| p.to_path_buf()); @@ -1092,6 +1178,7 @@ impl Workspace for WorkspaceServer { .get_root_settings(project_key) .ok_or_else(WorkspaceError::no_project)? }; + settings.module_graph_resolution_kind = module_graph_resolution_kind; let resolution_directory = if extends_root { self.projects.get_project_path(project_key) @@ -1237,14 +1324,17 @@ impl Workspace for WorkspaceServer { ); let capabilities = self.features.get_capabilities(language); + let settings = self.settings_handle(&settings, params.inline_config); self.projects.get_file_features(GetFileFeaturesParams { fs: self.fs.as_ref(), project_key: params.project_key, path: ¶ms.path, - features: params.features, + requested_features: params.features, language, capabilities: &capabilities, + handle: &settings, skip_ignore_check: params.skip_ignore_check, + not_requested_features: params.not_requested_features, }) } @@ -1339,7 +1429,8 @@ impl Workspace for WorkspaceServer { ¶ms.path, settings.experimental_full_html_support_enabled(), ); - + // Currently we don't inject inline configuration for debugging methods, review if we need it + let settings = self.settings_handle(&settings, None); debug_formatter_ir(¶ms.path, &document_file_source, parse, &settings) } @@ -1435,6 +1526,7 @@ impl Workspace for WorkspaceServer { path, content, version, + inline_config, }: ChangeFileParams, ) -> Result { let documents = self.documents.pin(); @@ -1455,6 +1547,7 @@ impl Workspace for WorkspaceServer { .projects .get_settings_based_on_path(project_key, &path) .ok_or_else(WorkspaceError::no_project)?; + let settings_handle = self.settings_handle(&settings, inline_config); // We remove the node cache for the document, if it exists. // This is done so that we need to hold the lock as short as possible @@ -1468,18 +1561,14 @@ impl Workspace for WorkspaceServer { let persist_node_cache = node_cache.is_some(); let mut node_cache = node_cache.unwrap_or_default(); - let parsed = self.parse(&path, &content, &settings, index, &mut node_cache)?; + let parsed = self.parse(&path, &content, &settings_handle, index, &mut node_cache)?; let mut services = DocumentServices::none(); let root = parsed.any_parse.unwrap_as_send_node(); let document_source = self.get_file_source(&path, settings.experimental_full_html_support_enabled()); - if document_source.is_css_like() - && (settings.is_linter_enabled() || settings.is_assist_enabled()) - { - services = CssDocumentServices::default() - .with_css_semantic_model(&parsed.any_parse.tree()) - .into(); - } + + let mut exported_bindings = EmbeddedExportedBindings::default(); + let mut builder = exported_bindings.builder(); // Second-pass parsing for HTML files with embedded JavaScript and CSS content let embedded_snippets = if DocumentFileSource::can_contain_embeds( @@ -1493,12 +1582,55 @@ impl Workspace for WorkspaceServer { &document_source, &parsed.any_parse, &mut node_cache, - &settings, + &settings_handle, + &mut builder, )? } else { vec![] }; + if document_source.is_css_like() + && (settings.is_linter_enabled() || settings.is_assist_enabled()) + { + services = CssDocumentServices::default() + .with_css_semantic_model(&parsed.any_parse.tree()) + .into(); + } + + exported_bindings.finish(builder); + services.set_embedded_bindings(exported_bindings); + + // Track value references from non-source snippets (templates) + let mut value_references = EmbeddedValueReferences::default(); + for snippet in &embedded_snippets { + if let Some(js_snippet) = snippet.as_js_embedded_snippet() { + let Some(file_source) = self.get_source(snippet.file_source_index()) else { + continue; + }; + let Some(js_file_source) = file_source.to_js_file_source() else { + continue; + }; + // Only process non-source snippets (templates) + if !js_file_source.is_embedded_source() { + let mut builder = value_references.builder(); + builder.visit_non_source_snippet(&js_snippet.parse.tree()); + value_references.finish(builder); + } + } + } + + // Also track component element names from HTML templates (Vue/Svelte) + if let Some(html_file_source) = document_source.to_html_file_source() + && html_file_source.supports_components() + { + let html_root: HtmlRoot = parsed.any_parse.tree(); + let mut builder = value_references.builder(); + builder.visit_html_root(&html_root); + value_references.finish(builder); + } + + services.set_embedded_value_references(value_references); + let document = Document { content, version: Some(version), @@ -1525,6 +1657,7 @@ impl Workspace for WorkspaceServer { let (dependencies, diagnostics) = self.update_service_data( &path, UpdateKind::AddedOrChanged(OpenFileReason::ClientRequest, root, services), + project_key, )?; final_diagnostics.extend( diagnostics @@ -1575,6 +1708,7 @@ impl Workspace for WorkspaceServer { skip, enabled_rules, pull_code_actions, + inline_config, } = params; let settings = self .projects @@ -1599,6 +1733,7 @@ impl Workspace for WorkspaceServer { } else { Vec::new() }; + let settings = self.settings_handle(&settings, inline_config); let results = lint(LintParams { parse, settings: &settings, @@ -1615,6 +1750,7 @@ impl Workspace for WorkspaceServer { plugins: plugins.clone(), diagnostic_offset: None, document_services: &services, + snippet_services: None, }); let LintResults { @@ -1622,7 +1758,7 @@ impl Workspace for WorkspaceServer { mut errors, mut skipped_diagnostics, } = results; - for embedded_node in embedded_snippets { + for embedded_node in &embedded_snippets { let Some(file_source) = self.get_source(embedded_node.file_source_index()) else { continue; }; @@ -1630,7 +1766,7 @@ impl Workspace for WorkspaceServer { let Some(lint) = capabilities.analyzer.lint else { continue; }; - let services = embedded_node.as_snippet_services(); + let snippet_services = embedded_node.as_snippet_services(); let results = lint(LintParams { parse: embedded_node.parse().clone(), @@ -1647,7 +1783,8 @@ impl Workspace for WorkspaceServer { pull_code_actions, plugins: plugins.clone(), diagnostic_offset: Some(embedded_node.content_offset()), - document_services: services, + document_services: &services, + snippet_services: Some(snippet_services), }); diagnostics.extend(results.diagnostics); @@ -1705,6 +1842,7 @@ impl Workspace for WorkspaceServer { only, skip, enabled_rules, + inline_config, } = params; let settings = self .projects @@ -1728,9 +1866,10 @@ impl Workspace for WorkspaceServer { } else { Vec::new() }; + let handle = self.settings_handle(&settings, inline_config); let mut final_result = pull_diagnostics_and_actions(DiagnosticsAndActionsParams { parse, - settings: &settings, + settings: &handle, path: &path, only: &only, skip: &skip, @@ -1758,7 +1897,7 @@ impl Workspace for WorkspaceServer { let snippet_result = pull_diagnostics_and_actions(DiagnosticsAndActionsParams { parse: embedded_node.parse().clone(), - settings: &settings, + settings: &handle, path: &path, only: &only, skip: &skip, @@ -1808,6 +1947,7 @@ impl Workspace for WorkspaceServer { skip, enabled_rules, categories, + inline_config, } = params; let settings = self .projects @@ -1824,6 +1964,8 @@ impl Workspace for WorkspaceServer { self.get_parse_with_snippets_and_services(&path)?; let language = self.get_file_source(&path, settings.experimental_full_html_support_enabled()); + let settings = self.settings_handle(&settings, inline_config); + let mut result = code_actions(CodeActionsParams { parse, range, @@ -1913,6 +2055,8 @@ impl Workspace for WorkspaceServer { ¶ms.path, settings.experimental_full_html_support_enabled(), ); + let settings = self.settings_handle(&settings, params.inline_config); + if !embedded_nodes.is_empty() { let format_embedded = format_embedded.ok_or_else(self.build_capability_error(¶ms.path))?; @@ -1951,6 +2095,7 @@ impl Workspace for WorkspaceServer { ¶ms.path, settings.experimental_full_html_support_enabled(), ); + let settings = self.settings_handle(&settings, params.inline_config); format_range( ¶ms.path, &document_file_source, @@ -1985,7 +2130,7 @@ impl Workspace for WorkspaceServer { ¶ms.path, settings.experimental_full_html_support_enabled(), ); - + let settings = self.settings_handle(&settings, params.inline_config); format_on_type( ¶ms.path, &document_file_source, @@ -2017,6 +2162,7 @@ impl Workspace for WorkspaceServer { enabled_rules, rule_categories, suppression_reason, + inline_config, } = params; let settings = self @@ -2051,6 +2197,7 @@ impl Workspace for WorkspaceServer { let mut errors = 0; let mut actions = vec![]; let mut skipped_suggested_fixes = 0; + let settings = self.settings_handle(&settings, inline_config); if let Some(update_snippets) = capabilities.analyzer.update_snippets { let mut new_snippets = vec![]; @@ -2172,6 +2319,10 @@ impl Workspace for WorkspaceServer { fn update_module_graph(&self, params: UpdateModuleGraphParams) -> Result<(), WorkspaceError> { let (parsed, services) = self.get_parse_and_services(params.path.as_path())?; + let settings = self + .projects + .get_settings_based_on_path(params.project_key, ¶ms.path) + .ok_or_else(WorkspaceError::no_project)?; let update_kind = match params.update_kind { super::UpdateKind::AddOrUpdate => UpdateKind::AddedOrChanged( OpenFileReason::ClientRequest, @@ -2181,7 +2332,11 @@ impl Workspace for WorkspaceServer { super::UpdateKind::Remove => UpdateKind::Removed, }; - self.update_module_graph_internal(¶ms.path, &update_kind); + self.update_module_graph_internal( + ¶ms.path, + &update_kind, + settings.module_graph_resolution_kind.is_modules_and_types(), + ); Ok(()) } @@ -2233,6 +2388,7 @@ impl Workspace for WorkspaceServer { let document_file_source = self.get_file_source(&path, settings.experimental_full_html_support_enabled()); + let settings = self.settings_handle(&settings, None); let matches = search(&path, &document_file_source, parse, query, &settings)?; Ok(SearchResults { path, matches }) @@ -2322,6 +2478,8 @@ impl WorkspaceScannerBridge for WorkspaceServer { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + // TODO: review here, it feels wrong that we can't pass the inline config + inline_config: None, }, ) .map(|result| (result.dependencies, result.diagnostics)) @@ -2400,6 +2558,8 @@ impl WorkspaceScannerBridge for WorkspaceServer { nested_configuration }; + let scan_kind = ProjectScanComputer::new(&nested_configuration).compute(); + let result = self.update_settings(UpdateSettingsParams { project_key, workspace_directory: nested_directory_path.map(BiomePath::from), @@ -2408,6 +2568,7 @@ impl WorkspaceScannerBridge for WorkspaceServer { .into_iter() .map(|(path, config)| (BiomePath::from(path), config)) .collect(), + module_graph_resolution_kind: ModuleGraphResolutionKind::from(&scan_kind), })?; returned_diagnostics.extend(result.diagnostics) @@ -2466,8 +2627,9 @@ impl WorkspaceScannerBridge for WorkspaceServer { fn unload_file( &self, path: &Utf8Path, + project_key: ProjectKey, ) -> Result, WorkspaceError> { - self.update_service_data(path, UpdateKind::Removed) + self.update_service_data(path, UpdateKind::Removed, project_key) .map(|(_, diagnostics)| { diagnostics .into_iter() @@ -2479,6 +2641,7 @@ impl WorkspaceScannerBridge for WorkspaceServer { fn unload_path( &self, path: &Utf8Path, + project_key: ProjectKey, ) -> Result, WorkspaceError> { // Note that we cannot check the kind of the path, because the watcher // would only attempt to unload a file or folder after it has been @@ -2492,7 +2655,7 @@ impl WorkspaceScannerBridge for WorkspaceServer { self.project_layout.unload_folder(path); // Finally unloads the path itself. - self.unload_file(path) + self.unload_file(path, project_key) } } @@ -2528,7 +2691,7 @@ pub enum UpdateKind { } impl Debug for UpdateKind { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::AddedOrChanged(reason, _, _) => { f.debug_tuple("AddedOrChanged").field(reason).finish() diff --git a/crates/biome_service/src/workspace/server.tests.rs b/crates/biome_service/src/workspace/server.tests.rs index c617463aa8e6..c9b69f6a3407 100644 --- a/crates/biome_service/src/workspace/server.tests.rs +++ b/crates/biome_service/src/workspace/server.tests.rs @@ -1,3 +1,7 @@ +use super::*; +use crate::settings::ModuleGraphResolutionKind; +use crate::test_utils::setup_workspace_and_open_project; +use crate::workspace::FeatureName; use biome_configuration::{ FormatterConfiguration, JsConfiguration, javascript::{JsFormatterConfiguration, JsParserConfiguration}, @@ -8,10 +12,6 @@ use biome_fs::MemoryFileSystem; use biome_json_parser::JsonParserOptions; use biome_rowan::TextSize; -use crate::test_utils::setup_workspace_and_open_project; - -use super::*; - #[test] fn commonjs_file_rejects_import_statement() { const FILE_CONTENT: &[u8] = b"import 'foo';"; @@ -40,6 +40,7 @@ fn commonjs_file_rejects_import_statement() { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -93,6 +94,7 @@ fn store_embedded_nodes_with_current_ranges() { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -154,6 +156,7 @@ fn format_html_with_scripts_and_css() { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -161,6 +164,7 @@ fn format_html_with_scripts_and_css() { .format_file(FormatFileParams { path: Utf8PathBuf::from("/project/file.html").into(), project_key, + inline_config: None, }) .unwrap(); @@ -241,6 +245,7 @@ function Foo({cond}) { configuration, workspace_directory: Some(BiomePath::new("/project")), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::None, }) .unwrap(); @@ -261,6 +266,7 @@ function Foo({cond}) { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -271,6 +277,7 @@ function Foo({cond}) { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -294,6 +301,7 @@ function Foo({cond}) { match workspace.format_file(FormatFileParams { project_key, path: BiomePath::new("/project/a.js"), + inline_config: None, }) { Ok(printed) => { insta::assert_snapshot!(printed.as_code(), @r###" @@ -354,6 +362,7 @@ function Foo({cond}) { configuration, workspace_directory: Some(BiomePath::new("/project")), extended_configurations: Default::default(), + module_graph_resolution_kind: ModuleGraphResolutionKind::None, }) .unwrap(); @@ -374,6 +383,7 @@ function Foo({cond}) { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -384,6 +394,7 @@ function Foo({cond}) { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -407,6 +418,7 @@ function Foo({cond}) { match workspace.format_file(FormatFileParams { project_key, path: BiomePath::new("/project/a.jsx"), + inline_config: None, }) { Ok(printed) => { insta::assert_snapshot!(printed.as_code(), @r###" @@ -449,6 +461,7 @@ fn pull_diagnostics_and_actions_for_js_file() { content: FileContent::FromServer, document_file_source: None, persist_node_cache: false, + inline_config: None, }) .unwrap(); @@ -460,6 +473,7 @@ fn pull_diagnostics_and_actions_for_js_file() { enabled_rules: vec![], project_key, categories: Default::default(), + inline_config: None, }) .unwrap(); @@ -474,6 +488,152 @@ fn pull_diagnostics_and_actions_for_js_file() { insta::assert_debug_snapshot!(result) } +#[test] +fn format_js_with_embedded_css() { + const FILE_PATH: &str = "/project/file.js"; + const FILE_CONTENT: &str = r#"const Foo = styled.div` + display: + flex; + color : red ; +`; + +const Bar = styled(Component)` + display: + flex; + color : red ; +`;"#; + + let fs = MemoryFileSystem::default(); + fs.insert(Utf8PathBuf::from(FILE_PATH), FILE_CONTENT); + + let (workspace, project_key) = setup_workspace_and_open_project(fs, "/"); + + workspace + .update_settings(UpdateSettingsParams { + project_key, + workspace_directory: None, + configuration: Configuration { + javascript: Some(JsConfiguration { + experimental_embedded_snippets_enabled: Some(true.into()), + ..Default::default() + }), + ..Default::default() + }, + extended_configurations: vec![], + module_graph_resolution_kind: ModuleGraphResolutionKind::None, + }) + .unwrap(); + + workspace + .open_file(OpenFileParams { + project_key, + path: BiomePath::new(FILE_PATH), + content: FileContent::FromServer, + document_file_source: None, + persist_node_cache: false, + inline_config: None, + }) + .unwrap(); + + let result = workspace + .format_file(FormatFileParams { + project_key, + path: Utf8PathBuf::from(FILE_PATH).into(), + inline_config: None, + }) + .unwrap(); + + insta::assert_snapshot!(result.as_code(), @r" + const Foo = styled.div` + display: flex; + color: red; + `; + + const Bar = styled(Component)` + display: flex; + color: red; + `; + "); +} + +#[test] +fn format_js_with_embedded_graphql() { + const FILE_PATH: &str = "/project/file.js"; + const FILE_CONTENT: &str = r#"const Foo = gql` + query PeopleCount { + people( + id: $peopleId){ + totalCount + }} +`; + +const Bar = graphql(` + query PeopleCount { + people( + id: $peopleId){ + totalCount + }} +`);"#; + + let fs = MemoryFileSystem::default(); + fs.insert(Utf8PathBuf::from(FILE_PATH), FILE_CONTENT); + + let (workspace, project_key) = setup_workspace_and_open_project(fs, "/"); + + workspace + .update_settings(UpdateSettingsParams { + project_key, + workspace_directory: None, + configuration: Configuration { + javascript: Some(JsConfiguration { + experimental_embedded_snippets_enabled: Some(true.into()), + ..Default::default() + }), + ..Default::default() + }, + extended_configurations: vec![], + module_graph_resolution_kind: ModuleGraphResolutionKind::None, + }) + .unwrap(); + + workspace + .open_file(OpenFileParams { + project_key, + path: BiomePath::new(FILE_PATH), + content: FileContent::FromServer, + document_file_source: None, + persist_node_cache: false, + inline_config: None, + }) + .unwrap(); + + let result = workspace + .format_file(FormatFileParams { + project_key, + path: Utf8PathBuf::from(FILE_PATH).into(), + inline_config: None, + }) + .unwrap(); + + insta::assert_snapshot!(result.as_code(), @r" + const Foo = gql` + query PeopleCount { + people(id: $peopleId) { + totalCount + } + } + `; + + const Bar = graphql(` + query PeopleCount { + people(id: $peopleId) { + totalCount + } + } + `); + "); +} + #[test] fn extends_root_resolves_globs_from_project_root() { const NESTED_CONFIGURATION: &str = r#" @@ -511,6 +671,7 @@ fn extends_root_resolves_globs_from_project_root() { configuration, workspace_directory: Some(BiomePath::new("/project/packages/pkg-a")), extended_configurations: Default::default(), + module_graph_resolution_kind: Default::default(), }) .unwrap(); diff --git a/crates/biome_string_case/Cargo.toml b/crates/biome_string_case/Cargo.toml index 9152b3b4f490..8f0deeff739b 100644 --- a/crates/biome_string_case/Cargo.toml +++ b/crates/biome_string_case/Cargo.toml @@ -13,6 +13,7 @@ publish = true [dependencies] biome_rowan = { workspace = true, optional = true } +caseless = "0.2.2" [features] biome_rowan = ["dep:biome_rowan"] diff --git a/crates/biome_string_case/src/lib.rs b/crates/biome_string_case/src/lib.rs index cd3059289e01..f9bb07de46cd 100644 --- a/crates/biome_string_case/src/lib.rs +++ b/crates/biome_string_case/src/lib.rs @@ -4,6 +4,8 @@ use std::{borrow::Cow, cmp::Ordering, ffi::OsStr}; +use caseless::Caseless; + #[cfg(feature = "biome_rowan")] pub mod comparable_token; @@ -664,6 +666,8 @@ pub trait StrOnlyExtension: ToOwned { /// is that this functions returns ```Cow``` and does not allocate /// if the string is already in lowercase. fn to_lowercase_cow(&self) -> Cow<'_, Self>; + /// Returns Unicode case-folded text as a Cow, allocating only when needed. + fn to_casefold_cow(&self) -> Cow<'_, Self>; } impl StrLikeExtension for str { @@ -696,6 +700,26 @@ impl StrOnlyExtension for str { Cow::Borrowed(self) } } + + fn to_casefold_cow(&self) -> Cow<'_, Self> { + // Fast path: check if case folding changes anything without allocating + let mut original = self.chars(); + let mut folded = self.chars().default_case_fold(); + + let differs = loop { + match (original.next(), folded.next()) { + (None, None) => break false, + (Some(o), Some(f)) if o == f => {} + _ => break true, + } + }; + + if differs { + Cow::Owned(caseless::default_case_fold_str(self)) + } else { + Cow::Borrowed(self) + } + } } impl StrLikeExtension for std::ffi::OsStr { @@ -1183,6 +1207,13 @@ mod tests { assert!(matches!("tešt".to_lowercase_cow(), Cow::Borrowed(_))); } + #[test] + fn to_casefold_cow() { + assert_eq!("ss", "ẞ".to_casefold_cow()); + assert_eq!("ss", "ß".to_casefold_cow()); + assert!(matches!("test".to_casefold_cow(), Cow::Borrowed(_))); + } + #[test] fn collation_weight_unique() { for weight in 0..=255 { diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-property.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-property.txt.snap index 76daae2f54a8..50dc28690fa3 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-property.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-property.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-value-in-arbitrary.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-value-in-arbitrary.txt.snap index 8ae24fb9d3f5..59063cc80aa6 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-value-in-arbitrary.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/arbitrary-candidate/missing-value-in-arbitrary.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-0.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-0.txt.snap index 83a96f4afdd7..b91985629b57 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-0.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-0.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-1.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-1.txt.snap index a7c1117c1011..3d041ea2e2a4 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-1.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-1.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-2.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-2.txt.snap index 5e0422988d0e..b787c953170b 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-2.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-value-2.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-variant.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-variant.txt.snap index a21f6f695a98..4b4ed9e5a927 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-variant.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/incomplete-arbitrary-variant.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value-1.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value-1.txt.snap index 29dd22e1a959..94d428676538 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value-1.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value-1.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value.txt.snap index 4c2cc86c23ae..5ad70d5b60b5 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-modifier-value.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-value.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-value.txt.snap index d1b85ed595f1..9158aa34a88b 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-value.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/error/missing-value.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/gradient.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/gradient.txt.snap index 6d46ad851de8..11697a5d3317 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/gradient.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/gradient.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/image-url.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/image-url.txt.snap index ec248a124ecf..5d1b643ffaff 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/image-url.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/image-url.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/inset.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/inset.txt.snap index 4e6b7de58a68..e085f1b31d4f 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/inset.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/inset.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/misc-xy.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/misc-xy.txt.snap index 4f264363f729..d0f90365d5d3 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/misc-xy.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/misc-xy.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/shadow.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/shadow.txt.snap index 2e32374de6f5..5f05fd741822 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/shadow.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/brackets/shadow.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-0.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-0.txt.snap index 2659d8844367..0b072b100375 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-0.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-0.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-1.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-1.txt.snap index 0ced80e9072e..0016e36f39b8 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-1.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-1.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-2.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-2.txt.snap index d6355bc32848..2d1ba32f77ee 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-2.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-2.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-3.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-3.txt.snap index 669a103932ed..bbcc30e922c1 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-3.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/candidates/arbitrary-candidate-3.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/data-attribute.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/data-attribute.txt.snap index d8c4b919a0dc..aca20f062124 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/data-attribute.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/data-attribute.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/precise-control.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/precise-control.txt.snap index a6a115ac0b98..93b68d0ee7a0 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/precise-control.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/precise-control.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/simple.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/simple.txt.snap index 72602980c5ac..54d81e0978dd 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/simple.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/gradients/simple.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/group-data.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/group-data.txt.snap index e9b3bd13695c..4a7c16881a71 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/group-data.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/group-data.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/2-classes.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/2-classes.txt.snap index 53f55a93105b..4dfe86d76a5b 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/2-classes.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/2-classes.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-0.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-0.txt.snap index 0a73ff5646d9..4d053cf5dffa 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-0.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-0.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-1.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-1.txt.snap index e0bd95a96c70..2160de105537 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-1.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/arbitrary-value-1.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-0.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-0.txt.snap index 1d7732ba845c..832fd2371b6e 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-0.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-0.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-1.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-1.txt.snap index 1f86a12a71c4..66197b44d0fd 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-1.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-1.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-2.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-2.txt.snap index 92a3cab79a12..6fc8fa0c0b62 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-2.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/base-has-dash-2.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-0.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-0.txt.snap index 29951445d20f..2c6d06d76536 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-0.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-0.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-1.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-1.txt.snap index 74b4bf5d8ca5..d0a2815c0b2c 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-1.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-1.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-2.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-2.txt.snap index 4d8fff716707..846176ebe9d3 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-2.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/basic-2.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/border.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/border.txt.snap index 64926f38808d..b5be6492867b 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/border.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/border.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/css-value.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/css-value.txt.snap index f78c9413de41..70d52897d564 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/css-value.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/css-value.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/empty.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/empty.txt.snap index 57862b37f27f..bf2cc46d62a8 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/empty.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/empty.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/important.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/important.txt.snap index a4042a11ab56..36c587d8e34d 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/important.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/important.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/modifier.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/modifier.txt.snap index 847c4bf4357b..8f45f0ea1777 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/modifier.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/modifier.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple-spaces.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple-spaces.txt.snap index aadffeb245d3..8b6aba15bafb 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple-spaces.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple-spaces.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple.txt.snap index d1288b290933..8972336b7803 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/multiple.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/negative.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/negative.txt.snap index 5e1cb6096561..1f794272245e 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/negative.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/negative.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/static.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/static.txt.snap index 030e153550de..5f11c1aeecad 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/static.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/simple/static.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-1.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-1.txt.snap index 79aa7e6189fa..4396734abc0c 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-1.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-1.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-2.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-2.txt.snap index fa9a83622d8e..b05899ab9962 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-2.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/stress/stress-2.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-variant.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-variant.txt.snap index a8c6e2d97b8b..3092616a4c07 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-variant.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/arbitrary-variant.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-arbirary-param.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-arbirary-param.txt.snap index 987ed10cfcf8..1d0bbd8966d6 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-arbirary-param.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-arbirary-param.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-named-param.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-named-param.txt.snap index ad086ddb1700..e7f6ba707297 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-named-param.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/functional-named-param.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover.txt.snap index 224684d92dd5..75873b15e8da 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover_focus.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover_focus.txt.snap index 6ed352220785..d83150a394c4 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover_focus.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/hover_focus.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/starts-with-number.txt.snap b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/starts-with-number.txt.snap index a441060349ae..440b3397668a 100644 --- a/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/starts-with-number.txt.snap +++ b/crates/biome_tailwind_parser/tests/tailwind_specs/ok/variants/starts-with-number.txt.snap @@ -2,6 +2,7 @@ source: crates/biome_tailwind_parser/tests/spec_test.rs expression: snapshot --- + ## Input ```text diff --git a/crates/biome_test_utils/src/lib.rs b/crates/biome_test_utils/src/lib.rs index 0c68d61d14fb..0b535e8d2d7a 100644 --- a/crates/biome_test_utils/src/lib.rs +++ b/crates/biome_test_utils/src/lib.rs @@ -1,39 +1,37 @@ #![deny(clippy::use_self)] -use std::ffi::c_int; -use std::fmt::Write; -use std::sync::{Arc, Once}; +mod bench_case; +pub use bench_case::BenchCase; use biome_analyze::options::{JsxRuntime, PreferredQuote}; use biome_analyze::{AnalyzerAction, AnalyzerConfiguration, AnalyzerOptions}; use biome_configuration::{Configuration, ConfigurationPathHint}; use biome_console::fmt::{Formatter, Termcolor}; use biome_console::markup; +use biome_css_parser::CssParserOptions; +use biome_css_syntax::AnyCssRoot; use biome_diagnostics::termcolor::Buffer; use biome_diagnostics::{DiagnosticExt, Error, PrintDiagnostic}; use biome_fs::{BiomePath, FileSystem, OsFileSystem}; -use biome_js_parser::{AnyJsRoot, JsFileSource, JsParserOptions}; +use biome_js_parser::{AnyJsRoot, JsParserOptions}; use biome_js_type_info::{TypeData, TypeResolver}; use biome_json_parser::ParseDiagnostic; use biome_module_graph::ModuleGraph; use biome_package::{Manifest, PackageJson, TsConfigJson, TurboJson}; use biome_project_layout::ProjectLayout; use biome_rowan::{Direction, Language, SyntaxKind, SyntaxNode, SyntaxSlot}; +use biome_service::WorkspaceError; +use biome_service::configuration::{LoadedConfiguration, load_configuration}; use biome_service::file_handlers::DocumentFileSource; use biome_service::projects::Projects; -use biome_service::settings::{ServiceLanguage, Settings}; +use biome_service::settings::{ServiceLanguage, Settings, SettingsHandle}; use biome_string_case::StrLikeExtension; use camino::{Utf8Path, Utf8PathBuf}; use json_comments::StripComments; use similar::{DiffableStr, TextDiff}; - -mod bench_case; - -pub use bench_case::BenchCase; -use biome_css_parser::CssParserOptions; -use biome_css_syntax::CssRoot; -use biome_service::WorkspaceError; -use biome_service::configuration::{LoadedConfiguration, load_configuration}; +use std::ffi::c_int; +use std::fmt::Write; +use std::sync::{Arc, Once}; pub fn scripts_from_json(extension: &str, input_code: &str) -> Option> { if extension == "json" || extension == "jsonc" { @@ -136,6 +134,54 @@ pub fn load_configuration_for_test_file( } } +pub fn create_parser_options( + input_file: &Utf8Path, + diagnostics: &mut Vec, +) -> Option { + let Ok((source, loaded_configuration)) = load_configuration_for_test_file(input_file) else { + return None; + }; + + let projects = Projects::default(); + let key = projects.insert_project(Utf8PathBuf::from("")); + + if loaded_configuration.has_errors() { + let configuration_path = loaded_configuration.file_path.unwrap().clone(); + diagnostics.extend( + loaded_configuration + .diagnostics + .into_iter() + .map(|diagnostic| { + diagnostic_to_string( + configuration_path.file_stem().unwrap(), + &source, + diagnostic, + ) + }) + .collect::>(), + ); + + Default::default() + } else { + let configuration = loaded_configuration.configuration; + let mut settings = projects.get_root_settings(key).unwrap_or_default(); + settings + .merge_with_configuration( + configuration, + None, + loaded_configuration.extended_configurations, + ) + .unwrap(); + + let document_file_source = DocumentFileSource::from_path( + input_file, + settings.experimental_full_html_support_enabled(), + ); + let handle = SettingsHandle::new(&settings, None); + Some(handle.parse_options::(&input_file.into(), &document_file_source)) + } +} + pub fn create_formatting_options( input_file: &Utf8Path, diagnostics: &mut Vec, @@ -181,7 +227,8 @@ where input_file, settings.experimental_full_html_support_enabled(), ); - settings.format_options::(&input_file.into(), &document_file_source) + let handle = SettingsHandle::new(&settings, None); + handle.format_options::(&input_file.into(), &document_file_source) } } @@ -202,23 +249,28 @@ pub fn module_graph_for_test_file( let dir = input_file.parent().unwrap().to_path_buf(); let paths = get_js_like_paths_in_dir(&dir); let fs = OsFileSystem::new(dir); - let paths = get_added_paths(&fs, &paths); + let paths = get_added_js_paths(&fs, &paths); - module_graph.update_graph_for_js_paths(&fs, project_layout, &paths); + module_graph.update_graph_for_js_paths(&fs, project_layout, &paths, true); Arc::new(module_graph) } /// Loads and parses files from the file system to pass them to service methods. -pub fn get_added_paths<'a>( +pub fn get_added_js_paths<'a>( fs: &dyn FileSystem, paths: &'a [BiomePath], ) -> Vec<(&'a BiomePath, AnyJsRoot)> { paths .iter() .filter_map(|path| { + let DocumentFileSource::Js(file_source) = + DocumentFileSource::from_path(path.as_path(), false) + else { + return None; + }; + let root = fs.read_file_from_path(path).ok().and_then(|content| { - let file_source = JsFileSource::try_from(path.as_path()).unwrap_or_default(); let parsed = biome_js_parser::parse(&content, file_source, JsParserOptions::default()); let diagnostics = parsed.diagnostics(); @@ -237,12 +289,18 @@ pub fn get_added_paths<'a>( pub fn get_css_added_paths<'a>( fs: &dyn FileSystem, paths: &'a [BiomePath], -) -> Vec<(&'a BiomePath, CssRoot)> { +) -> Vec<(&'a BiomePath, AnyCssRoot)> { paths .iter() .filter_map(|path| { + let DocumentFileSource::Css(file_source) = + DocumentFileSource::from_path(path.as_path(), false) + else { + return None; + }; let root = fs.read_file_from_path(path).ok().map(|content| { - let parsed = biome_css_parser::parse_css(&content, CssParserOptions::default()); + let parsed = + biome_css_parser::parse_css(&content, file_source, CssParserOptions::default()); let diagnostics = parsed.diagnostics(); assert!( diagnostics.is_empty(), diff --git a/crates/biome_unicode_table/src/lib.rs b/crates/biome_unicode_table/src/lib.rs index 3d2c3a82cd4c..9a5495ebc4af 100644 --- a/crates/biome_unicode_table/src/lib.rs +++ b/crates/biome_unicode_table/src/lib.rs @@ -4,9 +4,11 @@ use crate::bytes::DISPATCHER; use crate::tables::derived_property::{ID_Continue, ID_Start}; mod bytes; +mod punctuation; mod tables; pub use crate::bytes::Dispatch; +pub use crate::punctuation::is_unicode_punctuation; /// Tests if `c` is a valid start of a CSS identifier #[inline] diff --git a/crates/biome_unicode_table/src/punctuation.rs b/crates/biome_unicode_table/src/punctuation.rs new file mode 100644 index 000000000000..baee42b75d21 --- /dev/null +++ b/crates/biome_unicode_table/src/punctuation.rs @@ -0,0 +1,416 @@ +//! CommonMark Unicode punctuation table. +//! +//! Derived from the markdown-rs Unicode punctuation list used for CommonMark. +//! Per CommonMark, "Unicode punctuation" includes characters from both the +//! General_Category=Punctuation (P*) and General_Category=Symbol (S*) categories. +//! This is used for CommonMark flanking rules in emphasis parsing. + +// Note: duplicated from generated unicode tables to keep this module standalone. +#[inline] +fn bsearch_range_table(c: char, r: &[(char, char)]) -> bool { + use core::cmp::Ordering::{Equal, Greater, Less}; + r.binary_search_by(|&(lo, hi)| { + if lo > c { + Greater + } else if hi < c { + Less + } else { + Equal + } + }) + .is_ok() +} + +// Ranges are Unicode code points written as `\u{...}`. To translate them into +// human-readable names, look them up in the Unicode charts: +// https://www.unicode.org/charts/ +const PUNCTUATION_RANGES: &[(char, char)] = &[ + ('\u{0021}', '\u{002F}'), + ('\u{003A}', '\u{0040}'), + ('\u{005B}', '\u{0060}'), + ('\u{007B}', '\u{007E}'), + ('\u{00A1}', '\u{00A9}'), + ('\u{00AB}', '\u{00AC}'), + ('\u{00AE}', '\u{00B1}'), + ('\u{00B4}', '\u{00B4}'), + ('\u{00B6}', '\u{00B8}'), + ('\u{00BB}', '\u{00BB}'), + ('\u{00BF}', '\u{00BF}'), + ('\u{00D7}', '\u{00D7}'), + ('\u{00F7}', '\u{00F7}'), + ('\u{02C2}', '\u{02C5}'), + ('\u{02D2}', '\u{02DF}'), + ('\u{02E5}', '\u{02EB}'), + ('\u{02ED}', '\u{02ED}'), + ('\u{02EF}', '\u{02FF}'), + ('\u{0375}', '\u{0375}'), + ('\u{037E}', '\u{037E}'), + ('\u{0384}', '\u{0385}'), + ('\u{0387}', '\u{0387}'), + ('\u{03F6}', '\u{03F6}'), + ('\u{0482}', '\u{0482}'), + ('\u{055A}', '\u{055F}'), + ('\u{0589}', '\u{058A}'), + ('\u{058D}', '\u{058F}'), + ('\u{05BE}', '\u{05BE}'), + ('\u{05C0}', '\u{05C0}'), + ('\u{05C3}', '\u{05C3}'), + ('\u{05C6}', '\u{05C6}'), + ('\u{05F3}', '\u{05F4}'), + ('\u{0606}', '\u{060F}'), + ('\u{061B}', '\u{061B}'), + ('\u{061D}', '\u{061F}'), + ('\u{066A}', '\u{066D}'), + ('\u{06D4}', '\u{06D4}'), + ('\u{06DE}', '\u{06DE}'), + ('\u{06E9}', '\u{06E9}'), + ('\u{06FD}', '\u{06FE}'), + ('\u{0700}', '\u{070D}'), + ('\u{07F6}', '\u{07F9}'), + ('\u{07FE}', '\u{07FF}'), + ('\u{0830}', '\u{083E}'), + ('\u{085E}', '\u{085E}'), + ('\u{0888}', '\u{0888}'), + ('\u{0964}', '\u{0965}'), + ('\u{0970}', '\u{0970}'), + ('\u{09F2}', '\u{09F3}'), + ('\u{09FA}', '\u{09FB}'), + ('\u{09FD}', '\u{09FD}'), + ('\u{0A76}', '\u{0A76}'), + ('\u{0AF0}', '\u{0AF1}'), + ('\u{0B70}', '\u{0B70}'), + ('\u{0BF3}', '\u{0BFA}'), + ('\u{0C77}', '\u{0C77}'), + ('\u{0C7F}', '\u{0C7F}'), + ('\u{0C84}', '\u{0C84}'), + ('\u{0D4F}', '\u{0D4F}'), + ('\u{0D79}', '\u{0D79}'), + ('\u{0DF4}', '\u{0DF4}'), + ('\u{0E3F}', '\u{0E3F}'), + ('\u{0E4F}', '\u{0E4F}'), + ('\u{0E5A}', '\u{0E5B}'), + ('\u{0F01}', '\u{0F17}'), + ('\u{0F1A}', '\u{0F1F}'), + ('\u{0F34}', '\u{0F34}'), + ('\u{0F36}', '\u{0F36}'), + ('\u{0F38}', '\u{0F38}'), + ('\u{0F3A}', '\u{0F3D}'), + ('\u{0F85}', '\u{0F85}'), + ('\u{0FBE}', '\u{0FC5}'), + ('\u{0FC7}', '\u{0FCC}'), + ('\u{0FCE}', '\u{0FDA}'), + ('\u{104A}', '\u{104F}'), + ('\u{109E}', '\u{109F}'), + ('\u{10FB}', '\u{10FB}'), + ('\u{1360}', '\u{1368}'), + ('\u{1390}', '\u{1399}'), + ('\u{1400}', '\u{1400}'), + ('\u{166D}', '\u{166E}'), + ('\u{169B}', '\u{169C}'), + ('\u{16EB}', '\u{16ED}'), + ('\u{1735}', '\u{1736}'), + ('\u{17D4}', '\u{17D6}'), + ('\u{17D8}', '\u{17DB}'), + ('\u{1800}', '\u{180A}'), + ('\u{1940}', '\u{1940}'), + ('\u{1944}', '\u{1945}'), + ('\u{19DE}', '\u{19FF}'), + ('\u{1A1E}', '\u{1A1F}'), + ('\u{1AA0}', '\u{1AA6}'), + ('\u{1AA8}', '\u{1AAD}'), + ('\u{1B4E}', '\u{1B4F}'), + ('\u{1B5A}', '\u{1B6A}'), + ('\u{1B74}', '\u{1B7F}'), + ('\u{1BFC}', '\u{1BFF}'), + ('\u{1C3B}', '\u{1C3F}'), + ('\u{1C7E}', '\u{1C7F}'), + ('\u{1CC0}', '\u{1CC7}'), + ('\u{1CD3}', '\u{1CD3}'), + ('\u{1FBD}', '\u{1FBD}'), + ('\u{1FBF}', '\u{1FC1}'), + ('\u{1FCD}', '\u{1FCF}'), + ('\u{1FDD}', '\u{1FDF}'), + ('\u{1FED}', '\u{1FEF}'), + ('\u{1FFD}', '\u{1FFE}'), + ('\u{2010}', '\u{2027}'), + ('\u{2030}', '\u{205E}'), + ('\u{207A}', '\u{207E}'), + ('\u{208A}', '\u{208E}'), + ('\u{20A0}', '\u{20C0}'), + ('\u{2100}', '\u{2101}'), + ('\u{2103}', '\u{2106}'), + ('\u{2108}', '\u{2109}'), + ('\u{2114}', '\u{2114}'), + ('\u{2116}', '\u{2118}'), + ('\u{211E}', '\u{2123}'), + ('\u{2125}', '\u{2125}'), + ('\u{2127}', '\u{2127}'), + ('\u{2129}', '\u{2129}'), + ('\u{212E}', '\u{212E}'), + ('\u{213A}', '\u{213B}'), + ('\u{2140}', '\u{2144}'), + ('\u{214A}', '\u{214D}'), + ('\u{214F}', '\u{214F}'), + ('\u{218A}', '\u{218B}'), + ('\u{2190}', '\u{2429}'), + ('\u{2440}', '\u{244A}'), + ('\u{249C}', '\u{24E9}'), + ('\u{2500}', '\u{2775}'), + ('\u{2794}', '\u{2B73}'), + ('\u{2B76}', '\u{2B95}'), + ('\u{2B97}', '\u{2BFF}'), + ('\u{2CE5}', '\u{2CEA}'), + ('\u{2CF9}', '\u{2CFC}'), + ('\u{2CFE}', '\u{2CFF}'), + ('\u{2D70}', '\u{2D70}'), + ('\u{2E00}', '\u{2E2E}'), + ('\u{2E30}', '\u{2E5D}'), + ('\u{2E80}', '\u{2E99}'), + ('\u{2E9B}', '\u{2EF3}'), + ('\u{2F00}', '\u{2FD5}'), + ('\u{2FF0}', '\u{2FFF}'), + ('\u{3001}', '\u{3004}'), + ('\u{3008}', '\u{3020}'), + ('\u{3030}', '\u{3030}'), + ('\u{3036}', '\u{3037}'), + ('\u{303D}', '\u{303F}'), + ('\u{309B}', '\u{309C}'), + ('\u{30A0}', '\u{30A0}'), + ('\u{30FB}', '\u{30FB}'), + ('\u{3190}', '\u{3191}'), + ('\u{3196}', '\u{319F}'), + ('\u{31C0}', '\u{31E5}'), + ('\u{31EF}', '\u{31EF}'), + ('\u{3200}', '\u{321E}'), + ('\u{322A}', '\u{3247}'), + ('\u{3250}', '\u{3250}'), + ('\u{3260}', '\u{327F}'), + ('\u{328A}', '\u{32B0}'), + ('\u{32C0}', '\u{33FF}'), + ('\u{4DC0}', '\u{4DFF}'), + ('\u{A490}', '\u{A4C6}'), + ('\u{A4FE}', '\u{A4FF}'), + ('\u{A60D}', '\u{A60F}'), + ('\u{A673}', '\u{A673}'), + ('\u{A67E}', '\u{A67E}'), + ('\u{A6F2}', '\u{A6F7}'), + ('\u{A700}', '\u{A716}'), + ('\u{A720}', '\u{A721}'), + ('\u{A789}', '\u{A78A}'), + ('\u{A828}', '\u{A82B}'), + ('\u{A836}', '\u{A839}'), + ('\u{A874}', '\u{A877}'), + ('\u{A8CE}', '\u{A8CF}'), + ('\u{A8F8}', '\u{A8FA}'), + ('\u{A8FC}', '\u{A8FC}'), + ('\u{A92E}', '\u{A92F}'), + ('\u{A95F}', '\u{A95F}'), + ('\u{A9C1}', '\u{A9CD}'), + ('\u{A9DE}', '\u{A9DF}'), + ('\u{AA5C}', '\u{AA5F}'), + ('\u{AA77}', '\u{AA79}'), + ('\u{AADE}', '\u{AADF}'), + ('\u{AAF0}', '\u{AAF1}'), + ('\u{AB5B}', '\u{AB5B}'), + ('\u{AB6A}', '\u{AB6B}'), + ('\u{ABEB}', '\u{ABEB}'), + ('\u{FB29}', '\u{FB29}'), + ('\u{FBB2}', '\u{FBC2}'), + ('\u{FD3E}', '\u{FD4F}'), + ('\u{FDCF}', '\u{FDCF}'), + ('\u{FDFC}', '\u{FDFF}'), + ('\u{FE10}', '\u{FE19}'), + ('\u{FE30}', '\u{FE52}'), + ('\u{FE54}', '\u{FE66}'), + ('\u{FE68}', '\u{FE6B}'), + ('\u{FF01}', '\u{FF0F}'), + ('\u{FF1A}', '\u{FF20}'), + ('\u{FF3B}', '\u{FF40}'), + ('\u{FF5B}', '\u{FF65}'), + ('\u{FFE0}', '\u{FFE6}'), + ('\u{FFE8}', '\u{FFEE}'), + ('\u{FFFC}', '\u{FFFD}'), + ('\u{10100}', '\u{10102}'), + ('\u{10137}', '\u{1013F}'), + ('\u{10179}', '\u{10189}'), + ('\u{1018C}', '\u{1018E}'), + ('\u{10190}', '\u{1019C}'), + ('\u{101A0}', '\u{101A0}'), + ('\u{101D0}', '\u{101FC}'), + ('\u{1039F}', '\u{1039F}'), + ('\u{103D0}', '\u{103D0}'), + ('\u{1056F}', '\u{1056F}'), + ('\u{10857}', '\u{10857}'), + ('\u{10877}', '\u{10878}'), + ('\u{1091F}', '\u{1091F}'), + ('\u{1093F}', '\u{1093F}'), + ('\u{10A50}', '\u{10A58}'), + ('\u{10A7F}', '\u{10A7F}'), + ('\u{10AC8}', '\u{10AC8}'), + ('\u{10AF0}', '\u{10AF6}'), + ('\u{10B39}', '\u{10B3F}'), + ('\u{10B99}', '\u{10B9C}'), + ('\u{10D6E}', '\u{10D6E}'), + ('\u{10D8E}', '\u{10D8F}'), + ('\u{10EAD}', '\u{10EAD}'), + ('\u{10F55}', '\u{10F59}'), + ('\u{10F86}', '\u{10F89}'), + ('\u{11047}', '\u{1104D}'), + ('\u{110BB}', '\u{110BC}'), + ('\u{110BE}', '\u{110C1}'), + ('\u{11140}', '\u{11143}'), + ('\u{11174}', '\u{11175}'), + ('\u{111C5}', '\u{111C8}'), + ('\u{111CD}', '\u{111CD}'), + ('\u{111DB}', '\u{111DB}'), + ('\u{111DD}', '\u{111DF}'), + ('\u{11238}', '\u{1123D}'), + ('\u{112A9}', '\u{112A9}'), + ('\u{113D4}', '\u{113D5}'), + ('\u{113D7}', '\u{113D8}'), + ('\u{1144B}', '\u{1144F}'), + ('\u{1145A}', '\u{1145B}'), + ('\u{1145D}', '\u{1145D}'), + ('\u{114C6}', '\u{114C6}'), + ('\u{115C1}', '\u{115D7}'), + ('\u{11641}', '\u{11643}'), + ('\u{11660}', '\u{1166C}'), + ('\u{116B9}', '\u{116B9}'), + ('\u{1173C}', '\u{1173F}'), + ('\u{1183B}', '\u{1183B}'), + ('\u{11944}', '\u{11946}'), + ('\u{119E2}', '\u{119E2}'), + ('\u{11A3F}', '\u{11A46}'), + ('\u{11A9A}', '\u{11A9C}'), + ('\u{11A9E}', '\u{11AA2}'), + ('\u{11B00}', '\u{11B09}'), + ('\u{11BE1}', '\u{11BE1}'), + ('\u{11C41}', '\u{11C45}'), + ('\u{11C70}', '\u{11C71}'), + ('\u{11EF7}', '\u{11EF8}'), + ('\u{11F43}', '\u{11F4F}'), + ('\u{11FD5}', '\u{11FF1}'), + ('\u{11FFF}', '\u{11FFF}'), + ('\u{12470}', '\u{12474}'), + ('\u{12FF1}', '\u{12FF2}'), + ('\u{16A6E}', '\u{16A6F}'), + ('\u{16AF5}', '\u{16AF5}'), + ('\u{16B37}', '\u{16B3F}'), + ('\u{16B44}', '\u{16B45}'), + ('\u{16D6D}', '\u{16D6F}'), + ('\u{16E97}', '\u{16E9A}'), + ('\u{16FE2}', '\u{16FE2}'), + ('\u{1BC9C}', '\u{1BC9C}'), + ('\u{1BC9F}', '\u{1BC9F}'), + ('\u{1CC00}', '\u{1CCEF}'), + ('\u{1CD00}', '\u{1CEB3}'), + ('\u{1CF50}', '\u{1CFC3}'), + ('\u{1D000}', '\u{1D0F5}'), + ('\u{1D100}', '\u{1D126}'), + ('\u{1D129}', '\u{1D164}'), + ('\u{1D16A}', '\u{1D16C}'), + ('\u{1D183}', '\u{1D184}'), + ('\u{1D18C}', '\u{1D1A9}'), + ('\u{1D1AE}', '\u{1D1EA}'), + ('\u{1D200}', '\u{1D241}'), + ('\u{1D245}', '\u{1D245}'), + ('\u{1D300}', '\u{1D356}'), + ('\u{1D6C1}', '\u{1D6C1}'), + ('\u{1D6DB}', '\u{1D6DB}'), + ('\u{1D6FB}', '\u{1D6FB}'), + ('\u{1D715}', '\u{1D715}'), + ('\u{1D735}', '\u{1D735}'), + ('\u{1D74F}', '\u{1D74F}'), + ('\u{1D76F}', '\u{1D76F}'), + ('\u{1D789}', '\u{1D789}'), + ('\u{1D7A9}', '\u{1D7A9}'), + ('\u{1D7C3}', '\u{1D7C3}'), + ('\u{1D800}', '\u{1D9FF}'), + ('\u{1DA37}', '\u{1DA3A}'), + ('\u{1DA6D}', '\u{1DA74}'), + ('\u{1DA76}', '\u{1DA83}'), + ('\u{1DA85}', '\u{1DA8B}'), + ('\u{1E14F}', '\u{1E14F}'), + ('\u{1E2FF}', '\u{1E2FF}'), + ('\u{1E5FF}', '\u{1E5FF}'), + ('\u{1E95E}', '\u{1E95F}'), + ('\u{1ECAC}', '\u{1ECAC}'), + ('\u{1ECB0}', '\u{1ECB0}'), + ('\u{1ED2E}', '\u{1ED2E}'), + ('\u{1EEF0}', '\u{1EEF1}'), + ('\u{1F000}', '\u{1F02B}'), + ('\u{1F030}', '\u{1F093}'), + ('\u{1F0A0}', '\u{1F0AE}'), + ('\u{1F0B1}', '\u{1F0BF}'), + ('\u{1F0C1}', '\u{1F0CF}'), + ('\u{1F0D1}', '\u{1F0F5}'), + ('\u{1F10D}', '\u{1F1AD}'), + ('\u{1F1E6}', '\u{1F202}'), + ('\u{1F210}', '\u{1F23B}'), + ('\u{1F240}', '\u{1F248}'), + ('\u{1F250}', '\u{1F251}'), + ('\u{1F260}', '\u{1F265}'), + ('\u{1F300}', '\u{1F6D7}'), + ('\u{1F6DC}', '\u{1F6EC}'), + ('\u{1F6F0}', '\u{1F6FC}'), + ('\u{1F700}', '\u{1F776}'), + ('\u{1F77B}', '\u{1F7D9}'), + ('\u{1F7E0}', '\u{1F7EB}'), + ('\u{1F7F0}', '\u{1F7F0}'), + ('\u{1F800}', '\u{1F80B}'), + ('\u{1F810}', '\u{1F847}'), + ('\u{1F850}', '\u{1F859}'), + ('\u{1F860}', '\u{1F887}'), + ('\u{1F890}', '\u{1F8AD}'), + ('\u{1F8B0}', '\u{1F8BB}'), + ('\u{1F8C0}', '\u{1F8C1}'), + ('\u{1F900}', '\u{1FA53}'), + ('\u{1FA60}', '\u{1FA6D}'), + ('\u{1FA70}', '\u{1FA7C}'), + ('\u{1FA80}', '\u{1FA89}'), + ('\u{1FA8F}', '\u{1FAC6}'), + ('\u{1FACE}', '\u{1FADC}'), + ('\u{1FADF}', '\u{1FAE9}'), + ('\u{1FAF0}', '\u{1FAF8}'), + ('\u{1FB00}', '\u{1FB92}'), + ('\u{1FB94}', '\u{1FBEF}'), +]; + +/// Check if a character is Unicode punctuation per CommonMark. +#[inline] +pub fn is_unicode_punctuation(c: char) -> bool { + bsearch_range_table(c, PUNCTUATION_RANGES) +} + +#[cfg(test)] +mod tests { + use super::PUNCTUATION_RANGES; + use super::is_unicode_punctuation; + + #[test] + fn ascii_punctuation() { + assert!(is_unicode_punctuation('!')); + assert!(is_unicode_punctuation('.')); + assert!(is_unicode_punctuation('(')); + } + + #[test] + fn non_punctuation() { + assert!(!is_unicode_punctuation('a')); + assert!(!is_unicode_punctuation(' ')); + assert!(!is_unicode_punctuation('0')); + } + + #[test] + fn unicode_punctuation() { + assert!(is_unicode_punctuation('\u{2014}')); + assert!(is_unicode_punctuation('\u{00BF}')); + } + + #[test] + fn table_is_sorted() { + for window in PUNCTUATION_RANGES.windows(2) { + assert!(window[0].1 < window[1].0, "Ranges must be sorted"); + } + } +} diff --git a/crates/biome_unicode_table/src/tables.rs b/crates/biome_unicode_table/src/tables.rs index 0d71672e2e5a..a269a3f01e51 100644 --- a/crates/biome_unicode_table/src/tables.rs +++ b/crates/biome_unicode_table/src/tables.rs @@ -70,7 +70,7 @@ pub mod derived_property { ('ࡀ', '\u{85b}'), ('ࡠ', 'ࡪ'), ('ࡰ', 'ࢇ'), - ('ࢉ', '\u{88f}'), + ('ࢉ', '࢏'), ('\u{897}', '\u{8e1}'), ('\u{8e3}', '\u{963}'), ('०', '९'), @@ -160,7 +160,7 @@ pub mod derived_property { ('\u{c4a}', '\u{c4d}'), ('\u{c55}', '\u{c56}'), ('ౘ', 'ౚ'), - ('\u{c5c}', 'ౝ'), + ('౜', 'ౝ'), ('ౠ', '\u{c63}'), ('౦', '౯'), ('ಀ', 'ಃ'), @@ -173,7 +173,7 @@ pub mod derived_property { ('\u{cc6}', '\u{cc8}'), ('\u{cca}', '\u{ccd}'), ('\u{cd5}', '\u{cd6}'), - ('\u{cdc}', 'ೞ'), + ('೜', 'ೞ'), ('ೠ', '\u{ce3}'), ('೦', '೯'), ('ೱ', 'ೳ'), @@ -380,7 +380,7 @@ pub mod derived_property { ('ꜗ', 'ꜟ'), ('Ꜣ', 'ꞈ'), ('Ꞌ', 'Ƛ'), - ('\u{a7f1}', 'ꠧ'), + ('꟱', 'ꠧ'), ('\u{a82c}', '\u{a82c}'), ('ꡀ', 'ꡳ'), ('ꢀ', '\u{a8c5}'), @@ -496,7 +496,7 @@ pub mod derived_property { ('𐣴', '𐣵'), ('𐤀', '𐤕'), ('𐤠', '𐤹'), - ('\u{10940}', '\u{10959}'), + ('𐥀', '𐥙'), ('𐦀', '𐦷'), ('𐦾', '𐦿'), ('𐨀', '\u{10a03}'), @@ -525,7 +525,7 @@ pub mod derived_property { ('𐺀', '𐺩'), ('\u{10eab}', '\u{10eac}'), ('𐺰', '𐺱'), - ('𐻂', '\u{10ec7}'), + ('𐻂', '𐻇'), ('\u{10efa}', '𐼜'), ('𐼧', '𐼧'), ('𐼰', '\u{10f50}'), @@ -620,7 +620,7 @@ pub mod derived_property { ('𑩐', '\u{11a99}'), ('𑪝', '𑪝'), ('𑪰', '𑫸'), - ('\u{11b60}', '\u{11b67}'), + ('\u{11b60}', '𑭧'), ('𑯀', '𑯠'), ('𑯰', '𑯹'), ('𑰀', '𑰈'), @@ -643,8 +643,8 @@ pub mod derived_property { ('\u{11d90}', '\u{11d91}'), ('𑶓', '𑶘'), ('𑶠', '𑶩'), - ('\u{11db0}', '\u{11ddb}'), - ('\u{11de0}', '\u{11de9}'), + ('𑶰', '𑷛'), + ('𑷠', '𑷩'), ('𑻠', '𑻶'), ('\u{11f00}', '𑼐'), ('𑼒', '\u{11f3a}'), @@ -675,17 +675,17 @@ pub mod derived_property { ('𖵀', '𖵬'), ('𖵰', '𖵹'), ('𖹀', '𖹿'), - ('\u{16ea0}', '\u{16eb8}'), - ('\u{16ebb}', '\u{16ed3}'), + ('𖺠', '𖺸'), + ('𖺻', '𖻓'), ('𖼀', '𖽊'), ('\u{16f4f}', '𖾇'), ('\u{16f8f}', '𖾟'), ('𖿠', '𖿡'), ('𖿣', '\u{16fe4}'), - ('\u{16ff0}', '\u{16ff6}'), + ('\u{16ff0}', '𖿶'), ('𗀀', '𘳕'), - ('𘳿', '\u{18d1e}'), - ('\u{18d80}', '\u{18df2}'), + ('𘳿', '𘴞'), + ('𘶀', '𘷲'), ('𚿰', '𚿳'), ('𚿵', '𚿻'), ('𚿽', '𚿾'), @@ -763,9 +763,9 @@ pub mod derived_property { ('𞋀', '𞋹'), ('𞓐', '𞓹'), ('𞗐', '𞗺'), - ('\u{1e6c0}', '\u{1e6de}'), - ('\u{1e6e0}', '\u{1e6f5}'), - ('\u{1e6fe}', '\u{1e6ff}'), + ('𞛀', '𞛞'), + ('𞛠', '\u{1e6f5}'), + ('𞛾', '𞛿'), ('𞟠', '𞟦'), ('𞟨', '𞟫'), ('𞟭', '𞟮'), @@ -810,12 +810,12 @@ pub mod derived_property { ('🯰', '🯹'), ('𠀀', '𪛟'), ('𪜀', '𫠝'), - ('𫠠', '\u{2cead}'), + ('𫠠', '𬺭'), ('𬺰', '𮯠'), ('𮯰', '𮹝'), ('丽', '𪘀'), ('𰀀', '𱍊'), - ('𱍐', '\u{33479}'), + ('𱍐', '𳑹'), ('\u{e0100}', '\u{e01ef}'), ]; pub fn ID_Continue(c: char) -> bool { @@ -872,7 +872,7 @@ pub mod derived_property { ('ࡀ', 'ࡘ'), ('ࡠ', 'ࡪ'), ('ࡰ', 'ࢇ'), - ('ࢉ', '\u{88f}'), + ('ࢉ', '࢏'), ('ࢠ', 'ࣉ'), ('ऄ', 'ह'), ('ऽ', 'ऽ'), @@ -938,7 +938,7 @@ pub mod derived_property { ('ప', 'హ'), ('ఽ', 'ఽ'), ('ౘ', 'ౚ'), - ('\u{c5c}', 'ౝ'), + ('౜', 'ౝ'), ('ౠ', 'ౡ'), ('ಀ', 'ಀ'), ('ಅ', 'ಌ'), @@ -947,7 +947,7 @@ pub mod derived_property { ('ಪ', 'ಳ'), ('ವ', 'ಹ'), ('ಽ', 'ಽ'), - ('\u{cdc}', 'ೞ'), + ('೜', 'ೞ'), ('ೠ', 'ೡ'), ('ೱ', 'ೲ'), ('ഄ', 'ഌ'), @@ -1130,7 +1130,7 @@ pub mod derived_property { ('ꜗ', 'ꜟ'), ('Ꜣ', 'ꞈ'), ('Ꞌ', 'Ƛ'), - ('\u{a7f1}', 'ꠁ'), + ('꟱', 'ꠁ'), ('ꠃ', 'ꠅ'), ('ꠇ', 'ꠊ'), ('ꠌ', 'ꠢ'), @@ -1246,7 +1246,7 @@ pub mod derived_property { ('𐣴', '𐣵'), ('𐤀', '𐤕'), ('𐤠', '𐤹'), - ('\u{10940}', '\u{10959}'), + ('𐥀', '𐥙'), ('𐦀', '𐦷'), ('𐦾', '𐦿'), ('𐨀', '𐨀'), @@ -1269,7 +1269,7 @@ pub mod derived_property { ('𐵯', '𐶅'), ('𐺀', '𐺩'), ('𐺰', '𐺱'), - ('𐻂', '\u{10ec7}'), + ('𐻂', '𐻇'), ('𐼀', '𐼜'), ('𐼧', '𐼧'), ('𐼰', '𐽅'), @@ -1362,7 +1362,7 @@ pub mod derived_property { ('𑵧', '𑵨'), ('𑵪', '𑶉'), ('𑶘', '𑶘'), - ('\u{11db0}', '\u{11ddb}'), + ('𑶰', '𑷛'), ('𑻠', '𑻲'), ('𑼂', '𑼂'), ('𑼄', '𑼐'), @@ -1387,17 +1387,17 @@ pub mod derived_property { ('𖭽', '𖮏'), ('𖵀', '𖵬'), ('𖹀', '𖹿'), - ('\u{16ea0}', '\u{16eb8}'), - ('\u{16ebb}', '\u{16ed3}'), + ('𖺠', '𖺸'), + ('𖺻', '𖻓'), ('𖼀', '𖽊'), ('𖽐', '𖽐'), ('𖾓', '𖾟'), ('𖿠', '𖿡'), ('𖿣', '𖿣'), - ('\u{16ff2}', '\u{16ff6}'), + ('𖿲', '𖿶'), ('𗀀', '𘳕'), - ('𘳿', '\u{18d1e}'), - ('\u{18d80}', '\u{18df2}'), + ('𘳿', '𘴞'), + ('𘶀', '𘷲'), ('𚿰', '𚿳'), ('𚿵', '𚿻'), ('𚿽', '𚿾'), @@ -1452,12 +1452,12 @@ pub mod derived_property { ('𞓐', '𞓫'), ('𞗐', '𞗭'), ('𞗰', '𞗰'), - ('\u{1e6c0}', '\u{1e6de}'), - ('\u{1e6e0}', '\u{1e6e2}'), - ('\u{1e6e4}', '\u{1e6e5}'), - ('\u{1e6e7}', '\u{1e6ed}'), - ('\u{1e6f0}', '\u{1e6f4}'), - ('\u{1e6fe}', '\u{1e6ff}'), + ('𞛀', '𞛞'), + ('𞛠', '𞛢'), + ('𞛤', '𞛥'), + ('𞛧', '𞛭'), + ('𞛰', '𞛴'), + ('𞛾', '𞛿'), ('𞟠', '𞟦'), ('𞟨', '𞟫'), ('𞟭', '𞟮'), @@ -1500,12 +1500,12 @@ pub mod derived_property { ('𞺫', '𞺻'), ('𠀀', '𪛟'), ('𪜀', '𫠝'), - ('𫠠', '\u{2cead}'), + ('𫠠', '𬺭'), ('𬺰', '𮯠'), ('𮯰', '𮹝'), ('丽', '𪘀'), ('𰀀', '𱍊'), - ('𱍐', '\u{33479}'), + ('𱍐', '𳑹'), ]; pub fn ID_Start(c: char) -> bool { super::bsearch_range_table(c, ID_Start_table) diff --git a/crates/biome_wasm/build.rs b/crates/biome_wasm/build.rs index 1b55c5358ded..fc7accee0256 100644 --- a/crates/biome_wasm/build.rs +++ b/crates/biome_wasm/build.rs @@ -66,7 +66,12 @@ fn main() -> io::Result<()> { // Wasm-bindgen will paste the generated TS code as-is into the final .d.ts file, // ensure it looks good by running it through the formatter - let formatted = format_node(JsFormatOptions::new(JsFileSource::ts()), module.syntax()).unwrap(); + let formatted = format_node( + JsFormatOptions::new(JsFileSource::ts()), + module.syntax(), + false, + ) + .unwrap(); let printed = formatted.print().unwrap(); let definitions = printed.into_code(); diff --git a/crates/biome_yaml_formatter/Cargo.toml b/crates/biome_yaml_formatter/Cargo.toml new file mode 100644 index 000000000000..cba11db73f85 --- /dev/null +++ b/crates/biome_yaml_formatter/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "biome_yaml_formatter" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Biome's YAML formatter" +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true +publish = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# cargo-workspaces metadata +[package.metadata.workspaces] +independent = true + +[dependencies] +biome_deserialize = { workspace = true } +biome_diagnostics = { workspace = true } +biome_formatter = { workspace = true } +biome_rowan = { workspace = true } +biome_suppression = { workspace = true } +biome_yaml_syntax = { workspace = true } +schemars = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"], optional = true } + +[dev-dependencies] +biome_formatter = { workspace = true, features = ["countme"] } +biome_yaml_parser = { path = "../biome_yaml_parser" } + +[features] +schema = ["dep:schemars", "serde"] +serde = ["dep:serde"] + +[lints] +workspace = true diff --git a/crates/biome_yaml_formatter/src/comments.rs b/crates/biome_yaml_formatter/src/comments.rs new file mode 100644 index 000000000000..4d07c8abbfc8 --- /dev/null +++ b/crates/biome_yaml_formatter/src/comments.rs @@ -0,0 +1,85 @@ +use biome_diagnostics::category; +use biome_formatter::comments::{ + CommentKind, CommentPlacement, CommentStyle, Comments, DecoratedComment, SourceComment, +}; +use biome_formatter::formatter::Formatter; +use biome_formatter::{FormatResult, FormatRule, write}; +use biome_rowan::{SyntaxTriviaPieceComments, TextSize}; +use biome_suppression::{SuppressionKind, parse_suppression_comment}; +use biome_yaml_syntax::{YamlLanguage, YamlRoot}; + +use crate::prelude::*; + +pub type YamlComments = Comments; + +#[derive(Default)] +pub struct FormatYamlLeadingComment; + +impl FormatRule> for FormatYamlLeadingComment { + type Context = YamlFormatContext; + + fn fmt( + &self, + comment: &SourceComment, + f: &mut Formatter, + ) -> FormatResult<()> { + write!(f, [comment.piece().as_piece()]) + } +} + +#[derive(Eq, PartialEq, Copy, Clone, Debug, Default)] +pub struct YamlCommentStyle; + +impl CommentStyle for YamlCommentStyle { + type Language = YamlLanguage; + + fn is_suppression(text: &str) -> bool { + parse_suppression_comment(text) + .filter_map(Result::ok) + .filter(|suppression| suppression.kind == SuppressionKind::Classic) + .flat_map(|suppression| suppression.categories) + .any(|(key, ..)| key == category!("format")) + } + + fn is_global_suppression(text: &str) -> bool { + parse_suppression_comment(text) + .filter_map(Result::ok) + .filter(|suppression| suppression.kind == SuppressionKind::All) + .flat_map(|suppression| suppression.categories) + .any(|(key, ..)| key == category!("format")) + } + + fn get_comment_kind(_comment: &SyntaxTriviaPieceComments) -> CommentKind { + CommentKind::Line + } + + fn place_comment( + &self, + comment: DecoratedComment, + ) -> CommentPlacement { + handle_global_suppression(comment) + } +} + +fn handle_global_suppression( + comment: DecoratedComment, +) -> CommentPlacement { + let node = comment.enclosing_node(); + + if node.text_range_with_trivia().start() == TextSize::from(0) { + let has_global_suppression = node.first_leading_trivia().is_some_and(|trivia| { + trivia + .pieces() + .filter(|piece| piece.is_comments()) + .any(|piece| YamlCommentStyle::is_global_suppression(piece.text())) + }); + let root = node.ancestors().find_map(YamlRoot::cast); + if let Some(root) = root + && has_global_suppression + { + return CommentPlacement::leading(root.syntax().clone(), comment); + } + } + + CommentPlacement::Default(comment) +} diff --git a/crates/biome_yaml_formatter/src/context.rs b/crates/biome_yaml_formatter/src/context.rs new file mode 100644 index 000000000000..7e5cd64f8e29 --- /dev/null +++ b/crates/biome_yaml_formatter/src/context.rs @@ -0,0 +1,163 @@ +use std::default::Default; +use std::fmt; +use std::rc::Rc; + +use biome_formatter::{ + CstFormatContext, FormatContext, FormatOptions, IndentStyle, LineEnding, LineWidth, + TrailingNewline, TransformSourceMap, +}; +use biome_formatter::{IndentWidth, prelude::*}; +use biome_yaml_syntax::{YamlFileSource, YamlLanguage}; + +use crate::YamlCommentStyle; +use crate::comments::{FormatYamlLeadingComment, YamlComments}; + +#[derive(Debug)] +pub struct YamlFormatContext { + options: YamlFormatOptions, + /// The comments of the nodes and tokens in the program. + comments: Rc, + source_map: Option, +} + +impl YamlFormatContext { + pub fn new(options: YamlFormatOptions, comments: YamlComments) -> Self { + Self { + options, + comments: Rc::new(comments), + source_map: None, + } + } + + pub fn with_source_map(mut self, source_map: Option) -> Self { + self.source_map = source_map; + self + } +} + +impl FormatContext for YamlFormatContext { + type Options = YamlFormatOptions; + + fn options(&self) -> &Self::Options { + &self.options + } + + fn source_map(&self) -> Option<&TransformSourceMap> { + None + } +} + +impl CstFormatContext for YamlFormatContext { + type Language = YamlLanguage; + type Style = YamlCommentStyle; + type CommentRule = FormatYamlLeadingComment; + + fn comments(&self) -> &YamlComments { + &self.comments + } +} + +#[derive(Debug, Default, Clone)] +pub struct YamlFormatOptions { + indent_style: IndentStyle, + indent_width: IndentWidth, + line_ending: LineEnding, + line_width: LineWidth, + /// Whether to add a trailing newline at the end of the file. Defaults to true. + trailing_newline: TrailingNewline, + /// The kind of file + _file_source: YamlFileSource, +} + +impl YamlFormatOptions { + pub fn new(file_source: YamlFileSource) -> Self { + Self { + _file_source: file_source, + trailing_newline: TrailingNewline::default(), + ..Default::default() + } + } + + pub fn with_indent_style(mut self, indent_style: IndentStyle) -> Self { + self.indent_style = indent_style; + self + } + + pub fn with_indent_width(mut self, indent_width: IndentWidth) -> Self { + self.indent_width = indent_width; + self + } + + pub fn with_line_ending(mut self, line_ending: LineEnding) -> Self { + self.line_ending = line_ending; + self + } + + pub fn with_line_width(mut self, line_width: LineWidth) -> Self { + self.line_width = line_width; + self + } + + pub fn with_trailing_newline(mut self, trailing_newline: TrailingNewline) -> Self { + self.trailing_newline = trailing_newline; + self + } + + pub fn set_indent_style(&mut self, indent_style: IndentStyle) { + self.indent_style = indent_style; + } + + pub fn set_indent_width(&mut self, indent_width: IndentWidth) { + self.indent_width = indent_width; + } + + pub fn set_line_ending(&mut self, line_ending: LineEnding) { + self.line_ending = line_ending; + } + + pub fn set_line_width(&mut self, line_width: LineWidth) { + self.line_width = line_width; + } + + pub fn set_trailing_newline(&mut self, trailing_newline: TrailingNewline) { + self.trailing_newline = trailing_newline; + } +} + +impl FormatOptions for YamlFormatOptions { + fn indent_style(&self) -> IndentStyle { + self.indent_style + } + + fn indent_width(&self) -> IndentWidth { + self.indent_width + } + + fn line_width(&self) -> LineWidth { + self.line_width + } + + fn line_ending(&self) -> LineEnding { + self.line_ending + } + + fn trailing_newline(&self) -> TrailingNewline { + self.trailing_newline + } + + fn as_print_options(&self) -> PrinterOptions { + PrinterOptions::from(self) + } +} + +impl fmt::Display for YamlFormatOptions { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Indent style: {}", self.indent_style)?; + writeln!(f, "Indent width: {}", self.indent_width.value())?; + writeln!(f, "Line ending: {}", self.line_ending)?; + writeln!(f, "Line width: {}", self.line_width.value())?; + writeln!(f, "Trailing newline: {}", self.trailing_newline.value())?; + + Ok(()) + } +} diff --git a/crates/biome_yaml_formatter/src/cst.rs b/crates/biome_yaml_formatter/src/cst.rs new file mode 100644 index 000000000000..9abb530a3f41 --- /dev/null +++ b/crates/biome_yaml_formatter/src/cst.rs @@ -0,0 +1,31 @@ +use biome_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatResult}; +use biome_yaml_syntax::{YamlSyntaxNode, map_syntax_node}; + +use crate::prelude::*; + +#[derive(Debug, Copy, Clone, Default)] +pub struct FormatYamlSyntaxNode; + +impl FormatRule for FormatYamlSyntaxNode { + type Context = YamlFormatContext; + + fn fmt(&self, node: &YamlSyntaxNode, f: &mut YamlFormatter) -> FormatResult<()> { + map_syntax_node!(node.clone(), node => node.format().fmt(f)) + } +} + +impl AsFormat for YamlSyntaxNode { + type Format<'a> = FormatRefWithRule<'a, Self, FormatYamlSyntaxNode>; + + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new(self, FormatYamlSyntaxNode) + } +} + +impl IntoFormat for YamlSyntaxNode { + type Format = FormatOwnedWithRule; + + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new(self, FormatYamlSyntaxNode) + } +} diff --git a/crates/biome_yaml_formatter/src/generated.rs b/crates/biome_yaml_formatter/src/generated.rs new file mode 100644 index 000000000000..8fdd13a7250d --- /dev/null +++ b/crates/biome_yaml_formatter/src/generated.rs @@ -0,0 +1,1725 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +#![allow(clippy::use_self)] +#![expect(clippy::default_constructed_unit_structs)] +use crate::{ + AsFormat, FormatBogusNodeRule, FormatNodeRule, IntoFormat, YamlFormatContext, YamlFormatter, +}; +use biome_formatter::{FormatOwnedWithRule, FormatRefWithRule, FormatResult, FormatRule}; +impl FormatRule + for crate::yaml::auxiliary::alias_node::FormatYamlAliasNode +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlAliasNode, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlAliasNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlAliasNode, + crate::yaml::auxiliary::alias_node::FormatYamlAliasNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::alias_node::FormatYamlAliasNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlAliasNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlAliasNode, + crate::yaml::auxiliary::alias_node::FormatYamlAliasNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::alias_node::FormatYamlAliasNode::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::anchor_property::FormatYamlAnchorProperty +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlAnchorProperty, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlAnchorProperty { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlAnchorProperty, + crate::yaml::auxiliary::anchor_property::FormatYamlAnchorProperty, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::anchor_property::FormatYamlAnchorProperty::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlAnchorProperty { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlAnchorProperty, + crate::yaml::auxiliary::anchor_property::FormatYamlAnchorProperty, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::anchor_property::FormatYamlAnchorProperty::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_content::FormatYamlBlockContent +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockContent, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockContent { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockContent, + crate::yaml::auxiliary::block_content::FormatYamlBlockContent, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::block_content::FormatYamlBlockContent::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockContent { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockContent, + crate::yaml::auxiliary::block_content::FormatYamlBlockContent, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::block_content::FormatYamlBlockContent::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_keep_indicator::FormatYamlBlockKeepIndicator +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockKeepIndicator, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockKeepIndicator { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockKeepIndicator, + crate::yaml::auxiliary::block_keep_indicator::FormatYamlBlockKeepIndicator, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::block_keep_indicator::FormatYamlBlockKeepIndicator::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockKeepIndicator { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockKeepIndicator, + crate::yaml::auxiliary::block_keep_indicator::FormatYamlBlockKeepIndicator, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::block_keep_indicator::FormatYamlBlockKeepIndicator::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_map_explicit_entry::FormatYamlBlockMapExplicitEntry +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockMapExplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockMapExplicitEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockMapExplicitEntry, + crate::yaml::auxiliary::block_map_explicit_entry::FormatYamlBlockMapExplicitEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: yaml :: auxiliary :: block_map_explicit_entry :: FormatYamlBlockMapExplicitEntry :: default ()) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockMapExplicitEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockMapExplicitEntry, + crate::yaml::auxiliary::block_map_explicit_entry::FormatYamlBlockMapExplicitEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: yaml :: auxiliary :: block_map_explicit_entry :: FormatYamlBlockMapExplicitEntry :: default ()) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_map_implicit_entry::FormatYamlBlockMapImplicitEntry +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockMapImplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockMapImplicitEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockMapImplicitEntry, + crate::yaml::auxiliary::block_map_implicit_entry::FormatYamlBlockMapImplicitEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: yaml :: auxiliary :: block_map_implicit_entry :: FormatYamlBlockMapImplicitEntry :: default ()) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockMapImplicitEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockMapImplicitEntry, + crate::yaml::auxiliary::block_map_implicit_entry::FormatYamlBlockMapImplicitEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: yaml :: auxiliary :: block_map_implicit_entry :: FormatYamlBlockMapImplicitEntry :: default ()) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_mapping::FormatYamlBlockMapping +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockMapping, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockMapping { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockMapping, + crate::yaml::auxiliary::block_mapping::FormatYamlBlockMapping, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::block_mapping::FormatYamlBlockMapping::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockMapping { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockMapping, + crate::yaml::auxiliary::block_mapping::FormatYamlBlockMapping, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::block_mapping::FormatYamlBlockMapping::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_sequence::FormatYamlBlockSequence +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockSequence, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockSequence { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockSequence, + crate::yaml::auxiliary::block_sequence::FormatYamlBlockSequence, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::block_sequence::FormatYamlBlockSequence::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockSequence { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockSequence, + crate::yaml::auxiliary::block_sequence::FormatYamlBlockSequence, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::block_sequence::FormatYamlBlockSequence::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_sequence_entry::FormatYamlBlockSequenceEntry +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockSequenceEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockSequenceEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockSequenceEntry, + crate::yaml::auxiliary::block_sequence_entry::FormatYamlBlockSequenceEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::block_sequence_entry::FormatYamlBlockSequenceEntry::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockSequenceEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockSequenceEntry, + crate::yaml::auxiliary::block_sequence_entry::FormatYamlBlockSequenceEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::block_sequence_entry::FormatYamlBlockSequenceEntry::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::block_strip_indicator::FormatYamlBlockStripIndicator +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBlockStripIndicator, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockStripIndicator { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockStripIndicator, + crate::yaml::auxiliary::block_strip_indicator::FormatYamlBlockStripIndicator, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::block_strip_indicator::FormatYamlBlockStripIndicator::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockStripIndicator { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockStripIndicator, + crate::yaml::auxiliary::block_strip_indicator::FormatYamlBlockStripIndicator, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::block_strip_indicator::FormatYamlBlockStripIndicator::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::directive::FormatYamlDirective +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlDirective, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlDirective { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlDirective, + crate::yaml::auxiliary::directive::FormatYamlDirective, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::directive::FormatYamlDirective::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlDirective { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlDirective, + crate::yaml::auxiliary::directive::FormatYamlDirective, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::directive::FormatYamlDirective::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::document::FormatYamlDocument +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlDocument, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlDocument { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlDocument, + crate::yaml::auxiliary::document::FormatYamlDocument, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::document::FormatYamlDocument::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlDocument { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlDocument, + crate::yaml::auxiliary::document::FormatYamlDocument, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::document::FormatYamlDocument::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::double_quoted_scalar::FormatYamlDoubleQuotedScalar +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlDoubleQuotedScalar, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlDoubleQuotedScalar { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlDoubleQuotedScalar, + crate::yaml::auxiliary::double_quoted_scalar::FormatYamlDoubleQuotedScalar, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::double_quoted_scalar::FormatYamlDoubleQuotedScalar::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlDoubleQuotedScalar { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlDoubleQuotedScalar, + crate::yaml::auxiliary::double_quoted_scalar::FormatYamlDoubleQuotedScalar, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::double_quoted_scalar::FormatYamlDoubleQuotedScalar::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::flow_in_block_node::FormatYamlFlowInBlockNode +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFlowInBlockNode, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowInBlockNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowInBlockNode, + crate::yaml::auxiliary::flow_in_block_node::FormatYamlFlowInBlockNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::flow_in_block_node::FormatYamlFlowInBlockNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowInBlockNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowInBlockNode, + crate::yaml::auxiliary::flow_in_block_node::FormatYamlFlowInBlockNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::flow_in_block_node::FormatYamlFlowInBlockNode::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::flow_json_node::FormatYamlFlowJsonNode +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFlowJsonNode, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowJsonNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowJsonNode, + crate::yaml::auxiliary::flow_json_node::FormatYamlFlowJsonNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::flow_json_node::FormatYamlFlowJsonNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowJsonNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowJsonNode, + crate::yaml::auxiliary::flow_json_node::FormatYamlFlowJsonNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::flow_json_node::FormatYamlFlowJsonNode::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::flow_map_explicit_entry::FormatYamlFlowMapExplicitEntry +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFlowMapExplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowMapExplicitEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowMapExplicitEntry, + crate::yaml::auxiliary::flow_map_explicit_entry::FormatYamlFlowMapExplicitEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: yaml :: auxiliary :: flow_map_explicit_entry :: FormatYamlFlowMapExplicitEntry :: default ()) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowMapExplicitEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowMapExplicitEntry, + crate::yaml::auxiliary::flow_map_explicit_entry::FormatYamlFlowMapExplicitEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: yaml :: auxiliary :: flow_map_explicit_entry :: FormatYamlFlowMapExplicitEntry :: default ()) + } +} +impl FormatRule + for crate::yaml::auxiliary::flow_map_implicit_entry::FormatYamlFlowMapImplicitEntry +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFlowMapImplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowMapImplicitEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowMapImplicitEntry, + crate::yaml::auxiliary::flow_map_implicit_entry::FormatYamlFlowMapImplicitEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: yaml :: auxiliary :: flow_map_implicit_entry :: FormatYamlFlowMapImplicitEntry :: default ()) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowMapImplicitEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowMapImplicitEntry, + crate::yaml::auxiliary::flow_map_implicit_entry::FormatYamlFlowMapImplicitEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: yaml :: auxiliary :: flow_map_implicit_entry :: FormatYamlFlowMapImplicitEntry :: default ()) + } +} +impl FormatRule + for crate::yaml::auxiliary::flow_mapping::FormatYamlFlowMapping +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFlowMapping, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowMapping { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowMapping, + crate::yaml::auxiliary::flow_mapping::FormatYamlFlowMapping, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::flow_mapping::FormatYamlFlowMapping::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowMapping { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowMapping, + crate::yaml::auxiliary::flow_mapping::FormatYamlFlowMapping, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::flow_mapping::FormatYamlFlowMapping::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::flow_sequence::FormatYamlFlowSequence +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFlowSequence, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowSequence { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowSequence, + crate::yaml::auxiliary::flow_sequence::FormatYamlFlowSequence, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::flow_sequence::FormatYamlFlowSequence::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowSequence { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowSequence, + crate::yaml::auxiliary::flow_sequence::FormatYamlFlowSequence, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::flow_sequence::FormatYamlFlowSequence::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::flow_yaml_node::FormatYamlFlowYamlNode +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFlowYamlNode, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowYamlNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowYamlNode, + crate::yaml::auxiliary::flow_yaml_node::FormatYamlFlowYamlNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::flow_yaml_node::FormatYamlFlowYamlNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowYamlNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowYamlNode, + crate::yaml::auxiliary::flow_yaml_node::FormatYamlFlowYamlNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::flow_yaml_node::FormatYamlFlowYamlNode::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::folded_scalar::FormatYamlFoldedScalar +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlFoldedScalar, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlFoldedScalar { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFoldedScalar, + crate::yaml::auxiliary::folded_scalar::FormatYamlFoldedScalar, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::folded_scalar::FormatYamlFoldedScalar::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFoldedScalar { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFoldedScalar, + crate::yaml::auxiliary::folded_scalar::FormatYamlFoldedScalar, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::folded_scalar::FormatYamlFoldedScalar::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::indentation_indicator::FormatYamlIndentationIndicator +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlIndentationIndicator, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlIndentationIndicator { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlIndentationIndicator, + crate::yaml::auxiliary::indentation_indicator::FormatYamlIndentationIndicator, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::indentation_indicator::FormatYamlIndentationIndicator::default( + ), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlIndentationIndicator { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlIndentationIndicator, + crate::yaml::auxiliary::indentation_indicator::FormatYamlIndentationIndicator, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::indentation_indicator::FormatYamlIndentationIndicator::default( + ), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::literal_scalar::FormatYamlLiteralScalar +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlLiteralScalar, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlLiteralScalar { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlLiteralScalar, + crate::yaml::auxiliary::literal_scalar::FormatYamlLiteralScalar, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::literal_scalar::FormatYamlLiteralScalar::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlLiteralScalar { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlLiteralScalar, + crate::yaml::auxiliary::literal_scalar::FormatYamlLiteralScalar, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::literal_scalar::FormatYamlLiteralScalar::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::plain_scalar::FormatYamlPlainScalar +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlPlainScalar, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlPlainScalar { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlPlainScalar, + crate::yaml::auxiliary::plain_scalar::FormatYamlPlainScalar, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::plain_scalar::FormatYamlPlainScalar::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlPlainScalar { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlPlainScalar, + crate::yaml::auxiliary::plain_scalar::FormatYamlPlainScalar, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::plain_scalar::FormatYamlPlainScalar::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::properties_anchor_first::FormatYamlPropertiesAnchorFirst +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlPropertiesAnchorFirst, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlPropertiesAnchorFirst { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlPropertiesAnchorFirst, + crate::yaml::auxiliary::properties_anchor_first::FormatYamlPropertiesAnchorFirst, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: yaml :: auxiliary :: properties_anchor_first :: FormatYamlPropertiesAnchorFirst :: default ()) + } +} +impl IntoFormat for biome_yaml_syntax::YamlPropertiesAnchorFirst { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlPropertiesAnchorFirst, + crate::yaml::auxiliary::properties_anchor_first::FormatYamlPropertiesAnchorFirst, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: yaml :: auxiliary :: properties_anchor_first :: FormatYamlPropertiesAnchorFirst :: default ()) + } +} +impl FormatRule + for crate::yaml::auxiliary::properties_tag_first::FormatYamlPropertiesTagFirst +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlPropertiesTagFirst, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlPropertiesTagFirst { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlPropertiesTagFirst, + crate::yaml::auxiliary::properties_tag_first::FormatYamlPropertiesTagFirst, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::properties_tag_first::FormatYamlPropertiesTagFirst::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlPropertiesTagFirst { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlPropertiesTagFirst, + crate::yaml::auxiliary::properties_tag_first::FormatYamlPropertiesTagFirst, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::properties_tag_first::FormatYamlPropertiesTagFirst::default(), + ) + } +} +impl FormatRule for crate::yaml::auxiliary::root::FormatYamlRoot { + type Context = YamlFormatContext; + #[inline(always)] + fn fmt(&self, node: &biome_yaml_syntax::YamlRoot, f: &mut YamlFormatter) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlRoot { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlRoot, + crate::yaml::auxiliary::root::FormatYamlRoot, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::root::FormatYamlRoot::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlRoot { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlRoot, + crate::yaml::auxiliary::root::FormatYamlRoot, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::root::FormatYamlRoot::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::single_quoted_scalar::FormatYamlSingleQuotedScalar +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlSingleQuotedScalar, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlSingleQuotedScalar { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlSingleQuotedScalar, + crate::yaml::auxiliary::single_quoted_scalar::FormatYamlSingleQuotedScalar, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::single_quoted_scalar::FormatYamlSingleQuotedScalar::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlSingleQuotedScalar { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlSingleQuotedScalar, + crate::yaml::auxiliary::single_quoted_scalar::FormatYamlSingleQuotedScalar, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::single_quoted_scalar::FormatYamlSingleQuotedScalar::default(), + ) + } +} +impl FormatRule + for crate::yaml::auxiliary::tag_property::FormatYamlTagProperty +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlTagProperty, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlTagProperty { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlTagProperty, + crate::yaml::auxiliary::tag_property::FormatYamlTagProperty, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::auxiliary::tag_property::FormatYamlTagProperty::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlTagProperty { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlTagProperty, + crate::yaml::auxiliary::tag_property::FormatYamlTagProperty, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::auxiliary::tag_property::FormatYamlTagProperty::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockHeaderList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockHeaderList, + crate::yaml::lists::block_header_list::FormatYamlBlockHeaderList, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::lists::block_header_list::FormatYamlBlockHeaderList::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockHeaderList { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockHeaderList, + crate::yaml::lists::block_header_list::FormatYamlBlockHeaderList, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::lists::block_header_list::FormatYamlBlockHeaderList::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockMapEntryList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockMapEntryList, + crate::yaml::lists::block_map_entry_list::FormatYamlBlockMapEntryList, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::lists::block_map_entry_list::FormatYamlBlockMapEntryList::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockMapEntryList { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockMapEntryList, + crate::yaml::lists::block_map_entry_list::FormatYamlBlockMapEntryList, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::lists::block_map_entry_list::FormatYamlBlockMapEntryList::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::YamlBlockSequenceEntryList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBlockSequenceEntryList, + crate::yaml::lists::block_sequence_entry_list::FormatYamlBlockSequenceEntryList, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule :: new (self , crate :: yaml :: lists :: block_sequence_entry_list :: FormatYamlBlockSequenceEntryList :: default ()) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBlockSequenceEntryList { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBlockSequenceEntryList, + crate::yaml::lists::block_sequence_entry_list::FormatYamlBlockSequenceEntryList, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule :: new (self , crate :: yaml :: lists :: block_sequence_entry_list :: FormatYamlBlockSequenceEntryList :: default ()) + } +} +impl AsFormat for biome_yaml_syntax::YamlDirectiveList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlDirectiveList, + crate::yaml::lists::directive_list::FormatYamlDirectiveList, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::lists::directive_list::FormatYamlDirectiveList::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlDirectiveList { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlDirectiveList, + crate::yaml::lists::directive_list::FormatYamlDirectiveList, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::lists::directive_list::FormatYamlDirectiveList::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::YamlDocumentList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlDocumentList, + crate::yaml::lists::document_list::FormatYamlDocumentList, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::lists::document_list::FormatYamlDocumentList::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlDocumentList { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlDocumentList, + crate::yaml::lists::document_list::FormatYamlDocumentList, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::lists::document_list::FormatYamlDocumentList::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowMapEntryList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowMapEntryList, + crate::yaml::lists::flow_map_entry_list::FormatYamlFlowMapEntryList, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::lists::flow_map_entry_list::FormatYamlFlowMapEntryList::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowMapEntryList { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowMapEntryList, + crate::yaml::lists::flow_map_entry_list::FormatYamlFlowMapEntryList, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::lists::flow_map_entry_list::FormatYamlFlowMapEntryList::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::YamlFlowSequenceEntryList { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlFlowSequenceEntryList, + crate::yaml::lists::flow_sequence_entry_list::FormatYamlFlowSequenceEntryList, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::lists::flow_sequence_entry_list::FormatYamlFlowSequenceEntryList::default( + ), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlFlowSequenceEntryList { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlFlowSequenceEntryList, + crate::yaml::lists::flow_sequence_entry_list::FormatYamlFlowSequenceEntryList, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::lists::flow_sequence_entry_list::FormatYamlFlowSequenceEntryList::default( + ), + ) + } +} +impl FormatRule for crate::yaml::bogus::bogus::FormatYamlBogus { + type Context = YamlFormatContext; + #[inline(always)] + fn fmt(&self, node: &biome_yaml_syntax::YamlBogus, f: &mut YamlFormatter) -> FormatResult<()> { + FormatBogusNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBogus { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBogus, + crate::yaml::bogus::bogus::FormatYamlBogus, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new(self, crate::yaml::bogus::bogus::FormatYamlBogus::default()) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBogus { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBogus, + crate::yaml::bogus::bogus::FormatYamlBogus, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new(self, crate::yaml::bogus::bogus::FormatYamlBogus::default()) + } +} +impl FormatRule + for crate::yaml::bogus::bogus_block_header::FormatYamlBogusBlockHeader +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBogusBlockHeader, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatBogusNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBogusBlockHeader { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBogusBlockHeader, + crate::yaml::bogus::bogus_block_header::FormatYamlBogusBlockHeader, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::bogus::bogus_block_header::FormatYamlBogusBlockHeader::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBogusBlockHeader { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBogusBlockHeader, + crate::yaml::bogus::bogus_block_header::FormatYamlBogusBlockHeader, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::bogus::bogus_block_header::FormatYamlBogusBlockHeader::default(), + ) + } +} +impl FormatRule + for crate::yaml::bogus::bogus_block_map_entry::FormatYamlBogusBlockMapEntry +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBogusBlockMapEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatBogusNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBogusBlockMapEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBogusBlockMapEntry, + crate::yaml::bogus::bogus_block_map_entry::FormatYamlBogusBlockMapEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::bogus::bogus_block_map_entry::FormatYamlBogusBlockMapEntry::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBogusBlockMapEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBogusBlockMapEntry, + crate::yaml::bogus::bogus_block_map_entry::FormatYamlBogusBlockMapEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::bogus::bogus_block_map_entry::FormatYamlBogusBlockMapEntry::default(), + ) + } +} +impl FormatRule + for crate::yaml::bogus::bogus_block_node::FormatYamlBogusBlockNode +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBogusBlockNode, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatBogusNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBogusBlockNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBogusBlockNode, + crate::yaml::bogus::bogus_block_node::FormatYamlBogusBlockNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::bogus::bogus_block_node::FormatYamlBogusBlockNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBogusBlockNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBogusBlockNode, + crate::yaml::bogus::bogus_block_node::FormatYamlBogusBlockNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::bogus::bogus_block_node::FormatYamlBogusBlockNode::default(), + ) + } +} +impl FormatRule + for crate::yaml::bogus::bogus_flow_node::FormatYamlBogusFlowNode +{ + type Context = YamlFormatContext; + #[inline(always)] + fn fmt( + &self, + node: &biome_yaml_syntax::YamlBogusFlowNode, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + FormatBogusNodeRule::::fmt(self, node, f) + } +} +impl AsFormat for biome_yaml_syntax::YamlBogusFlowNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::YamlBogusFlowNode, + crate::yaml::bogus::bogus_flow_node::FormatYamlBogusFlowNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::bogus::bogus_flow_node::FormatYamlBogusFlowNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::YamlBogusFlowNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::YamlBogusFlowNode, + crate::yaml::bogus::bogus_flow_node::FormatYamlBogusFlowNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::bogus::bogus_flow_node::FormatYamlBogusFlowNode::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlBlockHeader { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlBlockHeader, + crate::yaml::any::block_header::FormatAnyYamlBlockHeader, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::block_header::FormatAnyYamlBlockHeader::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlBlockHeader { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlBlockHeader, + crate::yaml::any::block_header::FormatAnyYamlBlockHeader, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::block_header::FormatAnyYamlBlockHeader::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlBlockInBlockNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlBlockInBlockNode, + crate::yaml::any::block_in_block_node::FormatAnyYamlBlockInBlockNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::block_in_block_node::FormatAnyYamlBlockInBlockNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlBlockInBlockNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlBlockInBlockNode, + crate::yaml::any::block_in_block_node::FormatAnyYamlBlockInBlockNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::block_in_block_node::FormatAnyYamlBlockInBlockNode::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlBlockMapEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlBlockMapEntry, + crate::yaml::any::block_map_entry::FormatAnyYamlBlockMapEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::block_map_entry::FormatAnyYamlBlockMapEntry::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlBlockMapEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlBlockMapEntry, + crate::yaml::any::block_map_entry::FormatAnyYamlBlockMapEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::block_map_entry::FormatAnyYamlBlockMapEntry::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlBlockNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlBlockNode, + crate::yaml::any::block_node::FormatAnyYamlBlockNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::block_node::FormatAnyYamlBlockNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlBlockNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlBlockNode, + crate::yaml::any::block_node::FormatAnyYamlBlockNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::block_node::FormatAnyYamlBlockNode::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlBlockSequenceEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlBlockSequenceEntry, + crate::yaml::any::block_sequence_entry::FormatAnyYamlBlockSequenceEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::block_sequence_entry::FormatAnyYamlBlockSequenceEntry::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlBlockSequenceEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlBlockSequenceEntry, + crate::yaml::any::block_sequence_entry::FormatAnyYamlBlockSequenceEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::block_sequence_entry::FormatAnyYamlBlockSequenceEntry::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlDocument { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlDocument, + crate::yaml::any::document::FormatAnyYamlDocument, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::document::FormatAnyYamlDocument::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlDocument { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlDocument, + crate::yaml::any::document::FormatAnyYamlDocument, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::document::FormatAnyYamlDocument::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlFlowMapEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlFlowMapEntry, + crate::yaml::any::flow_map_entry::FormatAnyYamlFlowMapEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::flow_map_entry::FormatAnyYamlFlowMapEntry::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlFlowMapEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlFlowMapEntry, + crate::yaml::any::flow_map_entry::FormatAnyYamlFlowMapEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::flow_map_entry::FormatAnyYamlFlowMapEntry::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlFlowNode { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlFlowNode, + crate::yaml::any::flow_node::FormatAnyYamlFlowNode, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::flow_node::FormatAnyYamlFlowNode::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlFlowNode { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlFlowNode, + crate::yaml::any::flow_node::FormatAnyYamlFlowNode, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::flow_node::FormatAnyYamlFlowNode::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlFlowSequenceEntry { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlFlowSequenceEntry, + crate::yaml::any::flow_sequence_entry::FormatAnyYamlFlowSequenceEntry, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::flow_sequence_entry::FormatAnyYamlFlowSequenceEntry::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlFlowSequenceEntry { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlFlowSequenceEntry, + crate::yaml::any::flow_sequence_entry::FormatAnyYamlFlowSequenceEntry, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::flow_sequence_entry::FormatAnyYamlFlowSequenceEntry::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlJsonContent { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlJsonContent, + crate::yaml::any::json_content::FormatAnyYamlJsonContent, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::json_content::FormatAnyYamlJsonContent::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlJsonContent { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlJsonContent, + crate::yaml::any::json_content::FormatAnyYamlJsonContent, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::json_content::FormatAnyYamlJsonContent::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlMappingImplicitKey { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlMappingImplicitKey, + crate::yaml::any::mapping_implicit_key::FormatAnyYamlMappingImplicitKey, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::mapping_implicit_key::FormatAnyYamlMappingImplicitKey::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlMappingImplicitKey { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlMappingImplicitKey, + crate::yaml::any::mapping_implicit_key::FormatAnyYamlMappingImplicitKey, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::mapping_implicit_key::FormatAnyYamlMappingImplicitKey::default(), + ) + } +} +impl AsFormat for biome_yaml_syntax::AnyYamlPropertiesCombination { + type Format<'a> = FormatRefWithRule< + 'a, + biome_yaml_syntax::AnyYamlPropertiesCombination, + crate::yaml::any::properties_combination::FormatAnyYamlPropertiesCombination, + >; + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new( + self, + crate::yaml::any::properties_combination::FormatAnyYamlPropertiesCombination::default(), + ) + } +} +impl IntoFormat for biome_yaml_syntax::AnyYamlPropertiesCombination { + type Format = FormatOwnedWithRule< + biome_yaml_syntax::AnyYamlPropertiesCombination, + crate::yaml::any::properties_combination::FormatAnyYamlPropertiesCombination, + >; + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new( + self, + crate::yaml::any::properties_combination::FormatAnyYamlPropertiesCombination::default(), + ) + } +} diff --git a/crates/biome_yaml_formatter/src/lib.rs b/crates/biome_yaml_formatter/src/lib.rs new file mode 100644 index 000000000000..b68187e87b51 --- /dev/null +++ b/crates/biome_yaml_formatter/src/lib.rs @@ -0,0 +1,386 @@ +mod comments; +mod context; +mod cst; +mod generated; +mod prelude; +mod verbatim; +mod yaml; + +pub(crate) use crate::context::YamlFormatContext; + +use biome_deserialize::TextRange; +use biome_formatter::comments::Comments; +use biome_formatter::formatter::Formatter; +use biome_formatter::prelude::{ + format_dangling_comments, format_leading_comments, format_trailing_comments, +}; +use biome_formatter::trivia::{FormatToken, format_skipped_token_trivia}; +use biome_formatter::{ + Buffer, CstFormatContext, Format, FormatContext, FormatLanguage, FormatOwnedWithRule, + FormatRefWithRule, FormatResult, FormatRule, Formatted, Printed, TransformSourceMap, +}; +use biome_rowan::{AstNode, SyntaxNode}; +use biome_yaml_syntax::{YamlLanguage, YamlSyntaxNode, YamlSyntaxToken}; +use std::iter::FusedIterator; + +use crate::comments::YamlCommentStyle; +use crate::context::YamlFormatOptions; +use crate::cst::FormatYamlSyntaxNode; +use crate::verbatim::{format_bogus_node, format_suppressed_node}; + +/// Used to get an object that knows how to format this object. +pub(crate) trait AsFormat { + type Format<'a>: biome_formatter::Format + where + Self: 'a; + + /// Returns an object that is able to format this object. + fn format(&self) -> Self::Format<'_>; +} + +/// Implement [AsFormat] for references to types that implement [AsFormat]. +impl AsFormat for &T +where + T: AsFormat, +{ + type Format<'a> + = T::Format<'a> + where + Self: 'a; + + fn format(&self) -> Self::Format<'_> { + AsFormat::format(&**self) + } +} + +/// Implement [AsFormat] for [SyntaxResult] where `T` implements [AsFormat]. +/// +/// Useful to format mandatory AST fields without having to unwrap the value first. +impl AsFormat for biome_rowan::SyntaxResult +where + T: AsFormat, +{ + type Format<'a> + = biome_rowan::SyntaxResult> + where + Self: 'a; + + fn format(&self) -> Self::Format<'_> { + match self { + Ok(value) => Ok(value.format()), + Err(err) => Err(*err), + } + } +} + +/// Implement [AsFormat] for [Option] when `T` implements [AsFormat] +/// +/// Allows to call format on optional AST fields without having to unwrap the field first. +impl AsFormat for Option +where + T: AsFormat, +{ + type Format<'a> + = Option> + where + Self: 'a; + + fn format(&self) -> Self::Format<'_> { + self.as_ref().map(|value| value.format()) + } +} + +/// Used to convert this object into an object that can be formatted. +/// +/// The difference to [AsFormat] is that this trait takes ownership of `self`. +pub(crate) trait IntoFormat { + type Format: biome_formatter::Format; + + fn into_format(self) -> Self::Format; +} + +impl IntoFormat for biome_rowan::SyntaxResult +where + T: IntoFormat, +{ + type Format = biome_rowan::SyntaxResult; + + fn into_format(self) -> Self::Format { + self.map(IntoFormat::into_format) + } +} + +/// Implement [IntoFormat] for [Option] when `T` implements [IntoFormat] +/// +/// Allows to call format on optional AST fields without having to unwrap the field first. +impl IntoFormat for Option +where + T: IntoFormat, +{ + type Format = Option; + + fn into_format(self) -> Self::Format { + self.map(IntoFormat::into_format) + } +} + +/// Formatting specific [Iterator] extensions +// False positive +pub(crate) trait FormattedIterExt { + /// Converts every item to an object that knows how to format it. + fn formatted(self) -> FormattedIter + where + Self: Iterator + Sized, + Self::Item: IntoFormat, + { + FormattedIter { + inner: self, + options: std::marker::PhantomData, + } + } +} + +impl FormattedIterExt for I where I: Iterator {} + +pub(crate) struct FormattedIter +where + Iter: Iterator, +{ + inner: Iter, + options: std::marker::PhantomData, +} + +impl Iterator for FormattedIter +where + Iter: Iterator, + Item: IntoFormat, +{ + type Item = Item::Format; + + fn next(&mut self) -> Option { + Some(self.inner.next()?.into_format()) + } +} + +impl FusedIterator for FormattedIter +where + Iter: FusedIterator, + Item: IntoFormat, +{ +} + +impl ExactSizeIterator for FormattedIter +where + Iter: Iterator + ExactSizeIterator, + Item: IntoFormat, +{ +} + +pub(crate) type YamlFormatter<'buf> = Formatter<'buf, YamlFormatContext>; + +/// Format a [YamlSyntaxNode] +pub(crate) trait FormatNodeRule +where + N: AstNode, +{ + fn fmt(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> { + if self.is_suppressed(node, f) || self.is_global_suppressed(node, f) { + return biome_formatter::write!(f, [format_suppressed_node(node.syntax())]); + } + + self.fmt_leading_comments(node, f)?; + self.fmt_fields(node, f)?; + self.fmt_dangling_comments(node, f)?; + self.fmt_trailing_comments(node, f) + } + + fn fmt_fields(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()>; + + /// Returns `true` if the node has a suppression comment and should use the same formatting as in the source document. + fn is_suppressed(&self, node: &N, f: &YamlFormatter) -> bool { + f.context().comments().is_suppressed(node.syntax()) + } + + /// Returns `true` if the node has a global suppression comment and should use the same formatting as in the source document. + fn is_global_suppressed(&self, node: &N, f: &YamlFormatter) -> bool { + f.context().comments().is_global_suppressed(node.syntax()) + } + + /// Formats the [leading comments](biome_formatter::comments#leading-comments) of the node. + /// + /// You may want to override this method if you want to manually handle the formatting of comments + /// inside of the `fmt_fields` method or customize the formatting of the leading comments. + fn fmt_leading_comments(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> { + format_leading_comments(node.syntax()).fmt(f) + } + + /// Formats the [dangling comments](biome_formatter::comments#dangling-comments) of the node. + /// + /// You should override this method if the node handled by this rule can have dangling comments because the + /// default implementation formats the dangling comments at the end of the node, which isn't ideal but ensures that + /// no comments are dropped. + /// + /// A node can have dangling comments if all its children are tokens or if all node childrens are optional. + fn fmt_dangling_comments(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> { + format_dangling_comments(node.syntax()) + .with_soft_block_indent() + .fmt(f) + } + + /// Formats the [trailing comments](biome_formatter::comments#trailing-comments) of the node. + /// + /// You may want to override this method if you want to manually handle the formatting of comments + /// inside of the `fmt_fields` method or customize the formatting of the trailing comments. + fn fmt_trailing_comments(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> { + format_trailing_comments(node.syntax()).fmt(f) + } +} + +/// Rule for formatting an bogus nodes. +pub(crate) trait FormatBogusNodeRule +where + N: AstNode, +{ + fn fmt(&self, node: &N, f: &mut YamlFormatter) -> FormatResult<()> { + format_bogus_node(node.syntax()).fmt(f) + } +} + +#[derive(Debug, Default, Clone)] +pub struct YamlFormatLanguage { + options: YamlFormatOptions, +} + +impl YamlFormatLanguage { + pub fn new(options: YamlFormatOptions) -> Self { + Self { options } + } +} + +impl FormatLanguage for YamlFormatLanguage { + type SyntaxLanguage = YamlLanguage; + type Context = YamlFormatContext; + type FormatRule = FormatYamlSyntaxNode; + + fn is_range_formatting_node(&self, _node: &SyntaxNode) -> bool { + true + } + + fn options(&self) -> &::Options { + &self.options + } + + fn create_context( + self, + root: &YamlSyntaxNode, + source_map: Option, + _delegate_fmt_embedded_nodes: bool, + ) -> Self::Context { + let comments = Comments::from_node(root, &YamlCommentStyle, source_map.as_ref()); + YamlFormatContext::new(self.options, comments).with_source_map(source_map) + } +} + +/// Format implementation specific to YAML tokens. + +#[derive(Debug, Default)] +pub(crate) struct FormatYamlSyntaxToken; + +impl FormatRule for FormatYamlSyntaxToken { + type Context = YamlFormatContext; + + fn fmt(&self, token: &YamlSyntaxToken, f: &mut Formatter) -> FormatResult<()> { + f.state_mut().track_token(token); + + self.format_skipped_token_trivia(token, f)?; + self.format_trimmed_token_trivia(token, f)?; + + Ok(()) + } +} + +impl FormatToken for FormatYamlSyntaxToken { + fn format_skipped_token_trivia( + &self, + token: &YamlSyntaxToken, + f: &mut Formatter, + ) -> FormatResult<()> { + format_skipped_token_trivia(token).fmt(f) + } +} + +impl AsFormat for YamlSyntaxToken { + type Format<'a> = FormatRefWithRule<'a, Self, FormatYamlSyntaxToken>; + + fn format(&self) -> Self::Format<'_> { + FormatRefWithRule::new(self, FormatYamlSyntaxToken) + } +} + +impl IntoFormat for YamlSyntaxToken { + type Format = FormatOwnedWithRule; + + fn into_format(self) -> Self::Format { + FormatOwnedWithRule::new(self, FormatYamlSyntaxToken) + } +} + +/// Formats a range within a file, supported by Biome +/// +/// This runs a simple heuristic to determine the initial indentation +/// level of the node based on the provided [YamlFormatOptions], which +/// must match currently the current initial of the file. Additionally, +/// because the reformatting happens only locally the resulting code +/// will be indented with the same level as the original selection, +/// even if it's a mismatch from the rest of the block the selection is in +/// +/// It returns a [Printed] result with a range corresponding to the +/// range of the input that was effectively overwritten by the formatter +pub fn format_range( + options: YamlFormatOptions, + root: &YamlSyntaxNode, + range: TextRange, +) -> FormatResult { + biome_formatter::format_range(root, range, YamlFormatLanguage::new(options)) +} + +/// Formats a YAML syntax tree. +/// +/// It returns the [Formatted] document that can be printed to a string. +pub fn format_node( + options: YamlFormatOptions, + root: &YamlSyntaxNode, +) -> FormatResult> { + biome_formatter::format_node(root, YamlFormatLanguage::new(options), false) +} + +/// Formats a single node within a file, supported by Biome. +/// +/// This runs a simple heuristic to determine the initial indentation +/// level of the node based on the provided [YamlFormatOptions], which +/// must match currently the current initial of the file. Additionally, +/// because the reformatting happens only locally the resulting code +/// will be indented with the same level as the original selection, +/// even if it's a mismatch from the rest of the block the selection is in +/// +/// Returns the [Printed] code. +pub fn format_sub_tree(options: YamlFormatOptions, root: &YamlSyntaxNode) -> FormatResult { + biome_formatter::format_sub_tree(root, YamlFormatLanguage::new(options)) +} + +#[cfg(test)] +mod tests { + use crate::context::YamlFormatOptions; + use crate::format_node; + use biome_yaml_parser::parse_yaml; + + #[test] + fn smoke_test() { + let src = r#"foo: bar"#; + let parse = parse_yaml(src); + let options = YamlFormatOptions::default(); + let formatted = format_node(options, &parse.syntax()).unwrap(); + + assert_eq!(formatted.print().unwrap().as_code(), "foo: bar"); + } +} diff --git a/crates/biome_yaml_formatter/src/prelude.rs b/crates/biome_yaml_formatter/src/prelude.rs new file mode 100644 index 000000000000..5b6ac5669663 --- /dev/null +++ b/crates/biome_yaml_formatter/src/prelude.rs @@ -0,0 +1,10 @@ +//! This module provides important and useful traits to help to format tokens and nodes +//! when implementing the [crate::FormatNodeRule] trait. + +#![allow(unused_imports)] +pub(crate) use crate::{ + AsFormat, FormatNodeRule, FormattedIterExt as _, IntoFormat, YamlFormatContext, YamlFormatter, + verbatim::format_yaml_verbatim_node as format_verbatim_node, verbatim::*, +}; +pub(crate) use biome_formatter::prelude::*; +pub(crate) use biome_rowan::{AstNode as _, AstNodeList as _, AstSeparatedList as _}; diff --git a/crates/biome_yaml_formatter/src/verbatim.rs b/crates/biome_yaml_formatter/src/verbatim.rs new file mode 100644 index 000000000000..01767299c950 --- /dev/null +++ b/crates/biome_yaml_formatter/src/verbatim.rs @@ -0,0 +1,169 @@ +use biome_formatter::format_element::tag::VerbatimKind; +use biome_formatter::formatter::Formatter; +use biome_formatter::prelude::{Tag, text}; +use biome_formatter::trivia::{FormatLeadingComments, FormatTrailingComments}; +use biome_formatter::{ + Buffer, CstFormatContext, Format, FormatContext, FormatElement, FormatResult, LINE_TERMINATORS, + normalize_newlines, +}; +use biome_rowan::{Direction, SyntaxElement, TextRange}; +use biome_yaml_syntax::YamlSyntaxNode; + +use crate::context::YamlFormatContext; + +/// "Formats" a node according to its original formatting in the source text. Being able to format +/// a node "as is" is useful if a node contains syntax errors. Formatting a node with syntax errors +/// has the risk that Biome misinterprets the structure of the code and formatting it could +/// "mess up" the developers, yet incomplete, work or accidentally introduce new syntax errors. +/// +/// You may be inclined to call `node.text` directly. However, using `text` doesn't track the nodes +/// nor its children source mapping information, resulting in incorrect source maps for this subtree. +/// +/// These nodes and tokens get tracked as [VerbatimKind::Verbatim], useful to understand +/// if these nodes still need to have their own implementation. +pub fn format_yaml_verbatim_node(node: &YamlSyntaxNode) -> FormatYamlVerbatimNode<'_> { + FormatYamlVerbatimNode { + node, + kind: VerbatimKind::Verbatim { + length: node.text_range_with_trivia().len(), + }, + format_comments: true, + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct FormatYamlVerbatimNode<'node> { + node: &'node YamlSyntaxNode, + kind: VerbatimKind, + format_comments: bool, +} + +impl Format for FormatYamlVerbatimNode<'_> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + for element in self.node.descendants_with_tokens(Direction::Next) { + match element { + SyntaxElement::Token(token) => f.state_mut().track_token(&token), + SyntaxElement::Node(node) => { + let comments = f.context().comments(); + comments.mark_suppression_checked(&node); + + for comment in comments.leading_dangling_trailing_comments(&node) { + comment.mark_formatted(); + } + } + } + } + + // The trimmed range of a node is its range without any of its leading or trailing trivia. + // Except for nodes that used to be parenthesized, the range than covers the source from the + // `(` to the `)` (the trimmed range of the parenthesized expression, not the inner expression) + let trimmed_source_range = f.context().source_map().map_or_else( + || self.node.text_trimmed_range(), + |source_map| source_map.trimmed_source_range(self.node), + ); + + f.write_element(FormatElement::Tag(Tag::StartVerbatim(self.kind)))?; + + fn source_range(f: &Formatter, range: TextRange) -> TextRange + where + Context: CstFormatContext, + { + f.context() + .source_map() + .map_or_else(|| range, |source_map| source_map.source_range(range)) + } + + // Format all leading comments that are outside of the node's source range. + if self.format_comments { + let comments = f.context().comments().clone(); + let leading_comments = comments.leading_comments(self.node); + + let outside_trimmed_range = leading_comments.partition_point(|comment| { + comment.piece().text_range().end() <= trimmed_source_range.start() + }); + + let (outside_trimmed_range, in_trimmed_range) = + leading_comments.split_at(outside_trimmed_range); + + biome_formatter::write!(f, [FormatLeadingComments::Comments(outside_trimmed_range)])?; + + for comment in in_trimmed_range { + comment.mark_formatted(); + } + } + + // Find the first skipped token trivia, if any, and include it in the verbatim range because + // the comments only format **up to** but not including skipped token trivia. + let start_source = self + .node + .first_leading_trivia() + .into_iter() + .flat_map(|trivia| trivia.pieces()) + .filter(|trivia| trivia.is_skipped()) + .map(|trivia| source_range(f, trivia.text_range()).start()) + .take_while(|start| *start < trimmed_source_range.start()) + .next() + .unwrap_or_else(|| trimmed_source_range.start()); + + let original_source = f.context().source_map().map_or_else( + || self.node.text_trimmed().to_string(), + |source_map| { + source_map + .source() + .text_slice(trimmed_source_range.cover_offset(start_source)) + .to_string() + }, + ); + + text( + &normalize_newlines(&original_source, LINE_TERMINATORS), + self.node.text_trimmed_range().start(), + ) + .fmt(f)?; + + for comment in f.context().comments().dangling_comments(self.node) { + comment.mark_formatted(); + } + + // Format all trailing comments that are outside of the trimmed range. + if self.format_comments { + let comments = f.context().comments().clone(); + + let trailing_comments = comments.trailing_comments(self.node); + + let outside_trimmed_range_start = trailing_comments.partition_point(|comment| { + source_range(f, comment.piece().text_range()).end() <= trimmed_source_range.end() + }); + + let (in_trimmed_range, outside_trimmed_range) = + trailing_comments.split_at(outside_trimmed_range_start); + + for comment in in_trimmed_range { + comment.mark_formatted(); + } + + biome_formatter::write!(f, [FormatTrailingComments::Comments(outside_trimmed_range)])?; + } + + f.write_element(FormatElement::Tag(Tag::EndVerbatim)) + } +} + +/// Formats bogus nodes. The difference between this method and `format_verbatim` is that this method +/// doesn't track nodes/tokens as [VerbatimKind::Verbatim]. They are just printed as they are. +pub fn format_bogus_node(node: &YamlSyntaxNode) -> FormatYamlVerbatimNode<'_> { + FormatYamlVerbatimNode { + node, + kind: VerbatimKind::Bogus, + format_comments: true, + } +} + +/// Format a node having formatter suppression comment applied to it +pub fn format_suppressed_node(node: &YamlSyntaxNode) -> FormatYamlVerbatimNode<'_> { + FormatYamlVerbatimNode { + node, + kind: VerbatimKind::Suppressed, + format_comments: true, + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/block_header.rs b/crates/biome_yaml_formatter/src/yaml/any/block_header.rs new file mode 100644 index 000000000000..d46032594d4d --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/block_header.rs @@ -0,0 +1,17 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlBlockHeader; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlBlockHeader; +impl FormatRule for FormatAnyYamlBlockHeader { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlBlockHeader, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlBlockHeader::YamlBlockKeepIndicator(node) => node.format().fmt(f), + AnyYamlBlockHeader::YamlBlockStripIndicator(node) => node.format().fmt(f), + AnyYamlBlockHeader::YamlBogusBlockHeader(node) => node.format().fmt(f), + AnyYamlBlockHeader::YamlIndentationIndicator(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/block_in_block_node.rs b/crates/biome_yaml_formatter/src/yaml/any/block_in_block_node.rs new file mode 100644 index 000000000000..43544b1fa4b1 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/block_in_block_node.rs @@ -0,0 +1,17 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlBlockInBlockNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlBlockInBlockNode; +impl FormatRule for FormatAnyYamlBlockInBlockNode { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlBlockInBlockNode, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlBlockInBlockNode::YamlBlockMapping(node) => node.format().fmt(f), + AnyYamlBlockInBlockNode::YamlBlockSequence(node) => node.format().fmt(f), + AnyYamlBlockInBlockNode::YamlFoldedScalar(node) => node.format().fmt(f), + AnyYamlBlockInBlockNode::YamlLiteralScalar(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/block_map_entry.rs b/crates/biome_yaml_formatter/src/yaml/any/block_map_entry.rs new file mode 100644 index 000000000000..d4d419ab9133 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/block_map_entry.rs @@ -0,0 +1,16 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlBlockMapEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlBlockMapEntry; +impl FormatRule for FormatAnyYamlBlockMapEntry { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlBlockMapEntry, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlBlockMapEntry::YamlBlockMapExplicitEntry(node) => node.format().fmt(f), + AnyYamlBlockMapEntry::YamlBlockMapImplicitEntry(node) => node.format().fmt(f), + AnyYamlBlockMapEntry::YamlBogusBlockMapEntry(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/block_node.rs b/crates/biome_yaml_formatter/src/yaml/any/block_node.rs new file mode 100644 index 000000000000..729eacfe66bb --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/block_node.rs @@ -0,0 +1,16 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlBlockNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlBlockNode; +impl FormatRule for FormatAnyYamlBlockNode { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlBlockNode, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlBlockNode::AnyYamlBlockInBlockNode(node) => node.format().fmt(f), + AnyYamlBlockNode::YamlBogusBlockNode(node) => node.format().fmt(f), + AnyYamlBlockNode::YamlFlowInBlockNode(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/block_sequence_entry.rs b/crates/biome_yaml_formatter/src/yaml/any/block_sequence_entry.rs new file mode 100644 index 000000000000..738c9ef0402a --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/block_sequence_entry.rs @@ -0,0 +1,15 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlBlockSequenceEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlBlockSequenceEntry; +impl FormatRule for FormatAnyYamlBlockSequenceEntry { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlBlockSequenceEntry, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlBlockSequenceEntry::YamlBlockSequenceEntry(node) => node.format().fmt(f), + AnyYamlBlockSequenceEntry::YamlBogus(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/document.rs b/crates/biome_yaml_formatter/src/yaml/any/document.rs new file mode 100644 index 000000000000..2c1942b78bab --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/document.rs @@ -0,0 +1,15 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlDocument; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlDocument; +impl FormatRule for FormatAnyYamlDocument { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlDocument, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlDocument::YamlBogus(node) => node.format().fmt(f), + AnyYamlDocument::YamlDocument(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/flow_map_entry.rs b/crates/biome_yaml_formatter/src/yaml/any/flow_map_entry.rs new file mode 100644 index 000000000000..f35895fb6407 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/flow_map_entry.rs @@ -0,0 +1,15 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlFlowMapEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlFlowMapEntry; +impl FormatRule for FormatAnyYamlFlowMapEntry { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlFlowMapEntry, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlFlowMapEntry::YamlFlowMapExplicitEntry(node) => node.format().fmt(f), + AnyYamlFlowMapEntry::YamlFlowMapImplicitEntry(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/flow_node.rs b/crates/biome_yaml_formatter/src/yaml/any/flow_node.rs new file mode 100644 index 000000000000..3bc3029ff2b2 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/flow_node.rs @@ -0,0 +1,17 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlFlowNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlFlowNode; +impl FormatRule for FormatAnyYamlFlowNode { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlFlowNode, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlFlowNode::YamlAliasNode(node) => node.format().fmt(f), + AnyYamlFlowNode::YamlBogusFlowNode(node) => node.format().fmt(f), + AnyYamlFlowNode::YamlFlowJsonNode(node) => node.format().fmt(f), + AnyYamlFlowNode::YamlFlowYamlNode(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/flow_sequence_entry.rs b/crates/biome_yaml_formatter/src/yaml/any/flow_sequence_entry.rs new file mode 100644 index 000000000000..86658c2e2536 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/flow_sequence_entry.rs @@ -0,0 +1,15 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlFlowSequenceEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlFlowSequenceEntry; +impl FormatRule for FormatAnyYamlFlowSequenceEntry { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlFlowSequenceEntry, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlFlowSequenceEntry::AnyYamlFlowMapEntry(node) => node.format().fmt(f), + AnyYamlFlowSequenceEntry::AnyYamlFlowNode(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/json_content.rs b/crates/biome_yaml_formatter/src/yaml/any/json_content.rs new file mode 100644 index 000000000000..1ac6b1ee0e2b --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/json_content.rs @@ -0,0 +1,17 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlJsonContent; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlJsonContent; +impl FormatRule for FormatAnyYamlJsonContent { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlJsonContent, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlJsonContent::YamlDoubleQuotedScalar(node) => node.format().fmt(f), + AnyYamlJsonContent::YamlFlowMapping(node) => node.format().fmt(f), + AnyYamlJsonContent::YamlFlowSequence(node) => node.format().fmt(f), + AnyYamlJsonContent::YamlSingleQuotedScalar(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/mapping_implicit_key.rs b/crates/biome_yaml_formatter/src/yaml/any/mapping_implicit_key.rs new file mode 100644 index 000000000000..acceaf2dae71 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/mapping_implicit_key.rs @@ -0,0 +1,15 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlMappingImplicitKey; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlMappingImplicitKey; +impl FormatRule for FormatAnyYamlMappingImplicitKey { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlMappingImplicitKey, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlMappingImplicitKey::YamlFlowJsonNode(node) => node.format().fmt(f), + AnyYamlMappingImplicitKey::YamlFlowYamlNode(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/any/mod.rs b/crates/biome_yaml_formatter/src/yaml/any/mod.rs new file mode 100644 index 000000000000..11b81ae45d25 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/mod.rs @@ -0,0 +1,14 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +pub(crate) mod block_header; +pub(crate) mod block_in_block_node; +pub(crate) mod block_map_entry; +pub(crate) mod block_node; +pub(crate) mod block_sequence_entry; +pub(crate) mod document; +pub(crate) mod flow_map_entry; +pub(crate) mod flow_node; +pub(crate) mod flow_sequence_entry; +pub(crate) mod json_content; +pub(crate) mod mapping_implicit_key; +pub(crate) mod properties_combination; diff --git a/crates/biome_yaml_formatter/src/yaml/any/properties_combination.rs b/crates/biome_yaml_formatter/src/yaml/any/properties_combination.rs new file mode 100644 index 000000000000..c88501a6113c --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/any/properties_combination.rs @@ -0,0 +1,15 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +use crate::prelude::*; +use biome_yaml_syntax::AnyYamlPropertiesCombination; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatAnyYamlPropertiesCombination; +impl FormatRule for FormatAnyYamlPropertiesCombination { + type Context = YamlFormatContext; + fn fmt(&self, node: &AnyYamlPropertiesCombination, f: &mut YamlFormatter) -> FormatResult<()> { + match node { + AnyYamlPropertiesCombination::YamlPropertiesAnchorFirst(node) => node.format().fmt(f), + AnyYamlPropertiesCombination::YamlPropertiesTagFirst(node) => node.format().fmt(f), + } + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/alias_node.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/alias_node.rs new file mode 100644 index 000000000000..d8681d63b6f4 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/alias_node.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlAliasNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlAliasNode; +impl FormatNodeRule for FormatYamlAliasNode { + fn fmt_fields(&self, node: &YamlAliasNode, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/anchor_property.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/anchor_property.rs new file mode 100644 index 000000000000..43980f6d75d4 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/anchor_property.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlAnchorProperty; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlAnchorProperty; +impl FormatNodeRule for FormatYamlAnchorProperty { + fn fmt_fields(&self, node: &YamlAnchorProperty, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_content.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_content.rs new file mode 100644 index 000000000000..f8bf5118716e --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_content.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockContent; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockContent; +impl FormatNodeRule for FormatYamlBlockContent { + fn fmt_fields(&self, node: &YamlBlockContent, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_keep_indicator.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_keep_indicator.rs new file mode 100644 index 000000000000..4b1fe2aa4a5a --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_keep_indicator.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockKeepIndicator; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockKeepIndicator; +impl FormatNodeRule for FormatYamlBlockKeepIndicator { + fn fmt_fields(&self, node: &YamlBlockKeepIndicator, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_map_explicit_entry.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_map_explicit_entry.rs new file mode 100644 index 000000000000..f9ec21c1e519 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_map_explicit_entry.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockMapExplicitEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockMapExplicitEntry; +impl FormatNodeRule for FormatYamlBlockMapExplicitEntry { + fn fmt_fields( + &self, + node: &YamlBlockMapExplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_map_implicit_entry.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_map_implicit_entry.rs new file mode 100644 index 000000000000..ba7a10d9d8d9 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_map_implicit_entry.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockMapImplicitEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockMapImplicitEntry; +impl FormatNodeRule for FormatYamlBlockMapImplicitEntry { + fn fmt_fields( + &self, + node: &YamlBlockMapImplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_mapping.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_mapping.rs new file mode 100644 index 000000000000..614d68aaaaf0 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_mapping.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockMapping; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockMapping; +impl FormatNodeRule for FormatYamlBlockMapping { + fn fmt_fields(&self, node: &YamlBlockMapping, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_sequence.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_sequence.rs new file mode 100644 index 000000000000..7a9299faf660 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_sequence.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockSequence; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockSequence; +impl FormatNodeRule for FormatYamlBlockSequence { + fn fmt_fields(&self, node: &YamlBlockSequence, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_sequence_entry.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_sequence_entry.rs new file mode 100644 index 000000000000..d68cf4a31d67 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_sequence_entry.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockSequenceEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockSequenceEntry; +impl FormatNodeRule for FormatYamlBlockSequenceEntry { + fn fmt_fields(&self, node: &YamlBlockSequenceEntry, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/block_strip_indicator.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_strip_indicator.rs new file mode 100644 index 000000000000..d4a2cbc39c1c --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/block_strip_indicator.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlBlockStripIndicator; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockStripIndicator; +impl FormatNodeRule for FormatYamlBlockStripIndicator { + fn fmt_fields( + &self, + node: &YamlBlockStripIndicator, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/directive.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/directive.rs new file mode 100644 index 000000000000..701388059162 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/directive.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlDirective; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlDirective; +impl FormatNodeRule for FormatYamlDirective { + fn fmt_fields(&self, node: &YamlDirective, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/document.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/document.rs new file mode 100644 index 000000000000..d8a632a8b20c --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/document.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlDocument; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlDocument; +impl FormatNodeRule for FormatYamlDocument { + fn fmt_fields(&self, node: &YamlDocument, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/double_quoted_scalar.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/double_quoted_scalar.rs new file mode 100644 index 000000000000..a6e549abc54b --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/double_quoted_scalar.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlDoubleQuotedScalar; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlDoubleQuotedScalar; +impl FormatNodeRule for FormatYamlDoubleQuotedScalar { + fn fmt_fields(&self, node: &YamlDoubleQuotedScalar, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_in_block_node.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_in_block_node.rs new file mode 100644 index 000000000000..42e0589a0732 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_in_block_node.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFlowInBlockNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowInBlockNode; +impl FormatNodeRule for FormatYamlFlowInBlockNode { + fn fmt_fields(&self, node: &YamlFlowInBlockNode, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_json_node.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_json_node.rs new file mode 100644 index 000000000000..5dace1e42283 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_json_node.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFlowJsonNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowJsonNode; +impl FormatNodeRule for FormatYamlFlowJsonNode { + fn fmt_fields(&self, node: &YamlFlowJsonNode, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_map_explicit_entry.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_map_explicit_entry.rs new file mode 100644 index 000000000000..cd580252d8a2 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_map_explicit_entry.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFlowMapExplicitEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowMapExplicitEntry; +impl FormatNodeRule for FormatYamlFlowMapExplicitEntry { + fn fmt_fields( + &self, + node: &YamlFlowMapExplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_map_implicit_entry.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_map_implicit_entry.rs new file mode 100644 index 000000000000..e0c701316e18 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_map_implicit_entry.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFlowMapImplicitEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowMapImplicitEntry; +impl FormatNodeRule for FormatYamlFlowMapImplicitEntry { + fn fmt_fields( + &self, + node: &YamlFlowMapImplicitEntry, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_mapping.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_mapping.rs new file mode 100644 index 000000000000..fbeaf30d4fe0 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_mapping.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFlowMapping; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowMapping; +impl FormatNodeRule for FormatYamlFlowMapping { + fn fmt_fields(&self, node: &YamlFlowMapping, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_sequence.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_sequence.rs new file mode 100644 index 000000000000..07a041e00cbf --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_sequence.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFlowSequence; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowSequence; +impl FormatNodeRule for FormatYamlFlowSequence { + fn fmt_fields(&self, node: &YamlFlowSequence, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_yaml_node.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_yaml_node.rs new file mode 100644 index 000000000000..d5151518795b --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/flow_yaml_node.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFlowYamlNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowYamlNode; +impl FormatNodeRule for FormatYamlFlowYamlNode { + fn fmt_fields(&self, node: &YamlFlowYamlNode, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/folded_scalar.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/folded_scalar.rs new file mode 100644 index 000000000000..f8feac979721 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/folded_scalar.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlFoldedScalar; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFoldedScalar; +impl FormatNodeRule for FormatYamlFoldedScalar { + fn fmt_fields(&self, node: &YamlFoldedScalar, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/indentation_indicator.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/indentation_indicator.rs new file mode 100644 index 000000000000..11313ca9bc9c --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/indentation_indicator.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlIndentationIndicator; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlIndentationIndicator; +impl FormatNodeRule for FormatYamlIndentationIndicator { + fn fmt_fields( + &self, + node: &YamlIndentationIndicator, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/literal_scalar.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/literal_scalar.rs new file mode 100644 index 000000000000..7d5421d6f195 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/literal_scalar.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlLiteralScalar; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlLiteralScalar; +impl FormatNodeRule for FormatYamlLiteralScalar { + fn fmt_fields(&self, node: &YamlLiteralScalar, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/mod.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/mod.rs new file mode 100644 index 000000000000..447ee591d836 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/mod.rs @@ -0,0 +1,31 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +pub(crate) mod alias_node; +pub(crate) mod anchor_property; +pub(crate) mod block_content; +pub(crate) mod block_keep_indicator; +pub(crate) mod block_map_explicit_entry; +pub(crate) mod block_map_implicit_entry; +pub(crate) mod block_mapping; +pub(crate) mod block_sequence; +pub(crate) mod block_sequence_entry; +pub(crate) mod block_strip_indicator; +pub(crate) mod directive; +pub(crate) mod document; +pub(crate) mod double_quoted_scalar; +pub(crate) mod flow_in_block_node; +pub(crate) mod flow_json_node; +pub(crate) mod flow_map_explicit_entry; +pub(crate) mod flow_map_implicit_entry; +pub(crate) mod flow_mapping; +pub(crate) mod flow_sequence; +pub(crate) mod flow_yaml_node; +pub(crate) mod folded_scalar; +pub(crate) mod indentation_indicator; +pub(crate) mod literal_scalar; +pub(crate) mod plain_scalar; +pub(crate) mod properties_anchor_first; +pub(crate) mod properties_tag_first; +pub(crate) mod root; +pub(crate) mod single_quoted_scalar; +pub(crate) mod tag_property; diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/plain_scalar.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/plain_scalar.rs new file mode 100644 index 000000000000..d5efa4f13e18 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/plain_scalar.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlPlainScalar; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlPlainScalar; +impl FormatNodeRule for FormatYamlPlainScalar { + fn fmt_fields(&self, node: &YamlPlainScalar, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/properties_anchor_first.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/properties_anchor_first.rs new file mode 100644 index 000000000000..d08b0a5ffbc7 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/properties_anchor_first.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlPropertiesAnchorFirst; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlPropertiesAnchorFirst; +impl FormatNodeRule for FormatYamlPropertiesAnchorFirst { + fn fmt_fields( + &self, + node: &YamlPropertiesAnchorFirst, + f: &mut YamlFormatter, + ) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/properties_tag_first.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/properties_tag_first.rs new file mode 100644 index 000000000000..5cf26c869338 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/properties_tag_first.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlPropertiesTagFirst; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlPropertiesTagFirst; +impl FormatNodeRule for FormatYamlPropertiesTagFirst { + fn fmt_fields(&self, node: &YamlPropertiesTagFirst, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/root.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/root.rs new file mode 100644 index 000000000000..1256000033f3 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/root.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlRoot; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlRoot; +impl FormatNodeRule for FormatYamlRoot { + fn fmt_fields(&self, node: &YamlRoot, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/single_quoted_scalar.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/single_quoted_scalar.rs new file mode 100644 index 000000000000..c13b0ec97e4a --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/single_quoted_scalar.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlSingleQuotedScalar; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlSingleQuotedScalar; +impl FormatNodeRule for FormatYamlSingleQuotedScalar { + fn fmt_fields(&self, node: &YamlSingleQuotedScalar, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/auxiliary/tag_property.rs b/crates/biome_yaml_formatter/src/yaml/auxiliary/tag_property.rs new file mode 100644 index 000000000000..8dedff076fba --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/auxiliary/tag_property.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_rowan::AstNode; +use biome_yaml_syntax::YamlTagProperty; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlTagProperty; +impl FormatNodeRule for FormatYamlTagProperty { + fn fmt_fields(&self, node: &YamlTagProperty, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/bogus/bogus.rs b/crates/biome_yaml_formatter/src/yaml/bogus/bogus.rs new file mode 100644 index 000000000000..4cd053824911 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/bogus/bogus.rs @@ -0,0 +1,5 @@ +use crate::FormatBogusNodeRule; +use biome_yaml_syntax::YamlBogus; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBogus; +impl FormatBogusNodeRule for FormatYamlBogus {} diff --git a/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_header.rs b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_header.rs new file mode 100644 index 000000000000..f194e8368da0 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_header.rs @@ -0,0 +1,5 @@ +use crate::FormatBogusNodeRule; +use biome_yaml_syntax::YamlBogusBlockHeader; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBogusBlockHeader; +impl FormatBogusNodeRule for FormatYamlBogusBlockHeader {} diff --git a/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_map_entry.rs b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_map_entry.rs new file mode 100644 index 000000000000..d69b6a74b407 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_map_entry.rs @@ -0,0 +1,5 @@ +use crate::FormatBogusNodeRule; +use biome_yaml_syntax::YamlBogusBlockMapEntry; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBogusBlockMapEntry; +impl FormatBogusNodeRule for FormatYamlBogusBlockMapEntry {} diff --git a/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_node.rs b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_node.rs new file mode 100644 index 000000000000..ed8d0b28a4c1 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_block_node.rs @@ -0,0 +1,5 @@ +use crate::FormatBogusNodeRule; +use biome_yaml_syntax::YamlBogusBlockNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBogusBlockNode; +impl FormatBogusNodeRule for FormatYamlBogusBlockNode {} diff --git a/crates/biome_yaml_formatter/src/yaml/bogus/bogus_flow_node.rs b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_flow_node.rs new file mode 100644 index 000000000000..a2e7fce5ceba --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/bogus/bogus_flow_node.rs @@ -0,0 +1,5 @@ +use crate::FormatBogusNodeRule; +use biome_yaml_syntax::YamlBogusFlowNode; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBogusFlowNode; +impl FormatBogusNodeRule for FormatYamlBogusFlowNode {} diff --git a/crates/biome_yaml_formatter/src/yaml/bogus/mod.rs b/crates/biome_yaml_formatter/src/yaml/bogus/mod.rs new file mode 100644 index 000000000000..1663b2d9c945 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/bogus/mod.rs @@ -0,0 +1,8 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +#[expect(clippy::module_inception)] +pub(crate) mod bogus; +pub(crate) mod bogus_block_header; +pub(crate) mod bogus_block_map_entry; +pub(crate) mod bogus_block_node; +pub(crate) mod bogus_flow_node; diff --git a/crates/biome_yaml_formatter/src/yaml/lists/block_header_list.rs b/crates/biome_yaml_formatter/src/yaml/lists/block_header_list.rs new file mode 100644 index 000000000000..e1d2828a0657 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/block_header_list.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_yaml_syntax::YamlBlockHeaderList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockHeaderList; +impl FormatRule for FormatYamlBlockHeaderList { + type Context = YamlFormatContext; + fn fmt(&self, node: &YamlBlockHeaderList, f: &mut YamlFormatter) -> FormatResult<()> { + f.join().entries(node.iter().formatted()).finish() + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/lists/block_map_entry_list.rs b/crates/biome_yaml_formatter/src/yaml/lists/block_map_entry_list.rs new file mode 100644 index 000000000000..dc5e8b6eea92 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/block_map_entry_list.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_yaml_syntax::YamlBlockMapEntryList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockMapEntryList; +impl FormatRule for FormatYamlBlockMapEntryList { + type Context = YamlFormatContext; + fn fmt(&self, node: &YamlBlockMapEntryList, f: &mut YamlFormatter) -> FormatResult<()> { + f.join().entries(node.iter().formatted()).finish() + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/lists/block_sequence_entry_list.rs b/crates/biome_yaml_formatter/src/yaml/lists/block_sequence_entry_list.rs new file mode 100644 index 000000000000..cbb2e3c4c4b1 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/block_sequence_entry_list.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_yaml_syntax::YamlBlockSequenceEntryList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlBlockSequenceEntryList; +impl FormatRule for FormatYamlBlockSequenceEntryList { + type Context = YamlFormatContext; + fn fmt(&self, node: &YamlBlockSequenceEntryList, f: &mut YamlFormatter) -> FormatResult<()> { + f.join().entries(node.iter().formatted()).finish() + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/lists/directive_list.rs b/crates/biome_yaml_formatter/src/yaml/lists/directive_list.rs new file mode 100644 index 000000000000..bf6d70ef198c --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/directive_list.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_yaml_syntax::YamlDirectiveList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlDirectiveList; +impl FormatRule for FormatYamlDirectiveList { + type Context = YamlFormatContext; + fn fmt(&self, node: &YamlDirectiveList, f: &mut YamlFormatter) -> FormatResult<()> { + f.join().entries(node.iter().formatted()).finish() + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/lists/document_list.rs b/crates/biome_yaml_formatter/src/yaml/lists/document_list.rs new file mode 100644 index 000000000000..cb1754b039af --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/document_list.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_yaml_syntax::YamlDocumentList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlDocumentList; +impl FormatRule for FormatYamlDocumentList { + type Context = YamlFormatContext; + fn fmt(&self, node: &YamlDocumentList, f: &mut YamlFormatter) -> FormatResult<()> { + f.join().entries(node.iter().formatted()).finish() + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/lists/flow_map_entry_list.rs b/crates/biome_yaml_formatter/src/yaml/lists/flow_map_entry_list.rs new file mode 100644 index 000000000000..41b137ffc718 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/flow_map_entry_list.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_yaml_syntax::YamlFlowMapEntryList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowMapEntryList; +impl FormatRule for FormatYamlFlowMapEntryList { + type Context = YamlFormatContext; + fn fmt(&self, node: &YamlFlowMapEntryList, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/lists/flow_sequence_entry_list.rs b/crates/biome_yaml_formatter/src/yaml/lists/flow_sequence_entry_list.rs new file mode 100644 index 000000000000..51296d2823fd --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/flow_sequence_entry_list.rs @@ -0,0 +1,10 @@ +use crate::prelude::*; +use biome_yaml_syntax::YamlFlowSequenceEntryList; +#[derive(Debug, Clone, Default)] +pub(crate) struct FormatYamlFlowSequenceEntryList; +impl FormatRule for FormatYamlFlowSequenceEntryList { + type Context = YamlFormatContext; + fn fmt(&self, node: &YamlFlowSequenceEntryList, f: &mut YamlFormatter) -> FormatResult<()> { + format_verbatim_node(node.syntax()).fmt(f) + } +} diff --git a/crates/biome_yaml_formatter/src/yaml/lists/mod.rs b/crates/biome_yaml_formatter/src/yaml/lists/mod.rs new file mode 100644 index 000000000000..54c57f85c0b0 --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/lists/mod.rs @@ -0,0 +1,9 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +pub(crate) mod block_header_list; +pub(crate) mod block_map_entry_list; +pub(crate) mod block_sequence_entry_list; +pub(crate) mod directive_list; +pub(crate) mod document_list; +pub(crate) mod flow_map_entry_list; +pub(crate) mod flow_sequence_entry_list; diff --git a/crates/biome_yaml_formatter/src/yaml/mod.rs b/crates/biome_yaml_formatter/src/yaml/mod.rs new file mode 100644 index 000000000000..54da8fe0418f --- /dev/null +++ b/crates/biome_yaml_formatter/src/yaml/mod.rs @@ -0,0 +1,6 @@ +//! This is a generated file. Don't modify it by hand! Run 'cargo codegen formatter' to re-generate the file. + +pub(crate) mod any; +pub(crate) mod auxiliary; +pub(crate) mod bogus; +pub(crate) mod lists; diff --git a/crates/biome_yaml_syntax/src/file_source.rs b/crates/biome_yaml_syntax/src/file_source.rs index e7f464081eb9..d1b3d90f6afa 100644 --- a/crates/biome_yaml_syntax/src/file_source.rs +++ b/crates/biome_yaml_syntax/src/file_source.rs @@ -60,7 +60,6 @@ impl YamlFileSource { /// /// [LSP spec]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem /// [VS Code spec]: https://code.visualstudio.com/docs/languages/identifiers - #[expect(dead_code)] pub fn try_from_language_id(language_id: &str) -> Result { match language_id { "yaml" => Ok(Self::yaml()), diff --git a/crates/biome_yaml_syntax/src/generated/kind.rs b/crates/biome_yaml_syntax/src/generated/kind.rs index 6c474be51452..394a992bee22 100644 --- a/crates/biome_yaml_syntax/src/generated/kind.rs +++ b/crates/biome_yaml_syntax/src/generated/kind.rs @@ -163,7 +163,7 @@ impl YamlSyntaxKind { DOC_END => "...", BACKTICK => "`", AT => "@", - EOF => "EOF", + EOF => "", FLOW_START => "start of a flow node", FLOW_END => "end of a flow node", MAPPING_START => "start of a block mapping", diff --git a/crates/biome_yaml_syntax/src/lib.rs b/crates/biome_yaml_syntax/src/lib.rs index 116bb1487b95..42bbb032418c 100644 --- a/crates/biome_yaml_syntax/src/lib.rs +++ b/crates/biome_yaml_syntax/src/lib.rs @@ -8,6 +8,7 @@ mod syntax_node; pub use self::generated::*; use biome_rowan::{AstNode, RawSyntaxKind}; pub use biome_rowan::{TextLen, TextRange, TextSize, TokenAtOffset, TriviaPieceKind, WalkEvent}; +pub use file_source::YamlFileSource; pub use syntax_node::*; impl From for YamlSyntaxKind { diff --git a/justfile b/justfile index 3195cca72d7f..65522097e9f4 100644 --- a/justfile +++ b/justfile @@ -65,8 +65,8 @@ gen-migrate: cargo run -p xtask_codegen --features configuration -- migrate-eslint # Generates the initial files for all formatter crates -gen-formatter: - cargo run -p xtask_codegen -- formatter +gen-formatter *args='': + cargo run -p xtask_codegen -- formatter {{args}} # Generates the Tailwind CSS preset for utility class sorting [working-directory: 'packages/tailwindcss-config-analyzer'] @@ -230,6 +230,14 @@ test-crate name: test-doc: cargo test --doc +# Run CommonMark conformance tests for the markdown parser +test-markdown-conformance: + cargo run -p xtask_coverage -- --suites=markdown/commonmark + +# Update the CommonMark spec.json to a specific version +update-commonmark-spec version: + ./scripts/update-commonmark-spec.sh {{version}} + # Tests a lint rule. The name of the rule needs to be camel case test-lintrule name: just _touch crates/biome_js_analyze/tests/spec_tests.rs diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index c4a7f74c5398..7420c8050865 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -2,36 +2,16 @@ import type { Transport } from "./transport"; export interface SupportsFeatureParams { features: FeatureName; + inlineConfig?: Configuration; + /** + * Features that shouldn't be enabled + */ + notRequestedFeatures?: FeatureName; path: BiomePath; projectKey: ProjectKey; skipIgnoreCheck?: boolean; } export type FeatureName = FeatureKind[]; -export type BiomePath = string; -export type ProjectKey = number; -export type FeatureKind = - | "format" - | "lint" - | "search" - | "assist" - | "debug" - | "htmlFullSupport"; -export interface FileFeaturesResult { - featuresSupported: FeaturesSupported; -} -export type FeaturesSupported = { [K in FeatureKind]?: SupportKind }; -export type SupportKind = - | "supported" - | "ignored" - | "protected" - | "featureNotEnabled" - | "fileNotSupported"; -export interface UpdateSettingsParams { - configuration: Configuration; - extendedConfigurations?: [BiomePath, Configuration][]; - projectKey: ProjectKey; - workspaceDirectory?: BiomePath; -} /** * The configuration that is contained inside the file `biome.json` */ @@ -102,6 +82,15 @@ project. By default, this is `true`. */ vcs?: VcsConfiguration; } +export type BiomePath = string; +export type ProjectKey = number; +export type FeatureKind = + | "format" + | "lint" + | "search" + | "assist" + | "debug" + | "htmlFullSupport"; export type Schema = string; export interface AssistConfiguration { /** @@ -224,6 +213,19 @@ match these patterns. */ lineWidth?: LineWidth; /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; + /** * Use any `.editorconfig` files to configure the formatter. Configuration in `biome.json` will override `.editorconfig` configuration. @@ -292,6 +294,10 @@ export interface JsConfiguration { * Assist options */ assist?: JsAssistConfiguration; + /** + * Enables support for embedding snippets. + */ + experimentalEmbeddedSnippetsEnabled?: Bool; /** * Formatting options */ @@ -437,6 +443,19 @@ export interface CssFormatterConfiguration { * The type of quotes used in CSS code. Defaults to double. */ quoteStyle?: QuoteStyle; + /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; } /** * Options that changes how the CSS linter behaves @@ -456,7 +475,8 @@ export interface CssParserConfiguration { */ allowWrongLineComments?: Bool; /** - * Enables parsing of CSS Modules specific features. + * Enables parsing of CSS Modules specific features. Enable this feature only +when your files don't end in `.module.css`. */ cssModules?: Bool; /** @@ -481,6 +501,7 @@ export type LineEnding = "lf" | "crlf" | "cr" | "auto"; The allowed range of values is 1..=320 */ export type LineWidth = number; +export type TrailingNewline = boolean; /** * Options that changes how the GraphQL linter behaves */ @@ -522,6 +543,19 @@ export interface GraphqlFormatterConfiguration { * The type of quotes used in GraphQL code. Defaults to double. */ quoteStyle?: QuoteStyle; + /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; } /** * Options that change how the GraphQL linter behaves. @@ -559,6 +593,19 @@ export interface GritFormatterConfiguration { * What's the max width of a line applied to Grit files. Defaults to 80. */ lineWidth?: LineWidth; + /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; } export interface GritLinterConfiguration { /** @@ -615,6 +662,19 @@ export interface HtmlFormatterConfiguration { * Whether void elements should be self-closed. Defaults to never. */ selfCloseVoidElements?: SelfCloseVoidElements; + /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; /** * Whether to account for whitespace sensitivity when formatting HTML (and its super languages). Defaults to "css". */ @@ -720,6 +780,19 @@ When formatting `package.json`, Biome will use `always` unless configured otherw * Print trailing commas wherever possible in multi-line comma-separated syntactic structures. Defaults to "all". */ trailingCommas?: JsTrailingCommas; + /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; } /** * Indicates the type of runtime or transformation used for interpreting JSX. @@ -803,6 +876,19 @@ When formatting `package.json`, Biome will use `always` unless configured otherw * Print trailing commas wherever possible in multi-line comma-separated syntactic structures. Defaults to "none". */ trailingCommas?: JsonTrailingCommas; + /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; } /** * Linter options specific to the JSON linter @@ -898,6 +984,11 @@ export type VcsClientKind = "git"; * A list of rules that belong to this group */ export interface Source { + /** + * Remove duplicate CSS classes. +See https://biomejs.dev/assist/actions/no-duplicate-classes + */ + noDuplicateClasses?: NoDuplicateClassesConfiguration; /** * Provides a code action to sort the imports and exports in the file using a built-in or custom order. See https://biomejs.dev/assist/actions/organize-imports @@ -913,6 +1004,11 @@ See https://biomejs.dev/assist/actions/use-sorted-attributes */ useSortedAttributes?: UseSortedAttributesConfiguration; /** + * Sort interface members by key. +See https://biomejs.dev/assist/actions/use-sorted-interface-members + */ + useSortedInterfaceMembers?: UseSortedInterfaceMembersConfiguration; + /** * Sort the keys of a JSON object in natural order. See https://biomejs.dev/assist/actions/use-sorted-keys */ @@ -984,7 +1080,8 @@ export type RuleDomain = | "vue" | "project" | "tailwind" - | "turborepo"; + | "turborepo" + | "types"; export type RuleDomainValue = "all" | "none" | "recommended"; export type SeverityOrA11y = GroupPlainConfiguration | A11y; export type SeverityOrComplexity = GroupPlainConfiguration | Complexity; @@ -1058,6 +1155,19 @@ has syntax errors * What's the max width of a line. Defaults to 80. */ lineWidth?: LineWidth; + /** + * Whether to add a trailing newline at the end of the file. + +Setting this option to `false` is **highly discouraged** because it could cause many problems with other tools: +- +- +- + +Disable the option at your own risk. + +Defaults to true. + */ + trailingNewline?: TrailingNewline; } export type OverrideGlobs = Glob[]; export interface OverrideLinterConfiguration { @@ -1074,12 +1184,18 @@ export interface OverrideLinterConfiguration { */ rules?: Rules; } +export type NoDuplicateClassesConfiguration = + | RuleAssistPlainConfiguration + | RuleAssistWithNoDuplicateClassesOptions; export type OrganizeImportsConfiguration = | RuleAssistPlainConfiguration | RuleAssistWithOrganizeImportsOptions; export type UseSortedAttributesConfiguration = | RuleAssistPlainConfiguration | RuleAssistWithUseSortedAttributesOptions; +export type UseSortedInterfaceMembersConfiguration = + | RuleAssistPlainConfiguration + | RuleAssistWithUseSortedInterfaceMembersOptions; export type UseSortedKeysConfiguration = | RuleAssistPlainConfiguration | RuleAssistWithUseSortedKeysOptions; @@ -1092,7 +1208,7 @@ export type GroupPlainConfiguration = "off" | "on" | "info" | "warn" | "error"; */ export interface A11y { /** - * Enforce that the accessKey attribute is not used on any HTML element. + * Enforce that the accesskey attribute is not used on any HTML element. See https://biomejs.dev/linter/rules/no-access-key */ noAccessKey?: NoAccessKeyConfiguration; @@ -1107,7 +1223,7 @@ See https://biomejs.dev/linter/rules/no-aria-unsupported-elements */ noAriaUnsupportedElements?: NoAriaUnsupportedElementsConfiguration; /** - * Enforce that autoFocus prop is not used on elements. + * Enforce that the autofocus attribute is not used on elements. See https://biomejs.dev/linter/rules/no-autofocus */ noAutofocus?: NoAutofocusConfiguration; @@ -1147,7 +1263,7 @@ See https://biomejs.dev/linter/rules/no-noninteractive-tabindex */ noNoninteractiveTabindex?: NoNoninteractiveTabindexConfiguration; /** - * Prevent the usage of positive integers on tabIndex property. + * Prevent the usage of positive integers on tabindex attribute. See https://biomejs.dev/linter/rules/no-positive-tabindex */ noPositiveTabindex?: NoPositiveTabindexConfiguration; @@ -1201,7 +1317,7 @@ See https://biomejs.dev/linter/rules/use-aria-props-supported-by-role */ useAriaPropsSupportedByRole?: UseAriaPropsSupportedByRoleConfiguration; /** - * Enforces the usage of the attribute type for the element button. + * Enforces the usage and validity of the attribute type for the element button. See https://biomejs.dev/linter/rules/use-button-type */ useButtonType?: UseButtonTypeConfiguration; @@ -1366,6 +1482,11 @@ See https://biomejs.dev/linter/rules/no-useless-catch */ noUselessCatch?: NoUselessCatchConfiguration; /** + * Disallow unused catch bindings. +See https://biomejs.dev/linter/rules/no-useless-catch-binding + */ + noUselessCatchBinding?: NoUselessCatchBindingConfiguration; + /** * Disallow unnecessary constructors. See https://biomejs.dev/linter/rules/no-useless-constructor */ @@ -1436,6 +1557,11 @@ See https://biomejs.dev/linter/rules/no-useless-type-constraint */ noUselessTypeConstraint?: NoUselessTypeConstraintConfiguration; /** + * Disallow the use of useless undefined. +See https://biomejs.dev/linter/rules/no-useless-undefined + */ + noUselessUndefined?: NoUselessUndefinedConfiguration; + /** * Disallow initializing variables to undefined. See https://biomejs.dev/linter/rules/no-useless-undefined-initialization */ @@ -1475,6 +1601,11 @@ See https://biomejs.dev/linter/rules/use-literal-keys */ useLiteralKeys?: UseLiteralKeysConfiguration; /** + * Enforce a maximum number of parameters in function definitions. +See https://biomejs.dev/linter/rules/use-max-params + */ + useMaxParams?: UseMaxParamsConfiguration; + /** * Disallow parseInt() and Number.parseInt() in favor of binary, octal, and hexadecimal literals. See https://biomejs.dev/linter/rules/use-numeric-literals */ @@ -1600,6 +1731,11 @@ See https://biomejs.dev/linter/rules/no-nested-component-definitions */ noNestedComponentDefinitions?: NoNestedComponentDefinitionsConfiguration; /** + * Prevent client components from being async functions. +See https://biomejs.dev/linter/rules/no-next-async-client-component + */ + noNextAsyncClientComponent?: NoNextAsyncClientComponentConfiguration; + /** * Forbid the use of Node.js builtin modules. See https://biomejs.dev/linter/rules/no-nodejs-modules */ @@ -1730,6 +1866,11 @@ See https://biomejs.dev/linter/rules/no-unreachable-super */ noUnreachableSuper?: NoUnreachableSuperConfiguration; /** + * Warn when importing non-existing exports. +See https://biomejs.dev/linter/rules/no-unresolved-imports + */ + noUnresolvedImports?: NoUnresolvedImportsConfiguration; + /** * Disallow control flow statements in finally blocks. See https://biomejs.dev/linter/rules/no-unsafe-finally */ @@ -1774,6 +1915,31 @@ See https://biomejs.dev/linter/rules/no-void-elements-with-children See https://biomejs.dev/linter/rules/no-void-type-return */ noVoidTypeReturn?: NoVoidTypeReturnConfiguration; + /** + * Enforce that Vue component data options are declared as functions. +See https://biomejs.dev/linter/rules/no-vue-data-object-declaration + */ + noVueDataObjectDeclaration?: NoVueDataObjectDeclarationConfiguration; + /** + * Disallow duplicate keys in Vue component data, methods, computed properties, and other options. +See https://biomejs.dev/linter/rules/no-vue-duplicate-keys + */ + noVueDuplicateKeys?: NoVueDuplicateKeysConfiguration; + /** + * Disallow reserved keys in Vue component data and computed properties. +See https://biomejs.dev/linter/rules/no-vue-reserved-keys + */ + noVueReservedKeys?: NoVueReservedKeysConfiguration; + /** + * Disallow reserved names to be used as props. +See https://biomejs.dev/linter/rules/no-vue-reserved-props + */ + noVueReservedProps?: NoVueReservedPropsConfiguration; + /** + * Disallow destructuring of props passed to setup in Vue projects. +See https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss + */ + noVueSetupPropsReactivityLoss?: NoVueSetupPropsReactivityLossConfiguration; /** * Enables the recommended rules for this group */ @@ -1829,6 +1995,16 @@ See https://biomejs.dev/linter/rules/use-qwik-classlist */ useQwikClasslist?: UseQwikClasslistConfiguration; /** + * Disallow use* hooks outside of component$ or other use* hooks in Qwik applications. +See https://biomejs.dev/linter/rules/use-qwik-method-usage + */ + useQwikMethodUsage?: UseQwikMethodUsageConfiguration; + /** + * Disallow unserializable expressions in Qwik dollar ($) scopes. +See https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope + */ + useQwikValidLexicalScope?: UseQwikValidLexicalScopeConfiguration; + /** * Enforce JSDoc comment lines to start with a single asterisk, except for the first one. See https://biomejs.dev/linter/rules/use-single-js-doc-asterisk */ @@ -1874,11 +2050,6 @@ See https://biomejs.dev/linter/rules/no-continue */ noContinue?: NoContinueConfiguration; /** - * Restrict imports of deprecated exports. -See https://biomejs.dev/linter/rules/no-deprecated-imports - */ - noDeprecatedImports?: NoDeprecatedImportsConfiguration; - /** * Disallow deprecated media types. See https://biomejs.dev/linter/rules/no-deprecated-media-type */ @@ -1899,11 +2070,6 @@ See https://biomejs.dev/linter/rules/no-duplicate-attributes */ noDuplicateAttributes?: NoDuplicateAttributesConfiguration; /** - * Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: "bundledDependencies", "bundleDependencies", "dependencies", "devDependencies", "overrides", "optionalDependencies", and "peerDependencies". -See https://biomejs.dev/linter/rules/no-duplicate-dependencies - */ - noDuplicateDependencies?: NoDuplicateDependenciesConfiguration; - /** * Require all enum value names to be unique. See https://biomejs.dev/linter/rules/no-duplicate-enum-value-names */ @@ -1939,11 +2105,6 @@ See https://biomejs.dev/linter/rules/no-duplicated-spread-props */ noDuplicatedSpreadProps?: NoDuplicatedSpreadPropsConfiguration; /** - * Disallow empty sources. -See https://biomejs.dev/linter/rules/no-empty-source - */ - noEmptySource?: NoEmptySourceConfiguration; - /** * Require the use of === or !== for comparison with null. See https://biomejs.dev/linter/rules/no-equals-to-null */ @@ -1979,21 +2140,11 @@ See https://biomejs.dev/linter/rules/no-hex-colors */ noHexColors?: NoHexColorsConfiguration; /** - * Prevent import cycles. -See https://biomejs.dev/linter/rules/no-import-cycles - */ - noImportCycles?: NoImportCyclesConfiguration; - /** * Disallows the usage of the unary operators ++ and --. See https://biomejs.dev/linter/rules/no-increment-decrement */ noIncrementDecrement?: NoIncrementDecrementConfiguration; /** - * Disallow string literals inside JSX elements. -See https://biomejs.dev/linter/rules/no-jsx-literals - */ - noJsxLiterals?: NoJsxLiteralsConfiguration; - /** * Disallow .bind(), arrow functions, or function expressions in JSX props. See https://biomejs.dev/linter/rules/no-jsx-props-bind */ @@ -2024,11 +2175,6 @@ See https://biomejs.dev/linter/rules/no-nested-promises */ noNestedPromises?: NoNestedPromisesConfiguration; /** - * Prevent client components from being async functions. -See https://biomejs.dev/linter/rules/no-next-async-client-component - */ - noNextAsyncClientComponent?: NoNextAsyncClientComponentConfiguration; - /** * Disallow function parameters that are only used in recursive calls. See https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion */ @@ -2039,11 +2185,6 @@ See https://biomejs.dev/linter/rules/no-proto */ noProto?: NoProtoConfiguration; /** - * Replaces usages of forwardRef with passing ref as a prop. -See https://biomejs.dev/linter/rules/no-react-forward-ref - */ - noReactForwardRef?: NoReactForwardRefConfiguration; - /** * Checks if a default export exports the same symbol as a named export. See https://biomejs.dev/linter/rules/no-redundant-default-export */ @@ -2094,66 +2235,21 @@ See https://biomejs.dev/linter/rules/no-unnecessary-conditions */ noUnnecessaryConditions?: NoUnnecessaryConditionsConfiguration; /** - * Warn when importing non-existing exports. -See https://biomejs.dev/linter/rules/no-unresolved-imports - */ - noUnresolvedImports?: NoUnresolvedImportsConfiguration; - /** - * Disallow expression statements that are neither a function call nor an assignment. -See https://biomejs.dev/linter/rules/no-unused-expressions - */ - noUnusedExpressions?: NoUnusedExpressionsConfiguration; - /** - * Disallow unused catch bindings. -See https://biomejs.dev/linter/rules/no-useless-catch-binding - */ - noUselessCatchBinding?: NoUselessCatchBindingConfiguration; - /** * Disallow redundant return statements. See https://biomejs.dev/linter/rules/no-useless-return */ noUselessReturn?: NoUselessReturnConfiguration; /** - * Disallow the use of useless undefined. -See https://biomejs.dev/linter/rules/no-useless-undefined - */ - noUselessUndefined?: NoUselessUndefinedConfiguration; - /** * Disallows using arrow functions when defining a watcher. See https://biomejs.dev/linter/rules/no-vue-arrow-func-in-watch */ noVueArrowFuncInWatch?: NoVueArrowFuncInWatchConfiguration; /** - * Enforce that Vue component data options are declared as functions. -See https://biomejs.dev/linter/rules/no-vue-data-object-declaration - */ - noVueDataObjectDeclaration?: NoVueDataObjectDeclarationConfiguration; - /** - * Disallow duplicate keys in Vue component data, methods, computed properties, and other options. -See https://biomejs.dev/linter/rules/no-vue-duplicate-keys - */ - noVueDuplicateKeys?: NoVueDuplicateKeysConfiguration; - /** * Disallow the use of Vue Options API. See https://biomejs.dev/linter/rules/no-vue-options-api */ noVueOptionsApi?: NoVueOptionsApiConfiguration; /** - * Disallow reserved keys in Vue component data and computed properties. -See https://biomejs.dev/linter/rules/no-vue-reserved-keys - */ - noVueReservedKeys?: NoVueReservedKeysConfiguration; - /** - * Disallow reserved names to be used as props. -See https://biomejs.dev/linter/rules/no-vue-reserved-props - */ - noVueReservedProps?: NoVueReservedPropsConfiguration; - /** - * Disallow destructuring of props passed to setup in Vue projects. -See https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss - */ - noVueSetupPropsReactivityLoss?: NoVueSetupPropsReactivityLossConfiguration; - /** * Disallow using v-if and v-for directives on the same element. See https://biomejs.dev/linter/rules/no-vue-v-if-with-v-for */ @@ -2173,11 +2269,6 @@ See https://biomejs.dev/linter/rules/use-await-thenable */ useAwaitThenable?: UseAwaitThenableConfiguration; /** - * Enforce consistent arrow function bodies. -See https://biomejs.dev/linter/rules/use-consistent-arrow-return - */ - useConsistentArrowReturn?: UseConsistentArrowReturnConfiguration; - /** * Disallow enums from having both number and string members. See https://biomejs.dev/linter/rules/use-consistent-enum-value-type */ @@ -2193,11 +2284,6 @@ See https://biomejs.dev/linter/rules/use-consistent-method-signatures */ useConsistentMethodSignatures?: UseConsistentMethodSignaturesConfiguration; /** - * Require the @deprecated directive to specify a deletion date. -See https://biomejs.dev/linter/rules/use-deprecated-date - */ - useDeprecatedDate?: UseDeprecatedDateConfiguration; - /** * Require destructuring from arrays and/or objects. See https://biomejs.dev/linter/rules/use-destructuring */ @@ -2248,21 +2334,6 @@ See https://biomejs.dev/linter/rules/use-lone-executable-definition */ useLoneExecutableDefinition?: UseLoneExecutableDefinitionConfiguration; /** - * Enforce a maximum number of parameters in function definitions. -See https://biomejs.dev/linter/rules/use-max-params - */ - useMaxParams?: UseMaxParamsConfiguration; - /** - * Disallow use* hooks outside of component$ or other use* hooks in Qwik applications. -See https://biomejs.dev/linter/rules/use-qwik-method-usage - */ - useQwikMethodUsage?: UseQwikMethodUsageConfiguration; - /** - * Disallow unserializable expressions in Qwik dollar ($) scopes. -See https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope - */ - useQwikValidLexicalScope?: UseQwikValidLexicalScopeConfiguration; - /** * Enforce RegExp#exec over String#match if no global flag is provided. See https://biomejs.dev/linter/rules/use-regexp-exec */ @@ -2531,6 +2602,11 @@ See https://biomejs.dev/linter/rules/no-inferrable-types */ noInferrableTypes?: NoInferrableTypesConfiguration; /** + * Disallow string literals inside JSX elements. +See https://biomejs.dev/linter/rules/no-jsx-literals + */ + noJsxLiterals?: NoJsxLiteralsConfiguration; + /** * Reports usage of "magic numbers" — numbers used directly instead of being assigned to named constants. See https://biomejs.dev/linter/rules/no-magic-numbers */ @@ -2606,7 +2682,7 @@ See https://biomejs.dev/linter/rules/no-useless-else */ noUselessElse?: NoUselessElseConfiguration; /** - * Disallow use of @value rule in css modules. + * Disallow use of @value rule in CSS modules. See https://biomejs.dev/linter/rules/no-value-at-rule */ noValueAtRule?: NoValueAtRuleConfiguration; @@ -2660,9 +2736,14 @@ See https://biomejs.dev/linter/rules/use-consistent-array-type */ useConsistentArrayType?: UseConsistentArrayTypeConfiguration; /** - * Enforce the use of new for all builtins, except String, Number and Boolean. -See https://biomejs.dev/linter/rules/use-consistent-builtin-instantiation - */ + * Enforce consistent arrow function bodies. +See https://biomejs.dev/linter/rules/use-consistent-arrow-return + */ + useConsistentArrowReturn?: UseConsistentArrowReturnConfiguration; + /** + * Enforce the use of new for all builtins, except String, Number and Boolean. +See https://biomejs.dev/linter/rules/use-consistent-builtin-instantiation + */ useConsistentBuiltinInstantiation?: UseConsistentBuiltinInstantiationConfiguration; /** * This rule enforces consistent use of curly braces inside JSX attributes and JSX children. @@ -2950,6 +3031,11 @@ See https://biomejs.dev/linter/rules/no-debugger */ noDebugger?: NoDebuggerConfiguration; /** + * Restrict imports of deprecated exports. +See https://biomejs.dev/linter/rules/no-deprecated-imports + */ + noDeprecatedImports?: NoDeprecatedImportsConfiguration; + /** * Disallow direct assignments to document.cookie. See https://biomejs.dev/linter/rules/no-document-cookie */ @@ -2985,6 +3071,11 @@ See https://biomejs.dev/linter/rules/no-duplicate-custom-properties */ noDuplicateCustomProperties?: NoDuplicateCustomPropertiesConfiguration; /** + * Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: "bundledDependencies", "bundleDependencies", "dependencies", "devDependencies", "overrides", "optionalDependencies", and "peerDependencies". +See https://biomejs.dev/linter/rules/no-duplicate-dependencies + */ + noDuplicateDependencies?: NoDuplicateDependenciesConfiguration; + /** * Disallow duplicate conditions in if-else-if chains. See https://biomejs.dev/linter/rules/no-duplicate-else-if */ @@ -3045,6 +3136,11 @@ See https://biomejs.dev/linter/rules/no-empty-interface */ noEmptyInterface?: NoEmptyInterfaceConfiguration; /** + * Disallow empty sources. +See https://biomejs.dev/linter/rules/no-empty-source + */ + noEmptySource?: NoEmptySourceConfiguration; + /** * Disallow variables from evolving into any type through reassignments. See https://biomejs.dev/linter/rules/no-evolving-types */ @@ -3110,6 +3206,11 @@ See https://biomejs.dev/linter/rules/no-import-assign */ noImportAssign?: NoImportAssignConfiguration; /** + * Prevent import cycles. +See https://biomejs.dev/linter/rules/no-import-cycles + */ + noImportCycles?: NoImportCyclesConfiguration; + /** * Disallow invalid !important within keyframe declarations. See https://biomejs.dev/linter/rules/no-important-in-keyframe */ @@ -3165,6 +3266,11 @@ See https://biomejs.dev/linter/rules/no-quickfix-biome */ noQuickfixBiome?: NoQuickfixBiomeConfiguration; /** + * Replaces usages of forwardRef with passing ref as a prop. +See https://biomejs.dev/linter/rules/no-react-forward-ref + */ + noReactForwardRef?: NoReactForwardRefConfiguration; + /** * Prevents React-specific JSX properties from being used. See https://biomejs.dev/linter/rules/no-react-specific-props */ @@ -3245,6 +3351,11 @@ See https://biomejs.dev/linter/rules/no-unsafe-negation */ noUnsafeNegation?: NoUnsafeNegationConfiguration; /** + * Disallow expression statements that are neither a function call nor an assignment. +See https://biomejs.dev/linter/rules/no-unused-expressions + */ + noUnusedExpressions?: NoUnusedExpressionsConfiguration; + /** * Disallow unnecessary escapes in string literals. See https://biomejs.dev/linter/rules/no-useless-escape-in-string */ @@ -3289,6 +3400,11 @@ See https://biomejs.dev/linter/rules/use-default-switch-clause-last */ useDefaultSwitchClauseLast?: UseDefaultSwitchClauseLastConfiguration; /** + * Require the @deprecated directive to specify a deletion date. +See https://biomejs.dev/linter/rules/use-deprecated-date + */ + useDeprecatedDate?: UseDeprecatedDateConfiguration; + /** * Enforce passing a message value when creating a built-in error. See https://biomejs.dev/linter/rules/use-error-message */ @@ -3341,6 +3457,10 @@ See https://biomejs.dev/linter/rules/use-strict-mode } export type Glob = string; export type RuleAssistPlainConfiguration = "off" | "on"; +export interface RuleAssistWithNoDuplicateClassesOptions { + level: RuleAssistPlainConfiguration; + options: NoDuplicateClassesOptions; +} export interface RuleAssistWithOrganizeImportsOptions { level: RuleAssistPlainConfiguration; options: OrganizeImportsOptions; @@ -3349,6 +3469,10 @@ export interface RuleAssistWithUseSortedAttributesOptions { level: RuleAssistPlainConfiguration; options: UseSortedAttributesOptions; } +export interface RuleAssistWithUseSortedInterfaceMembersOptions { + level: RuleAssistPlainConfiguration; + options: UseSortedInterfaceMembersOptions; +} export interface RuleAssistWithUseSortedKeysOptions { level: RuleAssistPlainConfiguration; options: UseSortedKeysOptions; @@ -3516,6 +3640,9 @@ export type NoThisInStaticConfiguration = export type NoUselessCatchConfiguration = | RulePlainConfiguration | RuleWithNoUselessCatchOptions; +export type NoUselessCatchBindingConfiguration = + | RulePlainConfiguration + | RuleWithNoUselessCatchBindingOptions; export type NoUselessConstructorConfiguration = | RulePlainConfiguration | RuleWithNoUselessConstructorOptions; @@ -3558,6 +3685,9 @@ export type NoUselessThisAliasConfiguration = export type NoUselessTypeConstraintConfiguration = | RulePlainConfiguration | RuleWithNoUselessTypeConstraintOptions; +export type NoUselessUndefinedConfiguration = + | RulePlainConfiguration + | RuleWithNoUselessUndefinedOptions; export type NoUselessUndefinedInitializationConfiguration = | RulePlainConfiguration | RuleWithNoUselessUndefinedInitializationOptions; @@ -3579,6 +3709,9 @@ export type UseIndexOfConfiguration = export type UseLiteralKeysConfiguration = | RulePlainConfiguration | RuleWithUseLiteralKeysOptions; +export type UseMaxParamsConfiguration = + | RulePlainConfiguration + | RuleWithUseMaxParamsOptions; export type UseNumericLiteralsConfiguration = | RulePlainConfiguration | RuleWithUseNumericLiteralsOptions; @@ -3651,6 +3784,9 @@ export type NoMissingVarFunctionConfiguration = export type NoNestedComponentDefinitionsConfiguration = | RulePlainConfiguration | RuleWithNoNestedComponentDefinitionsOptions; +export type NoNextAsyncClientComponentConfiguration = + | RulePlainConfiguration + | RuleWithNoNextAsyncClientComponentOptions; export type NoNodejsModulesConfiguration = | RulePlainConfiguration | RuleWithNoNodejsModulesOptions; @@ -3729,6 +3865,9 @@ export type NoUnreachableConfiguration = export type NoUnreachableSuperConfiguration = | RulePlainConfiguration | RuleWithNoUnreachableSuperOptions; +export type NoUnresolvedImportsConfiguration = + | RulePlainConfiguration + | RuleWithNoUnresolvedImportsOptions; export type NoUnsafeFinallyConfiguration = | RulePlainConfiguration | RuleWithNoUnsafeFinallyOptions; @@ -3756,6 +3895,21 @@ export type NoVoidElementsWithChildrenConfiguration = export type NoVoidTypeReturnConfiguration = | RulePlainConfiguration | RuleWithNoVoidTypeReturnOptions; +export type NoVueDataObjectDeclarationConfiguration = + | RulePlainConfiguration + | RuleWithNoVueDataObjectDeclarationOptions; +export type NoVueDuplicateKeysConfiguration = + | RulePlainConfiguration + | RuleWithNoVueDuplicateKeysOptions; +export type NoVueReservedKeysConfiguration = + | RulePlainConfiguration + | RuleWithNoVueReservedKeysOptions; +export type NoVueReservedPropsConfiguration = + | RulePlainConfiguration + | RuleWithNoVueReservedPropsOptions; +export type NoVueSetupPropsReactivityLossConfiguration = + | RulePlainConfiguration + | RuleWithNoVueSetupPropsReactivityLossOptions; export type UseExhaustiveDependenciesConfiguration = | RulePlainConfiguration | RuleWithUseExhaustiveDependenciesOptions; @@ -3786,6 +3940,12 @@ export type UseParseIntRadixConfiguration = export type UseQwikClasslistConfiguration = | RulePlainConfiguration | RuleWithUseQwikClasslistOptions; +export type UseQwikMethodUsageConfiguration = + | RulePlainConfiguration + | RuleWithUseQwikMethodUsageOptions; +export type UseQwikValidLexicalScopeConfiguration = + | RulePlainConfiguration + | RuleWithUseQwikValidLexicalScopeOptions; export type UseSingleJsDocAsteriskConfiguration = | RulePlainConfiguration | RuleWithUseSingleJsDocAsteriskOptions; @@ -3810,9 +3970,6 @@ export type NoBeforeInteractiveScriptOutsideDocumentConfiguration = export type NoContinueConfiguration = | RulePlainConfiguration | RuleWithNoContinueOptions; -export type NoDeprecatedImportsConfiguration = - | RulePlainConfiguration - | RuleWithNoDeprecatedImportsOptions; export type NoDeprecatedMediaTypeConfiguration = | RulePlainConfiguration | RuleWithNoDeprecatedMediaTypeOptions; @@ -3825,9 +3982,6 @@ export type NoDuplicateArgumentNamesConfiguration = export type NoDuplicateAttributesConfiguration = | RulePlainConfiguration | RuleWithNoDuplicateAttributesOptions; -export type NoDuplicateDependenciesConfiguration = - | RulePlainConfiguration - | RuleWithNoDuplicateDependenciesOptions; export type NoDuplicateEnumValueNamesConfiguration = | RulePlainConfiguration | RuleWithNoDuplicateEnumValueNamesOptions; @@ -3849,9 +4003,6 @@ export type NoDuplicateVariableNamesConfiguration = export type NoDuplicatedSpreadPropsConfiguration = | RulePlainConfiguration | RuleWithNoDuplicatedSpreadPropsOptions; -export type NoEmptySourceConfiguration = - | RulePlainConfiguration - | RuleWithNoEmptySourceOptions; export type NoEqualsToNullConfiguration = | RulePlainConfiguration | RuleWithNoEqualsToNullOptions; @@ -3873,15 +4024,9 @@ export type NoForInConfiguration = export type NoHexColorsConfiguration = | RulePlainConfiguration | RuleWithNoHexColorsOptions; -export type NoImportCyclesConfiguration = - | RulePlainConfiguration - | RuleWithNoImportCyclesOptions; export type NoIncrementDecrementConfiguration = | RulePlainConfiguration | RuleWithNoIncrementDecrementOptions; -export type NoJsxLiteralsConfiguration = - | RulePlainConfiguration - | RuleWithNoJsxLiteralsOptions; export type NoJsxPropsBindConfiguration = | RulePlainConfiguration | RuleWithNoJsxPropsBindOptions; @@ -3900,18 +4045,12 @@ export type NoMultiStrConfiguration = export type NoNestedPromisesConfiguration = | RulePlainConfiguration | RuleWithNoNestedPromisesOptions; -export type NoNextAsyncClientComponentConfiguration = - | RulePlainConfiguration - | RuleWithNoNextAsyncClientComponentOptions; export type NoParametersOnlyUsedInRecursionConfiguration = | RulePlainConfiguration | RuleWithNoParametersOnlyUsedInRecursionOptions; export type NoProtoConfiguration = | RulePlainConfiguration | RuleWithNoProtoOptions; -export type NoReactForwardRefConfiguration = - | RulePlainConfiguration - | RuleWithNoReactForwardRefOptions; export type NoRedundantDefaultExportConfiguration = | RulePlainConfiguration | RuleWithNoRedundantDefaultExportOptions; @@ -3942,42 +4081,15 @@ export type NoUnknownAttributeConfiguration = export type NoUnnecessaryConditionsConfiguration = | RulePlainConfiguration | RuleWithNoUnnecessaryConditionsOptions; -export type NoUnresolvedImportsConfiguration = - | RulePlainConfiguration - | RuleWithNoUnresolvedImportsOptions; -export type NoUnusedExpressionsConfiguration = - | RulePlainConfiguration - | RuleWithNoUnusedExpressionsOptions; -export type NoUselessCatchBindingConfiguration = - | RulePlainConfiguration - | RuleWithNoUselessCatchBindingOptions; export type NoUselessReturnConfiguration = | RulePlainConfiguration | RuleWithNoUselessReturnOptions; -export type NoUselessUndefinedConfiguration = - | RulePlainConfiguration - | RuleWithNoUselessUndefinedOptions; export type NoVueArrowFuncInWatchConfiguration = | RulePlainConfiguration | RuleWithNoVueArrowFuncInWatchOptions; -export type NoVueDataObjectDeclarationConfiguration = - | RulePlainConfiguration - | RuleWithNoVueDataObjectDeclarationOptions; -export type NoVueDuplicateKeysConfiguration = - | RulePlainConfiguration - | RuleWithNoVueDuplicateKeysOptions; export type NoVueOptionsApiConfiguration = | RulePlainConfiguration | RuleWithNoVueOptionsApiOptions; -export type NoVueReservedKeysConfiguration = - | RulePlainConfiguration - | RuleWithNoVueReservedKeysOptions; -export type NoVueReservedPropsConfiguration = - | RulePlainConfiguration - | RuleWithNoVueReservedPropsOptions; -export type NoVueSetupPropsReactivityLossConfiguration = - | RulePlainConfiguration - | RuleWithNoVueSetupPropsReactivityLossOptions; export type NoVueVIfWithVForConfiguration = | RulePlainConfiguration | RuleWithNoVueVIfWithVForOptions; @@ -3987,9 +4099,6 @@ export type UseArraySortCompareConfiguration = export type UseAwaitThenableConfiguration = | RulePlainConfiguration | RuleWithUseAwaitThenableOptions; -export type UseConsistentArrowReturnConfiguration = - | RulePlainConfiguration - | RuleWithUseConsistentArrowReturnOptions; export type UseConsistentEnumValueTypeConfiguration = | RulePlainConfiguration | RuleWithUseConsistentEnumValueTypeOptions; @@ -3999,9 +4108,6 @@ export type UseConsistentGraphqlDescriptionsConfiguration = export type UseConsistentMethodSignaturesConfiguration = | RulePlainConfiguration | RuleWithUseConsistentMethodSignaturesOptions; -export type UseDeprecatedDateConfiguration = - | RulePlainConfiguration - | RuleWithUseDeprecatedDateOptions; export type UseDestructuringConfiguration = | RulePlainConfiguration | RuleWithUseDestructuringOptions; @@ -4032,15 +4138,6 @@ export type UseLoneAnonymousOperationConfiguration = export type UseLoneExecutableDefinitionConfiguration = | RulePlainConfiguration | RuleWithUseLoneExecutableDefinitionOptions; -export type UseMaxParamsConfiguration = - | RulePlainConfiguration - | RuleWithUseMaxParamsOptions; -export type UseQwikMethodUsageConfiguration = - | RulePlainConfiguration - | RuleWithUseQwikMethodUsageOptions; -export type UseQwikValidLexicalScopeConfiguration = - | RulePlainConfiguration - | RuleWithUseQwikValidLexicalScopeOptions; export type UseRegexpExecConfiguration = | RulePlainConfiguration | RuleWithUseRegexpExecOptions; @@ -4188,6 +4285,9 @@ export type NoImplicitBooleanConfiguration = export type NoInferrableTypesConfiguration = | RulePlainConfiguration | RuleWithNoInferrableTypesOptions; +export type NoJsxLiteralsConfiguration = + | RulePlainConfiguration + | RuleWithNoJsxLiteralsOptions; export type NoMagicNumbersConfiguration = | RulePlainConfiguration | RuleWithNoMagicNumbersOptions; @@ -4263,6 +4363,9 @@ export type UseComponentExportOnlyModulesConfiguration = export type UseConsistentArrayTypeConfiguration = | RulePlainConfiguration | RuleWithUseConsistentArrayTypeOptions; +export type UseConsistentArrowReturnConfiguration = + | RulePlainConfiguration + | RuleWithUseConsistentArrowReturnOptions; export type UseConsistentBuiltinInstantiationConfiguration = | RulePlainConfiguration | RuleWithUseConsistentBuiltinInstantiationOptions; @@ -4434,6 +4537,9 @@ export type NoControlCharactersInRegexConfiguration = export type NoDebuggerConfiguration = | RulePlainConfiguration | RuleWithNoDebuggerOptions; +export type NoDeprecatedImportsConfiguration = + | RulePlainConfiguration + | RuleWithNoDeprecatedImportsOptions; export type NoDocumentCookieConfiguration = | RulePlainConfiguration | RuleWithNoDocumentCookieOptions; @@ -4455,6 +4561,9 @@ export type NoDuplicateClassMembersConfiguration = export type NoDuplicateCustomPropertiesConfiguration = | RulePlainConfiguration | RuleWithNoDuplicateCustomPropertiesOptions; +export type NoDuplicateDependenciesConfiguration = + | RulePlainConfiguration + | RuleWithNoDuplicateDependenciesOptions; export type NoDuplicateElseIfConfiguration = | RulePlainConfiguration | RuleWithNoDuplicateElseIfOptions; @@ -4491,6 +4600,9 @@ export type NoEmptyBlockStatementsConfiguration = export type NoEmptyInterfaceConfiguration = | RulePlainConfiguration | RuleWithNoEmptyInterfaceOptions; +export type NoEmptySourceConfiguration = + | RulePlainConfiguration + | RuleWithNoEmptySourceOptions; export type NoEvolvingTypesConfiguration = | RulePlainConfiguration | RuleWithNoEvolvingTypesOptions; @@ -4530,6 +4642,9 @@ export type NoImplicitAnyLetConfiguration = export type NoImportAssignConfiguration = | RulePlainConfiguration | RuleWithNoImportAssignOptions; +export type NoImportCyclesConfiguration = + | RulePlainConfiguration + | RuleWithNoImportCyclesOptions; export type NoImportantInKeyframeConfiguration = | RulePlainConfiguration | RuleWithNoImportantInKeyframeOptions; @@ -4563,6 +4678,9 @@ export type NoPrototypeBuiltinsConfiguration = export type NoQuickfixBiomeConfiguration = | RulePlainConfiguration | RuleWithNoQuickfixBiomeOptions; +export type NoReactForwardRefConfiguration = + | RulePlainConfiguration + | RuleWithNoReactForwardRefOptions; export type NoReactSpecificPropsConfiguration = | RulePlainConfiguration | RuleWithNoReactSpecificPropsOptions; @@ -4611,6 +4729,9 @@ export type NoUnsafeDeclarationMergingConfiguration = export type NoUnsafeNegationConfiguration = | RulePlainConfiguration | RuleWithNoUnsafeNegationOptions; +export type NoUnusedExpressionsConfiguration = + | RulePlainConfiguration + | RuleWithNoUnusedExpressionsOptions; export type NoUselessEscapeInStringConfiguration = | RulePlainConfiguration | RuleWithNoUselessEscapeInStringOptions; @@ -4633,6 +4754,9 @@ export type UseBiomeIgnoreFolderConfiguration = export type UseDefaultSwitchClauseLastConfiguration = | RulePlainConfiguration | RuleWithUseDefaultSwitchClauseLastOptions; +export type UseDeprecatedDateConfiguration = + | RulePlainConfiguration + | RuleWithUseDeprecatedDateOptions; export type UseErrorMessageConfiguration = | RulePlainConfiguration | RuleWithUseErrorMessageOptions; @@ -4663,6 +4787,16 @@ export type UseStaticResponseMethodsConfiguration = export type UseStrictModeConfiguration = | RulePlainConfiguration | RuleWithUseStrictModeOptions; +export interface NoDuplicateClassesOptions { + /** + * Additional attributes that will be sorted. + */ + attributes?: string[]; + /** + * Names of the functions or tagged templates that will be sorted. + */ + functions?: string[]; +} export interface OrganizeImportsOptions { groups?: ImportGroups; identifierOrder?: SortOrder; @@ -4670,7 +4804,14 @@ export interface OrganizeImportsOptions { export interface UseSortedAttributesOptions { sortOrder?: SortOrder; } +export type UseSortedInterfaceMembersOptions = {}; export interface UseSortedKeysOptions { + /** + * When enabled, groups object keys by their value's nesting depth before sorting. +Simple values (primitives, single-line arrays, single-line objects) are sorted first, +followed by nested values (multi-line objects, multi-line arrays). + */ + groupByNesting?: boolean; sortOrder?: SortOrder; } export type UseSortedPropertiesOptions = {}; @@ -4910,6 +5051,11 @@ export interface RuleWithNoUselessCatchOptions { level: RulePlainConfiguration; options?: NoUselessCatchOptions; } +export interface RuleWithNoUselessCatchBindingOptions { + fix?: FixKind; + level: RulePlainConfiguration; + options?: NoUselessCatchBindingOptions; +} export interface RuleWithNoUselessConstructorOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -4979,6 +5125,11 @@ export interface RuleWithNoUselessTypeConstraintOptions { level: RulePlainConfiguration; options?: NoUselessTypeConstraintOptions; } +export interface RuleWithNoUselessUndefinedOptions { + fix?: FixKind; + level: RulePlainConfiguration; + options?: NoUselessUndefinedOptions; +} export interface RuleWithNoUselessUndefinedInitializationOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -5013,6 +5164,10 @@ export interface RuleWithUseLiteralKeysOptions { level: RulePlainConfiguration; options?: UseLiteralKeysOptions; } +export interface RuleWithUseMaxParamsOptions { + level: RulePlainConfiguration; + options?: UseMaxParamsOptions; +} export interface RuleWithUseNumericLiteralsOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -5119,6 +5274,10 @@ export interface RuleWithNoNestedComponentDefinitionsOptions { level: RulePlainConfiguration; options?: NoNestedComponentDefinitionsOptions; } +export interface RuleWithNoNextAsyncClientComponentOptions { + level: RulePlainConfiguration; + options?: NoNextAsyncClientComponentOptions; +} export interface RuleWithNoNodejsModulesOptions { level: RulePlainConfiguration; options?: NoNodejsModulesOptions; @@ -5227,6 +5386,10 @@ export interface RuleWithNoUnreachableSuperOptions { level: RulePlainConfiguration; options?: NoUnreachableSuperOptions; } +export interface RuleWithNoUnresolvedImportsOptions { + level: RulePlainConfiguration; + options?: NoUnresolvedImportsOptions; +} export interface RuleWithNoUnsafeFinallyOptions { level: RulePlainConfiguration; options?: NoUnsafeFinallyOptions; @@ -5269,6 +5432,27 @@ export interface RuleWithNoVoidTypeReturnOptions { level: RulePlainConfiguration; options?: NoVoidTypeReturnOptions; } +export interface RuleWithNoVueDataObjectDeclarationOptions { + fix?: FixKind; + level: RulePlainConfiguration; + options?: NoVueDataObjectDeclarationOptions; +} +export interface RuleWithNoVueDuplicateKeysOptions { + level: RulePlainConfiguration; + options?: NoVueDuplicateKeysOptions; +} +export interface RuleWithNoVueReservedKeysOptions { + level: RulePlainConfiguration; + options?: NoVueReservedKeysOptions; +} +export interface RuleWithNoVueReservedPropsOptions { + level: RulePlainConfiguration; + options?: NoVueReservedPropsOptions; +} +export interface RuleWithNoVueSetupPropsReactivityLossOptions { + level: RulePlainConfiguration; + options?: NoVueSetupPropsReactivityLossOptions; +} export interface RuleWithUseExhaustiveDependenciesOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -5315,6 +5499,14 @@ export interface RuleWithUseQwikClasslistOptions { level: RulePlainConfiguration; options?: UseQwikClasslistOptions; } +export interface RuleWithUseQwikMethodUsageOptions { + level: RulePlainConfiguration; + options?: UseQwikMethodUsageOptions; +} +export interface RuleWithUseQwikValidLexicalScopeOptions { + level: RulePlainConfiguration; + options?: UseQwikValidLexicalScopeOptions; +} export interface RuleWithUseSingleJsDocAsteriskOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -5349,10 +5541,6 @@ export interface RuleWithNoContinueOptions { level: RulePlainConfiguration; options?: NoContinueOptions; } -export interface RuleWithNoDeprecatedImportsOptions { - level: RulePlainConfiguration; - options?: NoDeprecatedImportsOptions; -} export interface RuleWithNoDeprecatedMediaTypeOptions { level: RulePlainConfiguration; options?: NoDeprecatedMediaTypeOptions; @@ -5370,10 +5558,6 @@ export interface RuleWithNoDuplicateAttributesOptions { level: RulePlainConfiguration; options?: NoDuplicateAttributesOptions; } -export interface RuleWithNoDuplicateDependenciesOptions { - level: RulePlainConfiguration; - options?: NoDuplicateDependenciesOptions; -} export interface RuleWithNoDuplicateEnumValueNamesOptions { level: RulePlainConfiguration; options?: NoDuplicateEnumValueNamesOptions; @@ -5402,10 +5586,6 @@ export interface RuleWithNoDuplicatedSpreadPropsOptions { level: RulePlainConfiguration; options?: NoDuplicatedSpreadPropsOptions; } -export interface RuleWithNoEmptySourceOptions { - level: RulePlainConfiguration; - options?: NoEmptySourceOptions; -} export interface RuleWithNoEqualsToNullOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -5436,18 +5616,10 @@ export interface RuleWithNoHexColorsOptions { level: RulePlainConfiguration; options?: NoHexColorsOptions; } -export interface RuleWithNoImportCyclesOptions { - level: RulePlainConfiguration; - options?: NoImportCyclesOptions; -} export interface RuleWithNoIncrementDecrementOptions { level: RulePlainConfiguration; options?: NoIncrementDecrementOptions; } -export interface RuleWithNoJsxLiteralsOptions { - level: RulePlainConfiguration; - options?: NoJsxLiteralsOptions; -} export interface RuleWithNoJsxPropsBindOptions { level: RulePlainConfiguration; options?: NoJsxPropsBindOptions; @@ -5473,10 +5645,6 @@ export interface RuleWithNoNestedPromisesOptions { level: RulePlainConfiguration; options?: NoNestedPromisesOptions; } -export interface RuleWithNoNextAsyncClientComponentOptions { - level: RulePlainConfiguration; - options?: NoNextAsyncClientComponentOptions; -} export interface RuleWithNoParametersOnlyUsedInRecursionOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -5486,11 +5654,6 @@ export interface RuleWithNoProtoOptions { level: RulePlainConfiguration; options?: NoProtoOptions; } -export interface RuleWithNoReactForwardRefOptions { - fix?: FixKind; - level: RulePlainConfiguration; - options?: NoReactForwardRefOptions; -} export interface RuleWithNoRedundantDefaultExportOptions { level: RulePlainConfiguration; options?: NoRedundantDefaultExportOptions; @@ -5531,59 +5694,20 @@ export interface RuleWithNoUnnecessaryConditionsOptions { level: RulePlainConfiguration; options?: NoUnnecessaryConditionsOptions; } -export interface RuleWithNoUnresolvedImportsOptions { - level: RulePlainConfiguration; - options?: NoUnresolvedImportsOptions; -} -export interface RuleWithNoUnusedExpressionsOptions { - level: RulePlainConfiguration; - options?: NoUnusedExpressionsOptions; -} -export interface RuleWithNoUselessCatchBindingOptions { - fix?: FixKind; - level: RulePlainConfiguration; - options?: NoUselessCatchBindingOptions; -} export interface RuleWithNoUselessReturnOptions { fix?: FixKind; level: RulePlainConfiguration; options?: NoUselessReturnOptions; } -export interface RuleWithNoUselessUndefinedOptions { - fix?: FixKind; - level: RulePlainConfiguration; - options?: NoUselessUndefinedOptions; -} export interface RuleWithNoVueArrowFuncInWatchOptions { fix?: FixKind; level: RulePlainConfiguration; options?: NoVueArrowFuncInWatchOptions; } -export interface RuleWithNoVueDataObjectDeclarationOptions { - fix?: FixKind; - level: RulePlainConfiguration; - options?: NoVueDataObjectDeclarationOptions; -} -export interface RuleWithNoVueDuplicateKeysOptions { - level: RulePlainConfiguration; - options?: NoVueDuplicateKeysOptions; -} export interface RuleWithNoVueOptionsApiOptions { level: RulePlainConfiguration; options?: NoVueOptionsApiOptions; } -export interface RuleWithNoVueReservedKeysOptions { - level: RulePlainConfiguration; - options?: NoVueReservedKeysOptions; -} -export interface RuleWithNoVueReservedPropsOptions { - level: RulePlainConfiguration; - options?: NoVueReservedPropsOptions; -} -export interface RuleWithNoVueSetupPropsReactivityLossOptions { - level: RulePlainConfiguration; - options?: NoVueSetupPropsReactivityLossOptions; -} export interface RuleWithNoVueVIfWithVForOptions { level: RulePlainConfiguration; options?: NoVueVIfWithVForOptions; @@ -5596,11 +5720,6 @@ export interface RuleWithUseAwaitThenableOptions { level: RulePlainConfiguration; options?: UseAwaitThenableOptions; } -export interface RuleWithUseConsistentArrowReturnOptions { - fix?: FixKind; - level: RulePlainConfiguration; - options?: UseConsistentArrowReturnOptions; -} export interface RuleWithUseConsistentEnumValueTypeOptions { level: RulePlainConfiguration; options?: UseConsistentEnumValueTypeOptions; @@ -5611,11 +5730,7 @@ export interface RuleWithUseConsistentGraphqlDescriptionsOptions { } export interface RuleWithUseConsistentMethodSignaturesOptions { level: RulePlainConfiguration; - options?: UseConsistentMethodSignaturesOptions; -} -export interface RuleWithUseDeprecatedDateOptions { - level: RulePlainConfiguration; - options?: UseDeprecatedDateOptions; + options?: UseConsistentMethodSignaturesOptions; } export interface RuleWithUseDestructuringOptions { level: RulePlainConfiguration; @@ -5658,18 +5773,6 @@ export interface RuleWithUseLoneExecutableDefinitionOptions { level: RulePlainConfiguration; options?: UseLoneExecutableDefinitionOptions; } -export interface RuleWithUseMaxParamsOptions { - level: RulePlainConfiguration; - options?: UseMaxParamsOptions; -} -export interface RuleWithUseQwikMethodUsageOptions { - level: RulePlainConfiguration; - options?: UseQwikMethodUsageOptions; -} -export interface RuleWithUseQwikValidLexicalScopeOptions { - level: RulePlainConfiguration; - options?: UseQwikValidLexicalScopeOptions; -} export interface RuleWithUseRegexpExecOptions { level: RulePlainConfiguration; options?: UseRegexpExecOptions; @@ -5882,6 +5985,10 @@ export interface RuleWithNoInferrableTypesOptions { level: RulePlainConfiguration; options?: NoInferrableTypesOptions; } +export interface RuleWithNoJsxLiteralsOptions { + level: RulePlainConfiguration; + options?: NoJsxLiteralsOptions; +} export interface RuleWithNoMagicNumbersOptions { level: RulePlainConfiguration; options?: NoMagicNumbersOptions; @@ -5997,6 +6104,11 @@ export interface RuleWithUseConsistentArrayTypeOptions { level: RulePlainConfiguration; options?: UseConsistentArrayTypeOptions; } +export interface RuleWithUseConsistentArrowReturnOptions { + fix?: FixKind; + level: RulePlainConfiguration; + options?: UseConsistentArrowReturnOptions; +} export interface RuleWithUseConsistentBuiltinInstantiationOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -6260,6 +6372,10 @@ export interface RuleWithNoDebuggerOptions { level: RulePlainConfiguration; options?: NoDebuggerOptions; } +export interface RuleWithNoDeprecatedImportsOptions { + level: RulePlainConfiguration; + options?: NoDeprecatedImportsOptions; +} export interface RuleWithNoDocumentCookieOptions { level: RulePlainConfiguration; options?: NoDocumentCookieOptions; @@ -6289,6 +6405,10 @@ export interface RuleWithNoDuplicateCustomPropertiesOptions { level: RulePlainConfiguration; options?: NoDuplicateCustomPropertiesOptions; } +export interface RuleWithNoDuplicateDependenciesOptions { + level: RulePlainConfiguration; + options?: NoDuplicateDependenciesOptions; +} export interface RuleWithNoDuplicateElseIfOptions { level: RulePlainConfiguration; options?: NoDuplicateElseIfOptions; @@ -6338,6 +6458,10 @@ export interface RuleWithNoEmptyInterfaceOptions { level: RulePlainConfiguration; options?: NoEmptyInterfaceOptions; } +export interface RuleWithNoEmptySourceOptions { + level: RulePlainConfiguration; + options?: NoEmptySourceOptions; +} export interface RuleWithNoEvolvingTypesOptions { level: RulePlainConfiguration; options?: NoEvolvingTypesOptions; @@ -6394,6 +6518,10 @@ export interface RuleWithNoImportAssignOptions { level: RulePlainConfiguration; options?: NoImportAssignOptions; } +export interface RuleWithNoImportCyclesOptions { + level: RulePlainConfiguration; + options?: NoImportCyclesOptions; +} export interface RuleWithNoImportantInKeyframeOptions { level: RulePlainConfiguration; options?: NoImportantInKeyframeOptions; @@ -6443,6 +6571,11 @@ export interface RuleWithNoQuickfixBiomeOptions { level: RulePlainConfiguration; options?: NoQuickfixBiomeOptions; } +export interface RuleWithNoReactForwardRefOptions { + fix?: FixKind; + level: RulePlainConfiguration; + options?: NoReactForwardRefOptions; +} export interface RuleWithNoReactSpecificPropsOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -6513,6 +6646,10 @@ export interface RuleWithNoUnsafeNegationOptions { level: RulePlainConfiguration; options?: NoUnsafeNegationOptions; } +export interface RuleWithNoUnusedExpressionsOptions { + level: RulePlainConfiguration; + options?: NoUnusedExpressionsOptions; +} export interface RuleWithNoUselessEscapeInStringOptions { fix?: FixKind; level: RulePlainConfiguration; @@ -6548,6 +6685,10 @@ export interface RuleWithUseDefaultSwitchClauseLastOptions { level: RulePlainConfiguration; options?: UseDefaultSwitchClauseLastOptions; } +export interface RuleWithUseDeprecatedDateOptions { + level: RulePlainConfiguration; + options?: UseDeprecatedDateOptions; +} export interface RuleWithUseErrorMessageOptions { level: RulePlainConfiguration; options?: UseErrorMessageOptions; @@ -6702,6 +6843,11 @@ export type NoImportantStylesOptions = {}; export type NoStaticOnlyClassOptions = {}; export type NoThisInStaticOptions = {}; export type NoUselessCatchOptions = {}; +/** + * Options for the `noUselessCatchBinding` rule. +Currently empty; reserved for future extensions (e.g. allowlist of names). + */ +export type NoUselessCatchBindingOptions = {}; export type NoUselessConstructorOptions = {}; export type NoUselessContinueOptions = {}; export type NoUselessEmptyExportOptions = {}; @@ -6716,6 +6862,7 @@ export type NoUselessSwitchCaseOptions = {}; export type NoUselessTernaryOptions = {}; export type NoUselessThisAliasOptions = {}; export type NoUselessTypeConstraintOptions = {}; +export type NoUselessUndefinedOptions = {}; export type NoUselessUndefinedInitializationOptions = {}; export type NoVoidOptions = {}; export type UseArrowFunctionOptions = {}; @@ -6723,6 +6870,12 @@ export type UseDateNowOptions = {}; export type UseFlatMapOptions = {}; export type UseIndexOfOptions = {}; export type UseLiteralKeysOptions = {}; +export interface UseMaxParamsOptions { + /** + * Maximum number of parameters allowed (default: 4) + */ + max?: number; +} export type UseNumericLiteralsOptions = {}; export type UseOptionalChainOptions = {}; export type UseRegexLiteralsOptions = {}; @@ -6747,6 +6900,7 @@ export type NoInvalidPositionAtImportRuleOptions = {}; export type NoInvalidUseBeforeDeclarationOptions = {}; export type NoMissingVarFunctionOptions = {}; export type NoNestedComponentDefinitionsOptions = {}; +export type NoNextAsyncClientComponentOptions = {}; export type NoNodejsModulesOptions = {}; export type NoNonoctalDecimalEscapeOptions = {}; export type NoPrecisionLossOptions = {}; @@ -6794,16 +6948,37 @@ export interface NoUndeclaredVariablesOptions { */ checkTypes?: boolean; } -export type NoUnknownFunctionOptions = {}; +export interface NoUnknownFunctionOptions { + /** + * A list of unknown function names to ignore (case-insensitive). + */ + ignore?: string[]; +} export type NoUnknownMediaFeatureNameOptions = {}; -export type NoUnknownPropertyOptions = {}; -export type NoUnknownPseudoClassOptions = {}; -export type NoUnknownPseudoElementOptions = {}; +export interface NoUnknownPropertyOptions { + /** + * A list of unknown property names to ignore (case-insensitive). + */ + ignore?: string[]; +} +export interface NoUnknownPseudoClassOptions { + /** + * A list of unknown pseudo-class names to ignore (case-insensitive). + */ + ignore?: string[]; +} +export interface NoUnknownPseudoElementOptions { + /** + * A list of unknown pseudo-element names to ignore (case-insensitive). + */ + ignore?: string[]; +} export type NoUnknownTypeSelectorOptions = {}; export type NoUnknownUnitOptions = {}; export type NoUnmatchableAnbSelectorOptions = {}; export type NoUnreachableOptions = {}; export type NoUnreachableSuperOptions = {}; +export type NoUnresolvedImportsOptions = {}; export type NoUnsafeFinallyOptions = {}; export type NoUnsafeOptionalChainingOptions = {}; export interface NoUnusedFunctionParametersOptions { @@ -6823,6 +6998,11 @@ export interface NoUnusedVariablesOptions { } export type NoVoidElementsWithChildrenOptions = {}; export type NoVoidTypeReturnOptions = {}; +export type NoVueDataObjectDeclarationOptions = {}; +export type NoVueDuplicateKeysOptions = {}; +export type NoVueReservedKeysOptions = {}; +export type NoVueReservedPropsOptions = {}; +export type NoVueSetupPropsReactivityLossOptions = {}; export interface UseExhaustiveDependenciesOptions { /** * List of hooks of which the dependencies should be validated. @@ -6838,9 +7018,20 @@ export interface UseExhaustiveDependenciesOptions { reportUnnecessaryDependencies?: boolean; } export type UseGraphqlNamedOperationsOptions = {}; -export type UseHookAtTopLevelOptions = {}; +export interface UseHookAtTopLevelOptions { + /** + * List of function names that should not be treated as hooks. +Functions in this list will be ignored by the rule even if they follow the `use*` naming convention. + */ + ignore?: string[]; +} export type UseImageSizeOptions = null; export interface UseImportExtensionsOptions { + /** + * A map of file extensions to their suggested replacements. +For example, `{"ts": "js"}` would suggest `.js` extensions for TypeScript imports. + */ + extensionMappings?: Record; /** * If `true`, the suggested extension is always `.js` regardless of what extension the source file has in your project. @@ -6857,6 +7048,8 @@ export interface UseJsxKeyInIterableOptions { } export type UseParseIntRadixOptions = {}; export type UseQwikClasslistOptions = {}; +export type UseQwikMethodUsageOptions = {}; +export type UseQwikValidLexicalScopeOptions = {}; export type UseSingleJsDocAsteriskOptions = {}; export interface UseUniqueElementIdsOptions { /** @@ -6876,7 +7069,6 @@ export interface NoAmbiguousAnchorTextOptions { } export type NoBeforeInteractiveScriptOutsideDocumentOptions = {}; export type NoContinueOptions = {}; -export type NoDeprecatedImportsOptions = {}; export interface NoDeprecatedMediaTypeOptions { /** * Media types to allow (case-insensitive). @@ -6886,7 +7078,6 @@ export interface NoDeprecatedMediaTypeOptions { export type NoDivRegexOptions = {}; export type NoDuplicateArgumentNamesOptions = {}; export type NoDuplicateAttributesOptions = {}; -export type NoDuplicateDependenciesOptions = {}; export type NoDuplicateEnumValueNamesOptions = {}; export type NoDuplicateEnumValuesOptions = {}; export type NoDuplicateFieldDefinitionNamesOptions = {}; @@ -6894,12 +7085,6 @@ export type NoDuplicateGraphqlOperationNameOptions = {}; export type NoDuplicateInputFieldNamesOptions = {}; export type NoDuplicateVariableNamesOptions = {}; export type NoDuplicatedSpreadPropsOptions = {}; -export interface NoEmptySourceOptions { - /** - * Whether comments are considered meaningful - */ - allowComments?: boolean; -} export type NoEqualsToNullOptions = {}; export interface NoExcessiveClassesPerFileOptions { /** @@ -6921,45 +7106,20 @@ export type NoFloatingClassesOptions = {}; export type NoFloatingPromisesOptions = {}; export type NoForInOptions = {}; export type NoHexColorsOptions = {}; -export interface NoImportCyclesOptions { - /** - * Ignores type-only imports when finding an import cycle. A type-only import (`import type`) -will be removed by the compiler, so it cuts an import cycle at runtime. Note that named type -imports (`import { type Foo }`) aren't considered as type-only because it's not removed by -the compiler if the `verbatimModuleSyntax` option is enabled. Enabled by default. - */ - ignoreTypes?: boolean; -} export interface NoIncrementDecrementOptions { /** * Allows unary operators ++ and -- in the afterthought (final expression) of a for loop. */ allowForLoopAfterthoughts?: boolean; } -export interface NoJsxLiteralsOptions { - /** - * An array of strings that won't trigger the rule. Whitespaces are taken into consideration - */ - allowedStrings?: string[]; - /** - * When enabled, strings inside props are always ignored - */ - ignoreProps?: boolean; - /** - * When enabled, also flag string literals inside JSX expressions and attributes - */ - noStrings?: boolean; -} export type NoJsxPropsBindOptions = {}; export type NoLeakedRenderOptions = {}; export type NoMisusedPromisesOptions = {}; export type NoMultiAssignOptions = {}; export type NoMultiStrOptions = {}; export type NoNestedPromisesOptions = {}; -export type NoNextAsyncClientComponentOptions = {}; export type NoParametersOnlyUsedInRecursionOptions = {}; export type NoProtoOptions = {}; -export type NoReactForwardRefOptions = {}; export type NoRedundantDefaultExportOptions = {}; export type NoReturnAssignOptions = {}; export interface NoRootTypeOptions { @@ -6986,40 +7146,12 @@ export interface NoUnknownAttributeOptions { ignore?: string[]; } export type NoUnnecessaryConditionsOptions = {}; -export type NoUnresolvedImportsOptions = {}; -export type NoUnusedExpressionsOptions = {}; -/** - * Options for the `noUselessCatchBinding` rule. -Currently empty; reserved for future extensions (e.g. allowlist of names). - */ -export type NoUselessCatchBindingOptions = {}; export type NoUselessReturnOptions = {}; -export type NoUselessUndefinedOptions = {}; export type NoVueArrowFuncInWatchOptions = {}; -export type NoVueDataObjectDeclarationOptions = {}; -export type NoVueDuplicateKeysOptions = {}; export type NoVueOptionsApiOptions = {}; -export type NoVueReservedKeysOptions = {}; -export type NoVueReservedPropsOptions = {}; -export type NoVueSetupPropsReactivityLossOptions = {}; export type NoVueVIfWithVForOptions = {}; export type UseArraySortCompareOptions = {}; export type UseAwaitThenableOptions = {}; -/** - * Options for the `useConsistentArrowReturn` rule. - */ -export interface UseConsistentArrowReturnOptions { - /** - * Determines whether the rule enforces a consistent style when the return value is an object literal. - -This option is only applicable when used in conjunction with the `asNeeded` option. - */ - requireForObjectLiteral?: boolean; - /** - * The style to enforce for arrow function return statements. - */ - style?: UseConsistentArrowReturnStyle; -} export type UseConsistentEnumValueTypeOptions = {}; export interface UseConsistentGraphqlDescriptionsOptions { /** @@ -7038,9 +7170,6 @@ Default: "property" */ style?: MethodSignatureStyle; } -export interface UseDeprecatedDateOptions { - argumentName?: string; -} export type UseDestructuringOptions = {}; /** * Options for the `useErrorCause` rule. @@ -7064,14 +7193,6 @@ export interface UseInputNameOptions { } export type UseLoneAnonymousOperationOptions = {}; export type UseLoneExecutableDefinitionOptions = {}; -export interface UseMaxParamsOptions { - /** - * Maximum number of parameters allowed (default: 4) - */ - max?: number; -} -export type UseQwikMethodUsageOptions = {}; -export type UseQwikValidLexicalScopeOptions = {}; export type UseRegexpExecOptions = {}; export interface UseRequiredScriptsOptions { /** @@ -7188,6 +7309,20 @@ export type NoExportedImportsOptions = {}; export type NoHeadElementOptions = {}; export type NoImplicitBooleanOptions = {}; export type NoInferrableTypesOptions = {}; +export interface NoJsxLiteralsOptions { + /** + * An array of strings that won't trigger the rule. Whitespaces are taken into consideration + */ + allowedStrings?: string[]; + /** + * When enabled, strings inside props are always ignored + */ + ignoreProps?: boolean; + /** + * When enabled, also flag string literals inside JSX expressions and attributes + */ + noStrings?: boolean; +} export type NoMagicNumbersOptions = {}; export type NoNamespaceOptions = {}; export type NoNegationElseOptions = {}; @@ -7245,6 +7380,21 @@ export interface UseComponentExportOnlyModulesOptions { export interface UseConsistentArrayTypeOptions { syntax?: ConsistentArrayType; } +/** + * Options for the `useConsistentArrowReturn` rule. + */ +export interface UseConsistentArrowReturnOptions { + /** + * Determines whether the rule enforces a consistent style when the return value is an object literal. + +This option is only applicable when used in conjunction with the `asNeeded` option. + */ + requireForObjectLiteral?: boolean; + /** + * The style to enforce for arrow function return statements. + */ + style?: UseConsistentArrowReturnStyle; +} export type UseConsistentBuiltinInstantiationOptions = {}; export type UseConsistentCurlyBracesOptions = {}; export interface UseConsistentMemberAccessibilityOptions { @@ -7342,7 +7492,16 @@ export type UseTemplateOptions = {}; export type UseThrowNewErrorOptions = {}; export type UseThrowOnlyErrorOptions = {}; export type UseTrimStartEndOptions = {}; -export type UseUnifiedTypeSignaturesOptions = {}; +export interface UseUnifiedTypeSignaturesOptions { + /** + * Whether to ignore overloads with different JSDoc comments. + */ + ignoreDifferentJsDoc?: boolean; + /** + * Whether to ignore overloads with differently named parameters. + */ + ignoreDifferentlyNamedParameters?: boolean; +} export type NoAlertOptions = {}; export type NoApproximativeNumericConstantOptions = {}; export type NoArrayIndexKeyOptions = {}; @@ -7376,6 +7535,7 @@ export type NoConstEnumOptions = {}; export type NoConstantBinaryExpressionsOptions = {}; export type NoControlCharactersInRegexOptions = {}; export type NoDebuggerOptions = {}; +export type NoDeprecatedImportsOptions = {}; export type NoDocumentCookieOptions = {}; export type NoDocumentImportInPageOptions = {}; export interface NoDoubleEqualsOptions { @@ -7391,6 +7551,7 @@ export type NoDuplicateAtImportRulesOptions = {}; export type NoDuplicateCaseOptions = {}; export type NoDuplicateClassMembersOptions = {}; export type NoDuplicateCustomPropertiesOptions = {}; +export type NoDuplicateDependenciesOptions = {}; export type NoDuplicateElseIfOptions = {}; export type NoDuplicateFieldsOptions = {}; export type NoDuplicateFontNamesOptions = {}; @@ -7403,6 +7564,12 @@ export type NoDuplicateTestHooksOptions = {}; export type NoEmptyBlockOptions = {}; export type NoEmptyBlockStatementsOptions = {}; export type NoEmptyInterfaceOptions = {}; +export interface NoEmptySourceOptions { + /** + * Whether comments are considered meaningful + */ + allowComments?: boolean; +} export type NoEvolvingTypesOptions = {}; export type NoExplicitAnyOptions = {}; export type NoExportsInTestOptions = {}; @@ -7416,6 +7583,15 @@ export type NoGlobalIsNanOptions = {}; export type NoHeadImportInDocumentOptions = {}; export type NoImplicitAnyLetOptions = {}; export type NoImportAssignOptions = {}; +export interface NoImportCyclesOptions { + /** + * Ignores type-only imports when finding an import cycle. A type-only import (`import type`) +will be removed by the compiler, so it cuts an import cycle at runtime. Note that named type +imports (`import { type Foo }`) aren't considered as type-only because it's not removed by +the compiler if the `verbatimModuleSyntax` option is enabled. Enabled by default. + */ + ignoreTypes?: boolean; +} export type NoImportantInKeyframeOptions = {}; export type NoIrregularWhitespaceOptions = {}; export type NoLabelVarOptions = {}; @@ -7432,6 +7608,7 @@ export interface NoQuickfixBiomeOptions { */ additionalPaths?: string[]; } +export type NoReactForwardRefOptions = {}; export type NoReactSpecificPropsOptions = {}; export type NoRedeclareOptions = {}; export type NoRedundantUseStrictOptions = {}; @@ -7453,6 +7630,7 @@ export interface NoUnknownAtRulesOptions { } export type NoUnsafeDeclarationMergingOptions = {}; export type NoUnsafeNegationOptions = {}; +export type NoUnusedExpressionsOptions = {}; export type NoUselessEscapeInStringOptions = {}; export type NoUselessRegexBackrefsOptions = {}; export type NoVarOptions = {}; @@ -7461,12 +7639,21 @@ export type UseAdjacentOverloadSignaturesOptions = {}; export type UseAwaitOptions = {}; export type UseBiomeIgnoreFolderOptions = {}; export type UseDefaultSwitchClauseLastOptions = {}; +export interface UseDeprecatedDateOptions { + argumentName?: string; +} export type UseErrorMessageOptions = {}; export type UseGetterReturnOptions = {}; export type UseGoogleFontDisplayOptions = {}; export type UseGuardForInOptions = {}; export type UseIsArrayOptions = {}; -export type UseIterableCallbackReturnOptions = {}; +export interface UseIterableCallbackReturnOptions { + /** + * When `true`, the rule reports `forEach` callbacks that return a value (default behaviour). +When `false` or unset, such callbacks are ignored. + */ + checkForEach?: boolean; +} export type UseNamespaceKeywordOptions = {}; export type UseNumberToFixedDigitsArgumentOptions = {}; export type UseStaticResponseMethodsOptions = {}; @@ -7508,7 +7695,6 @@ while for `useState()` it would be `[1]`. stableResult?: StableHookResult; } export type Regex = string; -export type UseConsistentArrowReturnStyle = "asNeeded" | "always" | "never"; /** * The GraphQL description style to enforce. */ @@ -7526,6 +7712,7 @@ export type Paths = string | PathOptions; export type Patterns = PatternOptions; export type CustomRestrictedType = string | CustomRestrictedTypeOptions; export type ConsistentArrayType = "shorthand" | "generic"; +export type UseConsistentArrowReturnStyle = "asNeeded" | "always" | "never"; export type Accessibility = "noPublic" | "explicit" | "none"; export type ObjectPropertySyntax = "explicit" | "shorthand"; export type ConsistentTypeDefinition = "interface" | "type"; @@ -7690,6 +7877,25 @@ export type RestrictedModifier = | "protected" | "readonly" | "static"; +export interface FileFeaturesResult { + featuresSupported: FeaturesSupported; +} +export type FeaturesSupported = { [K in FeatureKind]?: SupportKind }; +export type SupportKind = + | "supported" + | "ignored" + | "protected" + | "featureNotEnabled" + | "fileNotSupported" + | "notRequested"; +export interface UpdateSettingsParams { + configuration: Configuration; + extendedConfigurations?: [BiomePath, Configuration][]; + moduleGraphResolutionKind?: ModuleGraphResolutionKind; + projectKey: ProjectKey; + workspaceDirectory?: BiomePath; +} +export type ModuleGraphResolutionKind = "none" | "modules" | "modulesAndTypes"; export interface UpdateSettingsResult { diagnostics: Diagnostic[]; } @@ -7767,6 +7973,7 @@ export type Category = | "lint/complexity/noStaticOnlyClass" | "lint/complexity/noThisInStatic" | "lint/complexity/noUselessCatch" + | "lint/complexity/noUselessCatchBinding" | "lint/complexity/noUselessConstructor" | "lint/complexity/noUselessContinue" | "lint/complexity/noUselessEmptyExport" @@ -7781,6 +7988,7 @@ export type Category = | "lint/complexity/noUselessTernary" | "lint/complexity/noUselessThisAlias" | "lint/complexity/noUselessTypeConstraint" + | "lint/complexity/noUselessUndefined" | "lint/complexity/noUselessUndefinedInitialization" | "lint/complexity/noVoid" | "lint/complexity/useArrowFunction" @@ -7788,6 +7996,7 @@ export type Category = | "lint/complexity/useFlatMap" | "lint/complexity/useIndexOf" | "lint/complexity/useLiteralKeys" + | "lint/complexity/useMaxParams" | "lint/complexity/useNumericLiterals" | "lint/complexity/useOptionalChain" | "lint/complexity/useRegexLiterals" @@ -7812,6 +8021,7 @@ export type Category = | "lint/correctness/noInvalidUseBeforeDeclaration" | "lint/correctness/noMissingVarFunction" | "lint/correctness/noNestedComponentDefinitions" + | "lint/correctness/noNextAsyncClientComponent" | "lint/correctness/noNodejsModules" | "lint/correctness/noNonoctalDecimalEscape" | "lint/correctness/noPrecisionLoss" @@ -7832,13 +8042,13 @@ export type Category = | "lint/correctness/noUnknownMediaFeatureName" | "lint/correctness/noUnknownProperty" | "lint/correctness/noUnknownPseudoClass" - | "lint/correctness/noUnknownPseudoClassSelector" | "lint/correctness/noUnknownPseudoElement" | "lint/correctness/noUnknownTypeSelector" | "lint/correctness/noUnknownUnit" | "lint/correctness/noUnmatchableAnbSelector" | "lint/correctness/noUnreachable" | "lint/correctness/noUnreachableSuper" + | "lint/correctness/noUnresolvedImports" | "lint/correctness/noUnsafeFinally" | "lint/correctness/noUnsafeOptionalChaining" | "lint/correctness/noUnusedFunctionParameters" @@ -7848,6 +8058,11 @@ export type Category = | "lint/correctness/noUnusedVariables" | "lint/correctness/noVoidElementsWithChildren" | "lint/correctness/noVoidTypeReturn" + | "lint/correctness/noVueDataObjectDeclaration" + | "lint/correctness/noVueDuplicateKeys" + | "lint/correctness/noVueReservedKeys" + | "lint/correctness/noVueReservedProps" + | "lint/correctness/noVueSetupPropsReactivityLoss" | "lint/correctness/useExhaustiveDependencies" | "lint/correctness/useGraphqlNamedOperations" | "lint/correctness/useHookAtTopLevel" @@ -7858,6 +8073,8 @@ export type Category = | "lint/correctness/useJsxKeyInIterable" | "lint/correctness/useParseIntRadix" | "lint/correctness/useQwikClasslist" + | "lint/correctness/useQwikMethodUsage" + | "lint/correctness/useQwikValidLexicalScope" | "lint/correctness/useSingleJsDocAsterisk" | "lint/correctness/useUniqueElementIds" | "lint/correctness/useValidForDirection" @@ -7867,12 +8084,10 @@ export type Category = | "lint/nursery/noBeforeInteractiveScriptOutsideDocument" | "lint/nursery/noColorInvalidHex" | "lint/nursery/noContinue" - | "lint/nursery/noDeprecatedImports" | "lint/nursery/noDeprecatedMediaType" | "lint/nursery/noDivRegex" | "lint/nursery/noDuplicateArgumentNames" | "lint/nursery/noDuplicateAttributes" - | "lint/nursery/noDuplicateDependencies" | "lint/nursery/noDuplicateEnumValueNames" | "lint/nursery/noDuplicateEnumValues" | "lint/nursery/noDuplicateFieldDefinitionNames" @@ -7880,7 +8095,6 @@ export type Category = | "lint/nursery/noDuplicateInputFieldNames" | "lint/nursery/noDuplicateVariableNames" | "lint/nursery/noDuplicatedSpreadProps" - | "lint/nursery/noEmptySource" | "lint/nursery/noEqualsToNull" | "lint/nursery/noExcessiveClassesPerFile" | "lint/nursery/noExcessiveLinesPerFile" @@ -7889,9 +8103,7 @@ export type Category = | "lint/nursery/noForIn" | "lint/nursery/noHexColors" | "lint/nursery/noImplicitCoercion" - | "lint/nursery/noImportCycles" | "lint/nursery/noIncrementDecrement" - | "lint/nursery/noJsxLiterals" | "lint/nursery/noJsxPropsBind" | "lint/nursery/noLeakedRender" | "lint/nursery/noMissingGenericFamilyKeyword" @@ -7899,10 +8111,8 @@ export type Category = | "lint/nursery/noMultiAssign" | "lint/nursery/noMultiStr" | "lint/nursery/noNestedPromises" - | "lint/nursery/noNextAsyncClientComponent" | "lint/nursery/noParametersOnlyUsedInRecursion" | "lint/nursery/noProto" - | "lint/nursery/noReactForwardRef" | "lint/nursery/noRedundantDefaultExport" | "lint/nursery/noReturnAssign" | "lint/nursery/noRootType" @@ -7913,30 +8123,19 @@ export type Category = | "lint/nursery/noUndeclaredEnvVars" | "lint/nursery/noUnknownAttribute" | "lint/nursery/noUnnecessaryConditions" - | "lint/nursery/noUnresolvedImports" - | "lint/nursery/noUnusedExpressions" | "lint/nursery/noUnwantedPolyfillio" | "lint/nursery/noUselessBackrefInRegex" - | "lint/nursery/noUselessCatchBinding" | "lint/nursery/noUselessReturn" - | "lint/nursery/noUselessUndefined" | "lint/nursery/noVueArrowFuncInWatch" - | "lint/nursery/noVueDataObjectDeclaration" - | "lint/nursery/noVueDuplicateKeys" | "lint/nursery/noVueOptionsApi" - | "lint/nursery/noVueReservedKeys" - | "lint/nursery/noVueReservedProps" - | "lint/nursery/noVueSetupPropsReactivityLoss" | "lint/nursery/noVueVIfWithVFor" | "lint/nursery/useArraySortCompare" | "lint/nursery/useAwaitThenable" | "lint/nursery/useBiomeSuppressionComment" - | "lint/nursery/useConsistentArrowReturn" | "lint/nursery/useConsistentEnumValueType" | "lint/nursery/useConsistentGraphqlDescriptions" | "lint/nursery/useConsistentMethodSignatures" | "lint/nursery/useConsistentObjectDefinition" - | "lint/nursery/useDeprecatedDate" | "lint/nursery/useDestructuring" | "lint/nursery/useErrorCause" | "lint/nursery/useExhaustiveSwitchCases" @@ -7950,13 +8149,15 @@ export type Category = | "lint/nursery/useJsxCurlyBraceConvention" | "lint/nursery/useLoneAnonymousOperation" | "lint/nursery/useLoneExecutableDefinition" - | "lint/nursery/useMaxParams" - | "lint/nursery/useQwikMethodUsage" - | "lint/nursery/useQwikValidLexicalScope" | "lint/nursery/useRegexpExec" | "lint/nursery/useRequiredScripts" | "lint/nursery/useSortedClasses" | "lint/nursery/useSpread" + | "lint/nursery/useUniqueArgumentNames" + | "lint/nursery/useUniqueFieldDefinitionNames" + | "lint/nursery/useUniqueGraphqlOperationName" + | "lint/nursery/useUniqueInputFieldNames" + | "lint/nursery/useUniqueVariableNames" | "lint/nursery/useVueConsistentDefinePropsDeclaration" | "lint/nursery/useVueConsistentVBindStyle" | "lint/nursery/useVueConsistentVOnStyle" @@ -8004,6 +8205,7 @@ export type Category = | "lint/style/noHeadElement" | "lint/style/noImplicitBoolean" | "lint/style/noInferrableTypes" + | "lint/style/noJsxLiterals" | "lint/style/noMagicNumbers" | "lint/style/noNamespace" | "lint/style/noNegationElse" @@ -8029,6 +8231,7 @@ export type Category = | "lint/style/useCollapsedIf" | "lint/style/useComponentExportOnlyModules" | "lint/style/useConsistentArrayType" + | "lint/style/useConsistentArrowReturn" | "lint/style/useConsistentBuiltinInstantiation" | "lint/style/useConsistentCurlyBraces" | "lint/style/useConsistentMemberAccessibility" @@ -8087,6 +8290,7 @@ export type Category = | "lint/suspicious/noConstantBinaryExpressions" | "lint/suspicious/noControlCharactersInRegex" | "lint/suspicious/noDebugger" + | "lint/suspicious/noDeprecatedImports" | "lint/suspicious/noDocumentCookie" | "lint/suspicious/noDocumentImportInPage" | "lint/suspicious/noDoubleEquals" @@ -8094,6 +8298,7 @@ export type Category = | "lint/suspicious/noDuplicateCase" | "lint/suspicious/noDuplicateClassMembers" | "lint/suspicious/noDuplicateCustomProperties" + | "lint/suspicious/noDuplicateDependencies" | "lint/suspicious/noDuplicateElseIf" | "lint/suspicious/noDuplicateFields" | "lint/suspicious/noDuplicateFontNames" @@ -8106,6 +8311,7 @@ export type Category = | "lint/suspicious/noEmptyBlock" | "lint/suspicious/noEmptyBlockStatements" | "lint/suspicious/noEmptyInterface" + | "lint/suspicious/noEmptySource" | "lint/suspicious/noEvolvingTypes" | "lint/suspicious/noExplicitAny" | "lint/suspicious/noExportsInTest" @@ -8119,6 +8325,7 @@ export type Category = | "lint/suspicious/noHeadImportInDocument" | "lint/suspicious/noImplicitAnyLet" | "lint/suspicious/noImportAssign" + | "lint/suspicious/noImportCycles" | "lint/suspicious/noImportantInKeyframe" | "lint/suspicious/noIrregularWhitespace" | "lint/suspicious/noLabelVar" @@ -8130,6 +8337,7 @@ export type Category = | "lint/suspicious/noOctalEscape" | "lint/suspicious/noPrototypeBuiltins" | "lint/suspicious/noQuickfixBiome" + | "lint/suspicious/noReactForwardRef" | "lint/suspicious/noReactSpecificProps" | "lint/suspicious/noRedeclare" | "lint/suspicious/noRedundantUseStrict" @@ -8146,6 +8354,7 @@ export type Category = | "lint/suspicious/noUnknownAtRules" | "lint/suspicious/noUnsafeDeclarationMerging" | "lint/suspicious/noUnsafeNegation" + | "lint/suspicious/noUnusedExpressions" | "lint/suspicious/noUselessEscapeInString" | "lint/suspicious/noUselessRegexBackrefs" | "lint/suspicious/noVar" @@ -8154,6 +8363,7 @@ export type Category = | "lint/suspicious/useAwait" | "lint/suspicious/useBiomeIgnoreFolder" | "lint/suspicious/useDefaultSwitchClauseLast" + | "lint/suspicious/useDeprecatedDate" | "lint/suspicious/useErrorMessage" | "lint/suspicious/useGetterReturn" | "lint/suspicious/useGoogleFontDisplay" @@ -8164,6 +8374,8 @@ export type Category = | "lint/suspicious/useNumberToFixedDigitsArgument" | "lint/suspicious/useStaticResponseMethods" | "lint/suspicious/useStrictMode" + | "assist/source/noDuplicateClasses" + | "assist/source/useSortedInterfaceMembers" | "assist/source/useSortedKeys" | "assist/source/useSortedProperties" | "assist/source/useSortedAttributes" @@ -8357,7 +8569,8 @@ Target paths must be absolute. targetPaths: BiomePath[]; }; } - | "project"; + | "project" + | "typeAware"; export interface ScanProjectResult { /** * A list of child configuration files found inside the project @@ -8379,6 +8592,7 @@ export interface Duration { export interface OpenFileParams { content: FileContent; documentFileSource?: DocumentFileSource; + inlineConfig?: Configuration; path: BiomePath; /** * Set to `true` to persist the node cache used during parsing, in order to @@ -8419,6 +8633,12 @@ export interface JsonFileSource { variant: JsonFileVariant; } export interface CssFileSource { + /** + * Used to mark if the CSS is embedded inside some particular files. This affects the parsing. +For example, if inside a styled`` literal, a top-level declaration is allowed. + */ + embeddingKind: EmbeddingKind2; + language: CssFileLanguage; variant: CssVariant; } export interface GraphqlFileSource { @@ -8431,7 +8651,6 @@ export interface GritFileSource { variant: GritVariant; } export type EmbeddingKind = - | "Svelte" | "None" | { Astro: { @@ -8443,11 +8662,23 @@ export type EmbeddingKind = } | { Vue: { + /** + * Where the bindings are defined + */ + is_source: boolean; /** * Whether the script is inside script tag with setup attribute */ setup: boolean; }; + } + | { + Svelte: { + /** + * Where the bindings are defined + */ + is_source: boolean; + }; }; export type Language = | "javaScript" @@ -8469,8 +8700,13 @@ export type LanguageVersion = "eS2022" | "eSNext"; * It represents the extension of the file */ export type JsonFileVariant = "standard" | "jsonc"; +export type EmbeddingKind2 = "None" | "Styled" | { Html: EmbeddingHtmlKind }; +/** + * The language of the stylesheet. + */ +export type CssFileLanguage = "css" | "scss"; /** - * The style of CSS contained in the file. + * Extra CSS features enabled for the file. Currently, Biome aims to be compatible with the latest Recommendation level standards. @@ -8488,12 +8724,14 @@ export type HtmlVariant = | "Vue" | "Svelte"; export type GritVariant = "Standard"; +export type EmbeddingHtmlKind = "None" | "Html" | "Vue" | "Astro" | "Svelte"; export type HtmlTextExpressions = "None" | "Single" | "Double"; export interface OpenFileResult { diagnostics: Diagnostic[]; } export interface ChangeFileParams { content: string; + inlineConfig?: Configuration; path: BiomePath; projectKey: ProjectKey; version: number; @@ -8527,6 +8765,7 @@ When this field is empty, Biome checks only `files.includes`. export type IgnoreKind = "path" | "ancestors"; export interface UpdateModuleGraphParams { path: BiomePath; + projectKey: ProjectKey; /** * The kind of update to apply to the module graph */ @@ -8628,6 +8867,7 @@ export interface PullDiagnosticsParams { * Rules to apply on top of the configuration */ enabledRules?: AnalyzerSelector[]; + inlineConfig?: Configuration; only?: AnalyzerSelector[]; path: BiomePath; projectKey: ProjectKey; @@ -8648,6 +8888,7 @@ export interface PullDiagnosticsResult { export interface PullActionsParams { categories?: RuleCategories; enabledRules?: AnalyzerSelector[]; + inlineConfig?: Configuration; only?: AnalyzerSelector[]; path: BiomePath; projectKey: ProjectKey; @@ -8717,6 +8958,7 @@ export type Applicability = "always" | "maybeIncorrect"; export interface PullDiagnosticsAndActionsParams { categories?: RuleCategories; enabledRules?: AnalyzerSelector[]; + inlineConfig?: Configuration; only?: AnalyzerSelector[]; path: BiomePath; projectKey: ProjectKey; @@ -8726,6 +8968,7 @@ export interface PullDiagnosticsAndActionsResult { diagnostics: [Diagnostic, CodeAction[]][]; } export interface FormatFileParams { + inlineConfig?: Configuration; path: BiomePath; projectKey: ProjectKey; } @@ -8749,11 +8992,13 @@ export interface SourceMarker { source: TextSize; } export interface FormatRangeParams { + inlineConfig?: Configuration; path: BiomePath; projectKey: ProjectKey; range: TextRange; } export interface FormatOnTypeParams { + inlineConfig?: Configuration; offset: TextSize; path: BiomePath; projectKey: ProjectKey; @@ -8764,6 +9009,7 @@ export interface FixFileParams { */ enabledRules?: AnalyzerSelector[]; fixFileMode: FixFileMode; + inlineConfig?: Configuration; only?: AnalyzerSelector[]; path: BiomePath; projectKey: ProjectKey; @@ -8827,7 +9073,7 @@ export interface ParsePatternParams { defaultLanguage: GritTargetLanguage; pattern: string; } -export type GritTargetLanguage = "CSS" | "JavaScript"; +export type GritTargetLanguage = "CSS" | "JavaScript" | "JSON"; export interface ParsePatternResult { patternId: PatternId; } diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 535326a02ee3..114045b87649 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -79,7 +79,7 @@ "type": "object", "properties": { "noAccessKey": { - "description": "Enforce that the accessKey attribute is not used on any HTML element.\nSee https://biomejs.dev/linter/rules/no-access-key", + "description": "Enforce that the accesskey attribute is not used on any HTML element.\nSee https://biomejs.dev/linter/rules/no-access-key", "anyOf": [ { "$ref": "#/$defs/NoAccessKeyConfiguration" }, { "type": "null" } @@ -100,7 +100,7 @@ ] }, "noAutofocus": { - "description": "Enforce that autoFocus prop is not used on elements.\nSee https://biomejs.dev/linter/rules/no-autofocus", + "description": "Enforce that the autofocus attribute is not used on elements.\nSee https://biomejs.dev/linter/rules/no-autofocus", "anyOf": [ { "$ref": "#/$defs/NoAutofocusConfiguration" }, { "type": "null" } @@ -162,7 +162,7 @@ ] }, "noPositiveTabindex": { - "description": "Prevent the usage of positive integers on tabIndex property.\nSee https://biomejs.dev/linter/rules/no-positive-tabindex", + "description": "Prevent the usage of positive integers on tabindex attribute.\nSee https://biomejs.dev/linter/rules/no-positive-tabindex", "anyOf": [ { "$ref": "#/$defs/NoPositiveTabindexConfiguration" }, { "type": "null" } @@ -238,7 +238,7 @@ ] }, "useButtonType": { - "description": "Enforces the usage of the attribute type for the element button.\nSee https://biomejs.dev/linter/rules/use-button-type", + "description": "Enforces the usage and validity of the attribute type for the element button.\nSee https://biomejs.dev/linter/rules/use-button-type", "anyOf": [ { "$ref": "#/$defs/UseButtonTypeConfiguration" }, { "type": "null" } @@ -531,6 +531,13 @@ { "type": "null" } ] }, + "noUselessCatchBinding": { + "description": "Disallow unused catch bindings.\nSee https://biomejs.dev/linter/rules/no-useless-catch-binding", + "anyOf": [ + { "$ref": "#/$defs/NoUselessCatchBindingConfiguration" }, + { "type": "null" } + ] + }, "noUselessConstructor": { "description": "Disallow unnecessary constructors.\nSee https://biomejs.dev/linter/rules/no-useless-constructor", "anyOf": [ @@ -629,6 +636,13 @@ { "type": "null" } ] }, + "noUselessUndefined": { + "description": "Disallow the use of useless undefined.\nSee https://biomejs.dev/linter/rules/no-useless-undefined", + "anyOf": [ + { "$ref": "#/$defs/NoUselessUndefinedConfiguration" }, + { "type": "null" } + ] + }, "noUselessUndefinedInitialization": { "description": "Disallow initializing variables to undefined.\nSee https://biomejs.dev/linter/rules/no-useless-undefined-initialization", "anyOf": [ @@ -682,6 +696,13 @@ { "type": "null" } ] }, + "useMaxParams": { + "description": "Enforce a maximum number of parameters in function definitions.\nSee https://biomejs.dev/linter/rules/use-max-params", + "anyOf": [ + { "$ref": "#/$defs/UseMaxParamsConfiguration" }, + { "type": "null" } + ] + }, "useNumericLiterals": { "description": "Disallow parseInt() and Number.parseInt() in favor of binary, octal, and hexadecimal literals.\nSee https://biomejs.dev/linter/rules/use-numeric-literals", "anyOf": [ @@ -905,6 +926,13 @@ { "type": "null" } ] }, + "noNextAsyncClientComponent": { + "description": "Prevent client components from being async functions.\nSee https://biomejs.dev/linter/rules/no-next-async-client-component", + "anyOf": [ + { "$ref": "#/$defs/NoNextAsyncClientComponentConfiguration" }, + { "type": "null" } + ] + }, "noNodejsModules": { "description": "Forbid the use of Node.js builtin modules.\nSee https://biomejs.dev/linter/rules/no-nodejs-modules", "anyOf": [ @@ -1087,6 +1115,13 @@ { "type": "null" } ] }, + "noUnresolvedImports": { + "description": "Warn when importing non-existing exports.\nSee https://biomejs.dev/linter/rules/no-unresolved-imports", + "anyOf": [ + { "$ref": "#/$defs/NoUnresolvedImportsConfiguration" }, + { "type": "null" } + ] + }, "noUnsafeFinally": { "description": "Disallow control flow statements in finally blocks.\nSee https://biomejs.dev/linter/rules/no-unsafe-finally", "anyOf": [ @@ -1150,6 +1185,41 @@ { "type": "null" } ] }, + "noVueDataObjectDeclaration": { + "description": "Enforce that Vue component data options are declared as functions.\nSee https://biomejs.dev/linter/rules/no-vue-data-object-declaration", + "anyOf": [ + { "$ref": "#/$defs/NoVueDataObjectDeclarationConfiguration" }, + { "type": "null" } + ] + }, + "noVueDuplicateKeys": { + "description": "Disallow duplicate keys in Vue component data, methods, computed properties, and other options.\nSee https://biomejs.dev/linter/rules/no-vue-duplicate-keys", + "anyOf": [ + { "$ref": "#/$defs/NoVueDuplicateKeysConfiguration" }, + { "type": "null" } + ] + }, + "noVueReservedKeys": { + "description": "Disallow reserved keys in Vue component data and computed properties.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-keys", + "anyOf": [ + { "$ref": "#/$defs/NoVueReservedKeysConfiguration" }, + { "type": "null" } + ] + }, + "noVueReservedProps": { + "description": "Disallow reserved names to be used as props.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-props", + "anyOf": [ + { "$ref": "#/$defs/NoVueReservedPropsConfiguration" }, + { "type": "null" } + ] + }, + "noVueSetupPropsReactivityLoss": { + "description": "Disallow destructuring of props passed to setup in Vue projects.\nSee https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss", + "anyOf": [ + { "$ref": "#/$defs/NoVueSetupPropsReactivityLossConfiguration" }, + { "type": "null" } + ] + }, "recommended": { "description": "Enables the recommended rules for this group", "type": ["boolean", "null"] @@ -1224,6 +1294,20 @@ { "type": "null" } ] }, + "useQwikMethodUsage": { + "description": "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee https://biomejs.dev/linter/rules/use-qwik-method-usage", + "anyOf": [ + { "$ref": "#/$defs/UseQwikMethodUsageConfiguration" }, + { "type": "null" } + ] + }, + "useQwikValidLexicalScope": { + "description": "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope", + "anyOf": [ + { "$ref": "#/$defs/UseQwikValidLexicalScopeConfiguration" }, + { "type": "null" } + ] + }, "useSingleJsDocAsterisk": { "description": "Enforce JSDoc comment lines to start with a single asterisk, except for the first one.\nSee https://biomejs.dev/linter/rules/use-single-js-doc-asterisk", "anyOf": [ @@ -1345,6 +1429,10 @@ "quoteStyle": { "description": "The type of quotes used in CSS code. Defaults to double.", "anyOf": [{ "$ref": "#/$defs/QuoteStyle" }, { "type": "null" }] + }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] } }, "additionalProperties": false @@ -1369,7 +1457,7 @@ "anyOf": [{ "$ref": "#/$defs/Bool" }, { "type": "null" }] }, "cssModules": { - "description": "Enables parsing of CSS Modules specific features.", + "description": "Enables parsing of CSS Modules specific features. Enable this feature only\nwhen your files don't end in `.module.css`.", "anyOf": [{ "$ref": "#/$defs/Bool" }, { "type": "null" }] }, "tailwindDirectives": { @@ -1583,6 +1671,10 @@ "description": "What's the max width of a line. Defaults to 80.", "anyOf": [{ "$ref": "#/$defs/LineWidth" }, { "type": "null" }] }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] + }, "useEditorconfig": { "description": "Use any `.editorconfig` files to configure the formatter. Configuration\nin `biome.json` will override `.editorconfig` configuration.\n\nDefault: `false`.", "anyOf": [{ "$ref": "#/$defs/Bool" }, { "type": "null" }] @@ -1668,6 +1760,10 @@ "description": "The type of quotes used in GraphQL code. Defaults to double.", "anyOf": [{ "$ref": "#/$defs/QuoteStyle" }, { "type": "null" }], "default": null + }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] } }, "additionalProperties": false @@ -1744,6 +1840,10 @@ "lineWidth": { "description": "What's the max width of a line applied to Grit files. Defaults to 80.", "anyOf": [{ "$ref": "#/$defs/LineWidth" }, { "type": "null" }] + }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] } }, "additionalProperties": false @@ -1920,6 +2020,10 @@ { "type": "null" } ] }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] + }, "whitespaceSensitivity": { "description": "Whether to account for whitespace sensitivity when formatting HTML (and its super languages). Defaults to \"css\".", "anyOf": [ @@ -2018,6 +2122,10 @@ { "type": "null" } ] }, + "experimentalEmbeddedSnippetsEnabled": { + "description": "Enables support for embedding snippets.", + "anyOf": [{ "$ref": "#/$defs/Bool" }, { "type": "null" }] + }, "formatter": { "description": "Formatting options", "anyOf": [ @@ -2119,6 +2227,10 @@ "trailingCommas": { "description": "Print trailing commas wherever possible in multi-line comma-separated syntactic structures. Defaults to \"all\".", "anyOf": [{ "$ref": "#/$defs/JsTrailingCommas" }, { "type": "null" }] + }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] } }, "additionalProperties": false @@ -2241,6 +2353,10 @@ { "$ref": "#/$defs/JsonTrailingCommas" }, { "type": "null" } ] + }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] } }, "additionalProperties": false @@ -3066,6 +3182,28 @@ "type": "object", "additionalProperties": false }, + "NoDuplicateClassesConfiguration": { + "oneOf": [ + { "$ref": "#/$defs/RuleAssistPlainConfiguration" }, + { "$ref": "#/$defs/RuleAssistWithNoDuplicateClassesOptions" } + ] + }, + "NoDuplicateClassesOptions": { + "type": "object", + "properties": { + "attributes": { + "description": "Additional attributes that will be sorted.", + "type": ["array", "null"], + "items": { "type": "string" } + }, + "functions": { + "description": "Names of the functions or tagged templates that will be sorted.", + "type": ["array", "null"], + "items": { "type": "string" } + } + }, + "additionalProperties": false + }, "NoDuplicateCustomPropertiesConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" }, @@ -4855,6 +4993,13 @@ }, "NoUnknownFunctionOptions": { "type": "object", + "properties": { + "ignore": { + "description": "A list of unknown function names to ignore (case-insensitive).", + "type": "array", + "items": { "type": "string" } + } + }, "additionalProperties": false }, "NoUnknownMediaFeatureNameConfiguration": { @@ -4875,6 +5020,13 @@ }, "NoUnknownPropertyOptions": { "type": "object", + "properties": { + "ignore": { + "description": "A list of unknown property names to ignore (case-insensitive).", + "type": "array", + "items": { "type": "string" } + } + }, "additionalProperties": false }, "NoUnknownPseudoClassConfiguration": { @@ -4885,6 +5037,13 @@ }, "NoUnknownPseudoClassOptions": { "type": "object", + "properties": { + "ignore": { + "description": "A list of unknown pseudo-class names to ignore (case-insensitive).", + "type": "array", + "items": { "type": "string" } + } + }, "additionalProperties": false }, "NoUnknownPseudoElementConfiguration": { @@ -4895,6 +5054,13 @@ }, "NoUnknownPseudoElementOptions": { "type": "object", + "properties": { + "ignore": { + "description": "A list of unknown pseudo-element names to ignore (case-insensitive).", + "type": "array", + "items": { "type": "string" } + } + }, "additionalProperties": false }, "NoUnknownTypeSelectorConfiguration": { @@ -5480,13 +5646,6 @@ { "type": "null" } ] }, - "noDeprecatedImports": { - "description": "Restrict imports of deprecated exports.\nSee https://biomejs.dev/linter/rules/no-deprecated-imports", - "anyOf": [ - { "$ref": "#/$defs/NoDeprecatedImportsConfiguration" }, - { "type": "null" } - ] - }, "noDeprecatedMediaType": { "description": "Disallow deprecated media types.\nSee https://biomejs.dev/linter/rules/no-deprecated-media-type", "anyOf": [ @@ -5515,13 +5674,6 @@ { "type": "null" } ] }, - "noDuplicateDependencies": { - "description": "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee https://biomejs.dev/linter/rules/no-duplicate-dependencies", - "anyOf": [ - { "$ref": "#/$defs/NoDuplicateDependenciesConfiguration" }, - { "type": "null" } - ] - }, "noDuplicateEnumValueNames": { "description": "Require all enum value names to be unique.\nSee https://biomejs.dev/linter/rules/no-duplicate-enum-value-names", "anyOf": [ @@ -5571,13 +5723,6 @@ { "type": "null" } ] }, - "noEmptySource": { - "description": "Disallow empty sources.\nSee https://biomejs.dev/linter/rules/no-empty-source", - "anyOf": [ - { "$ref": "#/$defs/NoEmptySourceConfiguration" }, - { "type": "null" } - ] - }, "noEqualsToNull": { "description": "Require the use of === or !== for comparison with null.\nSee https://biomejs.dev/linter/rules/no-equals-to-null", "anyOf": [ @@ -5627,13 +5772,6 @@ { "type": "null" } ] }, - "noImportCycles": { - "description": "Prevent import cycles.\nSee https://biomejs.dev/linter/rules/no-import-cycles", - "anyOf": [ - { "$ref": "#/$defs/NoImportCyclesConfiguration" }, - { "type": "null" } - ] - }, "noIncrementDecrement": { "description": "Disallows the usage of the unary operators ++ and --.\nSee https://biomejs.dev/linter/rules/no-increment-decrement", "anyOf": [ @@ -5641,13 +5779,6 @@ { "type": "null" } ] }, - "noJsxLiterals": { - "description": "Disallow string literals inside JSX elements.\nSee https://biomejs.dev/linter/rules/no-jsx-literals", - "anyOf": [ - { "$ref": "#/$defs/NoJsxLiteralsConfiguration" }, - { "type": "null" } - ] - }, "noJsxPropsBind": { "description": "Disallow .bind(), arrow functions, or function expressions in JSX props.\nSee https://biomejs.dev/linter/rules/no-jsx-props-bind", "anyOf": [ @@ -5690,13 +5821,6 @@ { "type": "null" } ] }, - "noNextAsyncClientComponent": { - "description": "Prevent client components from being async functions.\nSee https://biomejs.dev/linter/rules/no-next-async-client-component", - "anyOf": [ - { "$ref": "#/$defs/NoNextAsyncClientComponentConfiguration" }, - { "type": "null" } - ] - }, "noParametersOnlyUsedInRecursion": { "description": "Disallow function parameters that are only used in recursive calls.\nSee https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion", "anyOf": [ @@ -5711,13 +5835,6 @@ { "type": "null" } ] }, - "noReactForwardRef": { - "description": "Replaces usages of forwardRef with passing ref as a prop.\nSee https://biomejs.dev/linter/rules/no-react-forward-ref", - "anyOf": [ - { "$ref": "#/$defs/NoReactForwardRefConfiguration" }, - { "type": "null" } - ] - }, "noRedundantDefaultExport": { "description": "Checks if a default export exports the same symbol as a named export.\nSee https://biomejs.dev/linter/rules/no-redundant-default-export", "anyOf": [ @@ -5788,27 +5905,6 @@ { "type": "null" } ] }, - "noUnresolvedImports": { - "description": "Warn when importing non-existing exports.\nSee https://biomejs.dev/linter/rules/no-unresolved-imports", - "anyOf": [ - { "$ref": "#/$defs/NoUnresolvedImportsConfiguration" }, - { "type": "null" } - ] - }, - "noUnusedExpressions": { - "description": "Disallow expression statements that are neither a function call nor an assignment.\nSee https://biomejs.dev/linter/rules/no-unused-expressions", - "anyOf": [ - { "$ref": "#/$defs/NoUnusedExpressionsConfiguration" }, - { "type": "null" } - ] - }, - "noUselessCatchBinding": { - "description": "Disallow unused catch bindings.\nSee https://biomejs.dev/linter/rules/no-useless-catch-binding", - "anyOf": [ - { "$ref": "#/$defs/NoUselessCatchBindingConfiguration" }, - { "type": "null" } - ] - }, "noUselessReturn": { "description": "Disallow redundant return statements.\nSee https://biomejs.dev/linter/rules/no-useless-return", "anyOf": [ @@ -5816,13 +5912,6 @@ { "type": "null" } ] }, - "noUselessUndefined": { - "description": "Disallow the use of useless undefined.\nSee https://biomejs.dev/linter/rules/no-useless-undefined", - "anyOf": [ - { "$ref": "#/$defs/NoUselessUndefinedConfiguration" }, - { "type": "null" } - ] - }, "noVueArrowFuncInWatch": { "description": "Disallows using arrow functions when defining a watcher.\nSee https://biomejs.dev/linter/rules/no-vue-arrow-func-in-watch", "anyOf": [ @@ -5830,20 +5919,6 @@ { "type": "null" } ] }, - "noVueDataObjectDeclaration": { - "description": "Enforce that Vue component data options are declared as functions.\nSee https://biomejs.dev/linter/rules/no-vue-data-object-declaration", - "anyOf": [ - { "$ref": "#/$defs/NoVueDataObjectDeclarationConfiguration" }, - { "type": "null" } - ] - }, - "noVueDuplicateKeys": { - "description": "Disallow duplicate keys in Vue component data, methods, computed properties, and other options.\nSee https://biomejs.dev/linter/rules/no-vue-duplicate-keys", - "anyOf": [ - { "$ref": "#/$defs/NoVueDuplicateKeysConfiguration" }, - { "type": "null" } - ] - }, "noVueOptionsApi": { "description": "Disallow the use of Vue Options API.\nSee https://biomejs.dev/linter/rules/no-vue-options-api", "anyOf": [ @@ -5851,27 +5926,6 @@ { "type": "null" } ] }, - "noVueReservedKeys": { - "description": "Disallow reserved keys in Vue component data and computed properties.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-keys", - "anyOf": [ - { "$ref": "#/$defs/NoVueReservedKeysConfiguration" }, - { "type": "null" } - ] - }, - "noVueReservedProps": { - "description": "Disallow reserved names to be used as props.\nSee https://biomejs.dev/linter/rules/no-vue-reserved-props", - "anyOf": [ - { "$ref": "#/$defs/NoVueReservedPropsConfiguration" }, - { "type": "null" } - ] - }, - "noVueSetupPropsReactivityLoss": { - "description": "Disallow destructuring of props passed to setup in Vue projects.\nSee https://biomejs.dev/linter/rules/no-vue-setup-props-reactivity-loss", - "anyOf": [ - { "$ref": "#/$defs/NoVueSetupPropsReactivityLossConfiguration" }, - { "type": "null" } - ] - }, "noVueVIfWithVFor": { "description": "Disallow using v-if and v-for directives on the same element.\nSee https://biomejs.dev/linter/rules/no-vue-v-if-with-v-for", "anyOf": [ @@ -5897,13 +5951,6 @@ { "type": "null" } ] }, - "useConsistentArrowReturn": { - "description": "Enforce consistent arrow function bodies.\nSee https://biomejs.dev/linter/rules/use-consistent-arrow-return", - "anyOf": [ - { "$ref": "#/$defs/UseConsistentArrowReturnConfiguration" }, - { "type": "null" } - ] - }, "useConsistentEnumValueType": { "description": "Disallow enums from having both number and string members.\nSee https://biomejs.dev/linter/rules/use-consistent-enum-value-type", "anyOf": [ @@ -5925,13 +5972,6 @@ { "type": "null" } ] }, - "useDeprecatedDate": { - "description": "Require the @deprecated directive to specify a deletion date.\nSee https://biomejs.dev/linter/rules/use-deprecated-date", - "anyOf": [ - { "$ref": "#/$defs/UseDeprecatedDateConfiguration" }, - { "type": "null" } - ] - }, "useDestructuring": { "description": "Require destructuring from arrays and/or objects.\nSee https://biomejs.dev/linter/rules/use-destructuring", "anyOf": [ @@ -6002,27 +6042,6 @@ { "type": "null" } ] }, - "useMaxParams": { - "description": "Enforce a maximum number of parameters in function definitions.\nSee https://biomejs.dev/linter/rules/use-max-params", - "anyOf": [ - { "$ref": "#/$defs/UseMaxParamsConfiguration" }, - { "type": "null" } - ] - }, - "useQwikMethodUsage": { - "description": "Disallow use* hooks outside of component$ or other use* hooks in Qwik applications.\nSee https://biomejs.dev/linter/rules/use-qwik-method-usage", - "anyOf": [ - { "$ref": "#/$defs/UseQwikMethodUsageConfiguration" }, - { "type": "null" } - ] - }, - "useQwikValidLexicalScope": { - "description": "Disallow unserializable expressions in Qwik dollar ($) scopes.\nSee https://biomejs.dev/linter/rules/use-qwik-valid-lexical-scope", - "anyOf": [ - { "$ref": "#/$defs/UseQwikValidLexicalScopeConfiguration" }, - { "type": "null" } - ] - }, "useRegexpExec": { "description": "Enforce RegExp#exec over String#match if no global flag is provided.\nSee https://biomejs.dev/linter/rules/use-regexp-exec", "anyOf": [ @@ -6300,6 +6319,10 @@ "lineWidth": { "description": "What's the max width of a line. Defaults to 80.", "anyOf": [{ "$ref": "#/$defs/LineWidth" }, { "type": "null" }] + }, + "trailingNewline": { + "description": "Whether to add a trailing newline at the end of the file.\n\nSetting this option to `false` is **highly discouraged** because it could cause many problems with other tools:\n- \n- \n- \n\nDisable the option at your own risk.\n\nDefaults to true.", + "anyOf": [{ "$ref": "#/$defs/TrailingNewline" }, { "type": "null" }] } }, "additionalProperties": false @@ -6575,6 +6598,14 @@ "enum": ["abstract", "private", "protected", "readonly", "static"] }, "RuleAssistPlainConfiguration": { "type": "string", "enum": ["off", "on"] }, + "RuleAssistWithNoDuplicateClassesOptions": { + "type": "object", + "properties": { + "level": { "$ref": "#/$defs/RuleAssistPlainConfiguration" }, + "options": { "$ref": "#/$defs/NoDuplicateClassesOptions" } + }, + "required": ["level", "options"] + }, "RuleAssistWithOrganizeImportsOptions": { "type": "object", "properties": { @@ -6591,6 +6622,14 @@ }, "required": ["level", "options"] }, + "RuleAssistWithUseSortedInterfaceMembersOptions": { + "type": "object", + "properties": { + "level": { "$ref": "#/$defs/RuleAssistPlainConfiguration" }, + "options": { "$ref": "#/$defs/UseSortedInterfaceMembersOptions" } + }, + "required": ["level", "options"] + }, "RuleAssistWithUseSortedKeysOptions": { "type": "object", "properties": { @@ -6650,6 +6689,11 @@ "description": "Turborepo build system rules", "type": "string", "const": "turborepo" + }, + { + "description": "Rules that require type inference", + "type": "string", + "const": "types" } ] }, @@ -11003,6 +11047,13 @@ "description": "A list of rules that belong to this group", "type": "object", "properties": { + "noDuplicateClasses": { + "description": "Remove duplicate CSS classes.\nSee https://biomejs.dev/assist/actions/no-duplicate-classes", + "anyOf": [ + { "$ref": "#/$defs/NoDuplicateClassesConfiguration" }, + { "type": "null" } + ] + }, "organizeImports": { "description": "Provides a code action to sort the imports and exports in the file using a built-in or custom order.\nSee https://biomejs.dev/assist/actions/organize-imports", "anyOf": [ @@ -11021,6 +11072,13 @@ { "type": "null" } ] }, + "useSortedInterfaceMembers": { + "description": "Sort interface members by key.\nSee https://biomejs.dev/assist/actions/use-sorted-interface-members", + "anyOf": [ + { "$ref": "#/$defs/UseSortedInterfaceMembersConfiguration" }, + { "type": "null" } + ] + }, "useSortedKeys": { "description": "Sort the keys of a JSON object in natural order.\nSee https://biomejs.dev/assist/actions/use-sorted-keys", "anyOf": [ @@ -11142,6 +11200,13 @@ { "type": "null" } ] }, + "noJsxLiterals": { + "description": "Disallow string literals inside JSX elements.\nSee https://biomejs.dev/linter/rules/no-jsx-literals", + "anyOf": [ + { "$ref": "#/$defs/NoJsxLiteralsConfiguration" }, + { "type": "null" } + ] + }, "noMagicNumbers": { "description": "Reports usage of \"magic numbers\" — numbers used directly instead of being assigned to named constants.\nSee https://biomejs.dev/linter/rules/no-magic-numbers", "anyOf": [ @@ -11248,7 +11313,7 @@ ] }, "noValueAtRule": { - "description": "Disallow use of @value rule in css modules.\nSee https://biomejs.dev/linter/rules/no-value-at-rule", + "description": "Disallow use of @value rule in CSS modules.\nSee https://biomejs.dev/linter/rules/no-value-at-rule", "anyOf": [ { "$ref": "#/$defs/NoValueAtRuleConfiguration" }, { "type": "null" } @@ -11321,6 +11386,13 @@ { "type": "null" } ] }, + "useConsistentArrowReturn": { + "description": "Enforce consistent arrow function bodies.\nSee https://biomejs.dev/linter/rules/use-consistent-arrow-return", + "anyOf": [ + { "$ref": "#/$defs/UseConsistentArrowReturnConfiguration" }, + { "type": "null" } + ] + }, "useConsistentBuiltinInstantiation": { "description": "Enforce the use of new for all builtins, except String, Number and Boolean.\nSee https://biomejs.dev/linter/rules/use-consistent-builtin-instantiation", "anyOf": [ @@ -11729,6 +11801,13 @@ { "type": "null" } ] }, + "noDeprecatedImports": { + "description": "Restrict imports of deprecated exports.\nSee https://biomejs.dev/linter/rules/no-deprecated-imports", + "anyOf": [ + { "$ref": "#/$defs/NoDeprecatedImportsConfiguration" }, + { "type": "null" } + ] + }, "noDocumentCookie": { "description": "Disallow direct assignments to document.cookie.\nSee https://biomejs.dev/linter/rules/no-document-cookie", "anyOf": [ @@ -11778,6 +11857,13 @@ { "type": "null" } ] }, + "noDuplicateDependencies": { + "description": "Prevent the listing of duplicate dependencies. The rule supports the following dependency groups: \"bundledDependencies\", \"bundleDependencies\", \"dependencies\", \"devDependencies\", \"overrides\", \"optionalDependencies\", and \"peerDependencies\".\nSee https://biomejs.dev/linter/rules/no-duplicate-dependencies", + "anyOf": [ + { "$ref": "#/$defs/NoDuplicateDependenciesConfiguration" }, + { "type": "null" } + ] + }, "noDuplicateElseIf": { "description": "Disallow duplicate conditions in if-else-if chains.\nSee https://biomejs.dev/linter/rules/no-duplicate-else-if", "anyOf": [ @@ -11864,6 +11950,13 @@ { "type": "null" } ] }, + "noEmptySource": { + "description": "Disallow empty sources.\nSee https://biomejs.dev/linter/rules/no-empty-source", + "anyOf": [ + { "$ref": "#/$defs/NoEmptySourceConfiguration" }, + { "type": "null" } + ] + }, "noEvolvingTypes": { "description": "Disallow variables from evolving into any type through reassignments.\nSee https://biomejs.dev/linter/rules/no-evolving-types", "anyOf": [ @@ -11955,6 +12048,13 @@ { "type": "null" } ] }, + "noImportCycles": { + "description": "Prevent import cycles.\nSee https://biomejs.dev/linter/rules/no-import-cycles", + "anyOf": [ + { "$ref": "#/$defs/NoImportCyclesConfiguration" }, + { "type": "null" } + ] + }, "noImportantInKeyframe": { "description": "Disallow invalid !important within keyframe declarations.\nSee https://biomejs.dev/linter/rules/no-important-in-keyframe", "anyOf": [ @@ -12032,6 +12132,13 @@ { "type": "null" } ] }, + "noReactForwardRef": { + "description": "Replaces usages of forwardRef with passing ref as a prop.\nSee https://biomejs.dev/linter/rules/no-react-forward-ref", + "anyOf": [ + { "$ref": "#/$defs/NoReactForwardRefConfiguration" }, + { "type": "null" } + ] + }, "noReactSpecificProps": { "description": "Prevents React-specific JSX properties from being used.\nSee https://biomejs.dev/linter/rules/no-react-specific-props", "anyOf": [ @@ -12144,6 +12251,13 @@ { "type": "null" } ] }, + "noUnusedExpressions": { + "description": "Disallow expression statements that are neither a function call nor an assignment.\nSee https://biomejs.dev/linter/rules/no-unused-expressions", + "anyOf": [ + { "$ref": "#/$defs/NoUnusedExpressionsConfiguration" }, + { "type": "null" } + ] + }, "noUselessEscapeInString": { "description": "Disallow unnecessary escapes in string literals.\nSee https://biomejs.dev/linter/rules/no-useless-escape-in-string", "anyOf": [ @@ -12204,6 +12318,13 @@ { "type": "null" } ] }, + "useDeprecatedDate": { + "description": "Require the @deprecated directive to specify a deletion date.\nSee https://biomejs.dev/linter/rules/use-deprecated-date", + "anyOf": [ + { "$ref": "#/$defs/UseDeprecatedDateConfiguration" }, + { "type": "null" } + ] + }, "useErrorMessage": { "description": "Enforce passing a message value when creating a built-in error.\nSee https://biomejs.dev/linter/rules/use-error-message", "anyOf": [ @@ -12277,6 +12398,7 @@ }, "additionalProperties": false }, + "TrailingNewline": { "type": "boolean" }, "UseAdjacentOverloadSignaturesConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" }, @@ -13022,6 +13144,14 @@ }, "UseHookAtTopLevelOptions": { "type": "object", + "properties": { + "ignore": { + "description": "List of function names that should not be treated as hooks.\nFunctions in this list will be ignored by the rule even if they follow the `use*` naming convention.", + "type": ["array", "null"], + "items": { "type": "string" }, + "uniqueItems": true + } + }, "additionalProperties": false }, "UseHtmlLangConfiguration": { @@ -13057,6 +13187,11 @@ "UseImportExtensionsOptions": { "type": "object", "properties": { + "extensionMappings": { + "description": "A map of file extensions to their suggested replacements.\nFor example, `{\"ts\": \"js\"}` would suggest `.js` extensions for TypeScript imports.", + "type": ["object", "null"], + "additionalProperties": { "type": "string" } + }, "forceJsExtensions": { "description": "If `true`, the suggested extension is always `.js` regardless of what\nextension the source file has in your project.", "type": ["boolean", "null"] @@ -13143,6 +13278,12 @@ }, "UseIterableCallbackReturnOptions": { "type": "object", + "properties": { + "checkForEach": { + "description": "When `true`, the rule reports `forEach` callbacks that return a value (default behaviour).\nWhen `false` or unset, such callbacks are ignored.", + "type": ["boolean", "null"] + } + }, "additionalProperties": false }, "UseJsonImportAttributesConfiguration": { @@ -13605,6 +13746,16 @@ }, "additionalProperties": false }, + "UseSortedInterfaceMembersConfiguration": { + "oneOf": [ + { "$ref": "#/$defs/RuleAssistPlainConfiguration" }, + { "$ref": "#/$defs/RuleAssistWithUseSortedInterfaceMembersOptions" } + ] + }, + "UseSortedInterfaceMembersOptions": { + "type": "object", + "additionalProperties": false + }, "UseSortedKeysConfiguration": { "oneOf": [ { "$ref": "#/$defs/RuleAssistPlainConfiguration" }, @@ -13614,6 +13765,10 @@ "UseSortedKeysOptions": { "type": "object", "properties": { + "groupByNesting": { + "description": "When enabled, groups object keys by their value's nesting depth before sorting.\nSimple values (primitives, single-line arrays, single-line objects) are sorted first,\nfollowed by nested values (multi-line objects, multi-line arrays).", + "type": ["boolean", "null"] + }, "sortOrder": { "anyOf": [{ "$ref": "#/$defs/SortOrder" }, { "type": "null" }] } @@ -13719,6 +13874,16 @@ }, "UseUnifiedTypeSignaturesOptions": { "type": "object", + "properties": { + "ignoreDifferentJsDoc": { + "description": "Whether to ignore overloads with different JSDoc comments.", + "type": ["boolean", "null"] + }, + "ignoreDifferentlyNamedParameters": { + "description": "Whether to ignore overloads with differently named parameters.", + "type": ["boolean", "null"] + } + }, "additionalProperties": false }, "UseUniqueElementIdsConfiguration": { diff --git a/scripts/update-commonmark-spec.sh b/scripts/update-commonmark-spec.sh new file mode 100755 index 000000000000..21c64f1d1d75 --- /dev/null +++ b/scripts/update-commonmark-spec.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Script: update-commonmark-spec.sh +# Purpose: +# Download a specific version of the CommonMark spec.json and update +# both the spec file and the provenance comment in commonmark.rs. +# +# Usage: +# ./scripts/update-commonmark-spec.sh +# +# Example: +# ./scripts/update-commonmark-spec.sh 0.31.2 +# +# After running, verify with: +# just test-markdown-conformance + +SPEC_PATH="xtask/coverage/src/markdown/spec.json" +RS_PATH="xtask/coverage/src/markdown/commonmark.rs" + +print_help() { + cat <<'EOF' +Download a specific version of the CommonMark spec.json and update provenance. + +Usage: + update-commonmark-spec.sh + +Example: + update-commonmark-spec.sh 0.31.2 + +This will: + 1. Download https://spec.commonmark.org//spec.json + 2. Replace xtask/coverage/src/markdown/spec.json + 3. Update the provenance comment in commonmark.rs + +After updating, verify with: + just test-markdown-conformance +EOF +} + +if [[ $# -lt 1 ]]; then + print_help + exit 1 +fi + +if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then + print_help + exit 0 +fi + +VERSION="$1" +URL="https://spec.commonmark.org/${VERSION}/spec.json" +TODAY=$(date +%Y-%m-%d) + +echo "Downloading CommonMark spec version ${VERSION}..." +echo "URL: ${URL}" + +if ! curl -fsSL "${URL}" -o "${SPEC_PATH}.tmp"; then + echo "Error: Failed to download spec from ${URL}" >&2 + echo "Check that the version exists at https://spec.commonmark.org/" >&2 + rm -f "${SPEC_PATH}.tmp" + exit 1 +fi + +mv "${SPEC_PATH}.tmp" "${SPEC_PATH}" + +# Count examples +if command -v jq >/dev/null 2>&1; then + COUNT=$(jq 'length' "${SPEC_PATH}") +else + echo "Warning: jq not found, cannot count examples" >&2 + COUNT="unknown" +fi + +# Update provenance comment in commonmark.rs +echo "Updating provenance in ${RS_PATH}..." + +sed -i.bak \ + -e "s|// Version: .*|// Version: ${VERSION}|" \ + -e "s|// URL: .*|// URL: ${URL}|" \ + -e "s|// Downloaded: .*|// Downloaded: ${TODAY}|" \ + -e "s|// Examples: .*|// Examples: ${COUNT}|" \ + "${RS_PATH}" + +rm -f "${RS_PATH}.bak" + +# Print summary +echo +echo "Updated:" +echo " - ${SPEC_PATH}" +echo " - ${RS_PATH}" +echo +echo "Provenance:" +echo " Version: ${VERSION}" +echo " URL: ${URL}" +echo " Downloaded: ${TODAY}" +echo " Examples: ${COUNT}" +echo +echo "Next step:" +echo " just test-markdown-conformance" diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index ac5fa419e76a..efe2972958d5 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -63,13 +63,33 @@ CssBogusUnicodeRangeValue = SyntaxElement* CssBogusSupportsCondition = SyntaxElement* CssBogusIfBranch = SyntaxElement* CssBogusIfTest = SyntaxElement* +CssBogusSyntax = SyntaxElement* +CssBogusSyntaxSingleComponent = SyntaxElement* +CssBogusAttrName = SyntaxElement* +CssBogusFunctionParameter = SyntaxElement* +CssBogusType = SyntaxElement* CssBogusIfTestBooleanExpr = SyntaxElement* +AnyCssRoot = + CssRoot + | CssSnippetRoot + CssRoot = bom: 'UNICODE_BOM'? - rules: CssRuleList + items: CssRootItemList + eof: 'EOF' + +CssSnippetRoot = + items: CssDeclarationOrRuleList eof: 'EOF' +CssRootItemList = AnyCssRootItem* + +AnyCssRootItem = + AnyCssRule + | ScssDeclaration + | CssBogus + CssRuleList = AnyCssRule* AnyCssRule = @@ -472,6 +492,7 @@ CssDeclarationOrRuleList = AnyCssDeclarationOrRule* AnyCssDeclarationOrRule = AnyCssRule | CssDeclarationWithSemicolon + | ScssDeclaration | CssEmptyDeclaration | CssBogus | CssMetavariable @@ -491,6 +512,7 @@ CssDeclarationOrAtRuleList = AnyCssDeclarationOrAtRule* AnyCssDeclarationOrAtRule = CssDeclarationWithSemicolon + | ScssDeclaration | CssEmptyDeclaration | CssAtRule @@ -524,6 +546,7 @@ CssDeclarationList = AnyCssDeclaration* AnyCssDeclaration = CssDeclarationWithSemicolon + | ScssDeclaration | CssEmptyDeclaration AnyCssRuleBlock = @@ -653,6 +676,7 @@ AnyCssAtRule = | CssValueAtRule | CssPositionTryAtRule | CssViewTransitionAtRule + | CssFunctionAtRule // Tailwind CSS 4.0 at rules | TwThemeAtRule | TwSourceAtRule @@ -688,6 +712,7 @@ AnyCssAtRuleDeclarator = | CssSupportsAtRuleDeclarator | CssScopeAtRuleDeclarator | CssStartingStyleAtRuleDeclarator + | CssFunctionAtRuleDeclarator // Conditional block declarator nodes CssMediaAtRuleDeclarator = @@ -737,6 +762,14 @@ CssPositionTryAtRuleDeclarator = CssViewTransitionAtRuleDeclarator = 'view-transition' +CssFunctionAtRuleDeclarator = + 'function' + name: CssDashedIdentifier + '(' + parameters: CssFunctionParameterList + ')' + returns: CssReturnsStatement? + // @charset "UTF-8"; // ^^^^^^^^^^^^^^^^^ CssCharsetAtRule = @@ -1741,6 +1774,55 @@ CssViewTransitionAtRule = declarator: CssViewTransitionAtRuleDeclarator block: AnyCssDeclarationBlock +// https://drafts.csswg.org/css-mixins-1/#function-rule +// @function #? ) [ returns ]? { } +// +// @function --transparent(--color, --alpha) { } +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +CssFunctionAtRule = + declarator: CssFunctionAtRuleDeclarator + block: CssDeclarationOrAtRuleBlock + +// #? +// +/// @function --transparent(--color, --alpha) { } +// ^^^^^^^^^^^^^^^^ +CssFunctionParameterList = AnyCssFunctionParameter (',' AnyCssFunctionParameter)* + +AnyCssFunctionParameter = + CssFunctionParameter + | CssBogusFunctionParameter + +// +// +// @function --transparent(--color , --alpha ) { } +// ^^^^^^^ ^^^^^^^^ +AnyCssType = + CssTypeFunction + | AnyCssSyntaxComponent + | CssBogusType + +// @function --transparent(--color : red, --alpha : 0.5) { } +// ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^ +CssFunctionParameter = + name: CssDashedIdentifier + type: AnyCssType? + default_value: CssFunctionParameterDefaultValue? + +// @function --transparent(--color : red, --alpha : 0.5) { } +// ^^^^^ ^^^^^ +CssFunctionParameterDefaultValue = + ':' + value: AnyCssValue + +// returns +// +// @function --transparent(--color : red, --alpha : 0.5) returns { } +// ^^^^^^^^^^^^^^^ +CssReturnsStatement = + 'returns' + type: AnyCssType + /////////////// // AUXILIARY /////////////// @@ -1761,6 +1843,7 @@ AnyCssValue = | CssUnicodeRange | CssMetavariable | TwValueThemeReference + | ScssIdentifier // https://drafts.csswg.org/css-syntax/#typedef-dimension-token @@ -1808,6 +1891,7 @@ AnyCssFunction = CssFunction | CssUrlFunction | CssIfFunction + | CssAttrFunction // content: counter(section); // ^^^^^^^^^^^^^^^^ @@ -1993,9 +2077,15 @@ AnyCssExpression = CssBinaryExpression | CssParenthesizedExpression | CssListOfComponentValuesExpression + | CssCommaSeparatedValue CssListOfComponentValuesExpression = CssComponentValueList +CssCommaSeparatedValue = + '{' + items: CssGenericComponentValueList + '}' + CssBinaryExpression = left: AnyCssExpression operator_token: ( @@ -2003,7 +2093,6 @@ CssBinaryExpression = ) right: AnyCssExpression - CssParenthesizedExpression = '(' expression: AnyCssExpression? ')' @@ -2073,6 +2162,172 @@ CssUrlValueRaw = value: 'css_url_value_raw_literal' // https://github.com/getgrit/gritql/blob/main/resources/language-metavariables/tree-sitter-css/grammar.js CssMetavariable = value: 'grit_metavariable' +// https://drafts.csswg.org/css-values-5/#attr-notation +// attr() = attr( ? , ?) +// +// = [ ? '|' ]? +// = type( ) | raw-string | number | +// +// attr(data-test type(*), rem); +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +CssAttrFunction = + name: 'attr' + '(' + attr_name: CssAttrNameList + attr_type: AnyCssAttrType? + fallback_value: CssAttrFallbackValue? + ')' + +// [ ? '|' ]? +// attr(data-test | data-test-2 | data-test-3) +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +CssAttrNameList = (AnyCssAttrName ('|' AnyCssAttrName)*) + +AnyCssAttrName = + CssIdentifier + | CssBogusAttrName + +// Literal value that can be used as a type in the attr() function. +// attr(data-test raw-string) +// ^^^^^^^^^^ +CssRawStringDeclarator = + 'raw-string' + +// Literal value that can be used as a type in the attr() function. +// attr(data-test number) +// ^^^^^ +CssNumberDeclarator = + 'number' + +// attr(data-test rem); +// ^^^ +AnyCssAttrUnit = + CssRegularAttrUnit + | CssUnknownAttrUnit + +// A known CSS unit name such as "px", "rem", "em". +CssRegularAttrUnit = + unit: 'ident' + +// A fallback node for unrecognized attr unit names. +CssUnknownAttrUnit = + unit: 'ident' + +AnyCssAttrType = + CssRawStringDeclarator + | CssTypeFunction + | CssNumberDeclarator + | AnyCssAttrUnit + +// attr(data-test type(*), rem); +// ^^^^^ +CssAttrFallbackValue = + ',' + value: CssGenericComponentValueList + +// type( ) +CssTypeFunction = + name: 'type' + '(' + type: AnyCssSyntax + ')' + +// type(*) +// ^ +CssUniversalSyntax = + '*' + +// https://drafts.csswg.org/css-values-5/#typedef-syntax +// = '*' | [ ]* | +// = ? +// | '<' transform-list '>' +// = '<' '>' | +// = angle | color | custom-ident | image | integer +// | length | length-percentage | number +// | percentage | resolution | string | time +// | url | transform-function +// = '|' +// = [ '#' | '+' ] +// +// = +AnyCssSyntax = + CssUniversalSyntax + | CssSyntaxComponentList + | AnyCssSyntaxComponent + | CssString + | CssBogusSyntax + +// [ ]* +// +// type( | auto) +// ^^^^^^^^^^^^^^ +CssSyntaxComponentList = AnyCssSyntaxComponent ('|' AnyCssSyntaxComponent)* + +// ? | '<' transform-list '>' +// +// type(# | ) +// ^^^^^^^^ ^^^^^^^^^^^^^^^^ +AnyCssSyntaxComponent = + CssSyntaxComponentWithoutMultiplier + | CssSyntaxComponent + +// is treated differently from other syntax components +// type() +// ^^^^^^^^^^^^^^^^ +CssSyntaxComponentWithoutMultiplier = + '<' + type_name: 'ident' + '>' + +// ? +// +// type(#) +// ^^^^^^^^ +CssSyntaxComponent = + component: AnyCssSyntaxSingleComponent + multiplier: CssSyntaxMultiplier? + +//'<' '>' | +// +// type( | auto) +// ^^^^^^^ ^^^^ +AnyCssSyntaxSingleComponent = + CssSyntaxType + | CssIdentifier + | CssBogusSyntaxSingleComponent + +// '<' '>' +// +// type() +// ^^^^^^^ +AnyCssSyntaxTypeName = + CssRegularSyntaxTypeName + | CssUnknownSyntaxTypeName + +// A known syntax type name such as "angle", "color", "custom-ident" +// See https://drafts.csswg.org/css-values-5/#typedef-syntax for the complete set +// type() +// ^^^^^ +CssRegularSyntaxTypeName = + name: 'ident' + +// A fallback node for unrecognized syntax type names. +// type() +// ^^^^^^^ +CssUnknownSyntaxTypeName = + name: 'ident' + +// type() +// ^^^^^^^ +CssSyntaxType = + '<' + type_name: AnyCssSyntaxTypeName + '>' + +// type(# | +) +// ^ ^ +CssSyntaxMultiplier = + multiplier: ('#' | '+') // Tailwind @@ -2199,3 +2454,43 @@ TwPluginAtRule = TwSlotAtRule = 'slot' ';' + +///////////// +// SCSS +///////////// + +// $variable: value; +// ^^^^^^^^^^^^^^^^^ +ScssDeclaration = + name: AnyScssDeclarationName + ':' + value: CssGenericComponentValueList + modifiers: ScssVariableModifierList + ';'? + +// $var: 1 +// ^^^^ +AnyScssDeclarationName = + ScssIdentifier + | ScssNamespacedIdentifier + +// ns.$var: 1; +// ^^^^^^^ +ScssNamespacedIdentifier = + namespace: CssIdentifier + '.' + name: ScssIdentifier + +// $var: 1 !default !global; +// ^^^^^^^^^^^^^^^^ +ScssVariableModifierList = ScssVariableModifier* + +// $var: 1 !default; +// ^^^^^^^^ +ScssVariableModifier = + '!' + value: ('default' | 'global') + +// $var: 1; +// ^^^^ +ScssIdentifier = dollar: '$' name: CssIdentifier diff --git a/xtask/codegen/html.ungram b/xtask/codegen/html.ungram index c3fdf23136a4..0f10eb203449 100644 --- a/xtask/codegen/html.ungram +++ b/xtask/codegen/html.ungram @@ -109,7 +109,7 @@ HtmlTextExpression = 'html_literal' // HtmlSelfClosingElement = '<' - name: HtmlTagName + name: AnyHtmlTagName attributes: HtmlAttributeList '/'? '>' @@ -124,7 +124,7 @@ HtmlElement = // ^^ ^ HtmlOpeningElement = '<' - name: HtmlTagName + name: AnyHtmlTagName attributes: HtmlAttributeList '>' @@ -132,9 +132,29 @@ HtmlOpeningElement = HtmlClosingElement = '<' '/' - name: HtmlTagName + name: AnyHtmlTagName '>' +AnyHtmlTagName = + HtmlTagName + | HtmlComponentName + | HtmlMemberName + +// +// Used in Vue/Svelte/Astro files for PascalCase component names +HtmlComponentName = value: 'html_literal' + +AnyHtmlComponentObjectName = + HtmlTagName + | HtmlComponentName + | HtmlMemberName + +// +HtmlMemberName = + object: AnyHtmlComponentObjectName + '.' + member: HtmlTagName + // // Reference: https://html.spec.whatwg.org/multipage/syntax.html#cdata-sections HtmlCdataSection = @@ -165,6 +185,8 @@ AnyHtmlAttribute = | HtmlAttributeDoubleTextExpression | HtmlAttributeSingleTextExpression | SvelteAttachAttribute + | HtmlSpreadAttribute + | AnySvelteDirective | AnyVueDirective | HtmlBogusAttribute @@ -203,7 +225,6 @@ HtmlAttributeSingleTextExpression = expression: HtmlTextExpression '}' - // ================================== // Svelte // ================================== @@ -358,10 +379,47 @@ AnySvelteBlockItem = // ^^^^^^^^^^^^^^^^^^^^ SvelteEachAsKeyedItem = 'as' - name: HtmlTextExpression + name: AnySvelteEachName index: SvelteEachIndex? key: SvelteEachKey? + +AnySvelteEachName = + SvelteName + | AnySvelteDestructuredName + | HtmlTextExpression + + +AnySvelteDestructuredName = + SvelteCurlyDestructuredName + | SvelteSquareDestructuredName + +// {#each items as { foo, bar } } +// ^^^^^^^^^^^^ +SvelteCurlyDestructuredName = + '{' + names: SvelteBindingAssignmentBindingList + '}' + +// {#each items as [ foo, bar ] } +// ^^^^^^^^^^^^ +SvelteSquareDestructuredName = + '[' + names: SvelteBindingAssignmentBindingList + ']' + +SvelteBindingAssignmentBindingList = (AnySvelteBindingAssignmentBinding (',' AnySvelteBindingAssignmentBinding)*) + +AnySvelteBindingAssignmentBinding = + SvelteName + | SvelteRestBinding + +/// { ...rest } +/// ^^^^^^^ +SvelteRestBinding = + '...' + name: SvelteName + // {#each items, index} ... {/each} // ^^^^^^^^ SvelteEachKeyedItem = @@ -371,7 +429,7 @@ SvelteEachKeyedItem = // ^^^^^^^ SvelteEachIndex = ',' - value: HtmlTextExpression + value: SvelteName // {#each items as item, item (key)} ... {/each} // ^^^^^ @@ -473,8 +531,96 @@ SvelteSnippetClosingBlock = 'snippet' '}' +AnySvelteDirective = + SvelteBindDirective + | SvelteTransitionDirective + | SvelteInDirective + | SvelteOutDirective + | SvelteUseDirective + | SvelteAnimateDirective + | SvelteStyleDirective + | SvelteClassDirective + +//
    +// ^^^^^^^^^^^^^^^^^^ +SvelteBindDirective = + 'bind' + value: SvelteDirectiveValue + +//
    +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +SvelteTransitionDirective = + 'transition' + value: SvelteDirectiveValue + +//
    +// ^^^^^^ +SvelteInDirective = + 'in' + value: SvelteDirectiveValue + +//
    +// ^^^^^^^ +SvelteOutDirective = + 'out' + value: SvelteDirectiveValue + +//
    +// ^^^^^^^ +SvelteUseDirective = + 'use' + value: SvelteDirectiveValue + +//
    +// ^^^^^^^^^^^ +SvelteAnimateDirective = + 'animate' + value: SvelteDirectiveValue + +//
    +// ^^^^^^^^^ +SvelteStyleDirective = + 'style' + value: SvelteDirectiveValue + +//
    +// ^^^^^^^^^ +SvelteClassDirective = + 'class' + value: SvelteDirectiveValue + +//
    +// ^^^^^^^^^^^^^^^^^^^^ +SvelteDirectiveValue = + ':' + property: AnySvelteBindingProperty + modifiers: SvelteDirectiveModifierList + initializer: HtmlAttributeInitializerClause? + +AnySvelteBindingProperty = + SvelteName + | SvelteLiteral + +SvelteDirectiveModifierList = SvelteDirectiveModifier* + +//
    +// ^^^^^^^^^^^^^^ +SvelteDirectiveModifier = + '|' + name: SvelteName + +// +// ^^^^^^^^^^ +HtmlSpreadAttribute = + '{' + '...' + argument: HtmlTextExpression + '}' + + // Keep it different just for svelte SvelteName = 'ident' +SvelteLiteral = value: 'html_literal' HtmlString = value: 'html_string_literal' HtmlTagName = value: 'html_literal' HtmlAttributeName = value: 'html_literal' diff --git a/xtask/codegen/js.ungram b/xtask/codegen/js.ungram index 1640c81def0c..eebee9acb8d1 100644 --- a/xtask/codegen/js.ungram +++ b/xtask/codegen/js.ungram @@ -49,7 +49,7 @@ JsBogusNamedImportSpecifier = SyntaxElement* TsBogusType = SyntaxElement* AnyJsRoot = - JsScript | JsModule | JsExpressionSnipped | TsDeclarationModule + JsScript | JsModule | JsExpressionSnippet | TsDeclarationModule | JsExpressionTemplateRoot JsScript = bom: 'UNICODE_BOM'? @@ -72,7 +72,11 @@ TsDeclarationModule = items: JsModuleItemList eof: 'EOF' -JsExpressionSnipped = +JsExpressionSnippet = + expression: AnyJsExpression + eof: 'EOF' + +JsExpressionTemplateRoot = expression: AnyJsExpression eof: 'EOF' diff --git a/xtask/codegen/json.ungram b/xtask/codegen/json.ungram index 6b209c6910ee..2f7c91b0cd29 100644 --- a/xtask/codegen/json.ungram +++ b/xtask/codegen/json.ungram @@ -39,6 +39,7 @@ SyntaxElement = SyntaxElement JsonBogus = SyntaxElement* JsonBogusValue = SyntaxElement* +JsonBogusName = SyntaxElement* JsonRoot = bom: 'UNICODE_BOM'? @@ -52,13 +53,19 @@ AnyJsonValue = | JsonNumberValue | JsonArrayValue | JsonObjectValue + | JsonMetavariable | JsonBogusValue JsonObjectValue = '{' JsonMemberList '}' JsonMemberList = (JsonMember (',' JsonMember)* ','?) -JsonMember = name: JsonMemberName ':' value: AnyJsonValue +JsonMember = name: AnyJsonMemberName ':' value: AnyJsonValue + +AnyJsonMemberName = + JsonMemberName + | JsonMetavariable + | JsonBogusName JsonMemberName = value: 'json_string_literal' @@ -73,3 +80,6 @@ JsonNullValue = value: 'null' JsonStringValue = value: 'json_string_literal' JsonNumberValue = value: 'json_number_literal' + +// https://github.com/getgrit/gritql/blob/main/resources/language-metavariables/tree-sitter-json/grammar.js +JsonMetavariable = value: 'grit_metavariable' diff --git a/xtask/codegen/markdown.ungram b/xtask/codegen/markdown.ungram index b514b5c793a6..d818d4d9216d 100644 --- a/xtask/codegen/markdown.ungram +++ b/xtask/codegen/markdown.ungram @@ -1,6 +1,6 @@ // Markdown Un-Grammar. // -// This grammar specifies the structure of Rust's concrete syntax tree. +// This grammar specifies the structure of Markdown's concrete syntax tree. // It does not specify parsing rules (ambiguities, precedence, etc are out of scope). // Tokens are processed -- contextual keywords are recognised, compound operators glued. // @@ -48,22 +48,24 @@ MdDocument = MdBlockList = AnyMdBlock* AnyMdBlock = - AnyLeafBlock - | AnyContainerBlock + AnyMdLeafBlock + | AnyMdContainerBlock -AnyLeafBlock = +AnyMdLeafBlock = MdThematicBreakBlock | MdHeader | MdSetextHeader - | AnyCodeBlock + | AnyMdCodeBlock | MdHtmlBlock + | MdLinkReferenceDefinition | MdLinkBlock | MdParagraph + | MdNewline -AnyContainerBlock = +AnyMdContainerBlock = MdQuote | MdBulletListItem - | MdOrderListItem + | MdOrderedListItem @@ -84,68 +86,97 @@ MdHash = '#' // === // bar // --- -MdSetextHeader = MdParagraph +// The underline indicates heading level: '=' for H1, '-' for H2 +// The underline is stored as a setext underline literal token +MdSetextHeader = + content: MdInlineItemList + underline: 'md_setext_underline_literal' // indented code blocks & fenced code blocks -AnyCodeBlock = +AnyMdCodeBlock = MdIndentCodeBlock | MdFencedCodeBlock // code // ^^^^^^^^ // The space before "code" is intentional. +// Indentation is tracked in trivia, so we use MdInlineItemList for content. MdIndentCodeBlock = - lines: MdIndentedCodeLineList - -MdIndentedCodeLineList = MdIndentedCodeLine* - -MdIndentedCodeLine = - indentation: MdIndent - content: MdTextual + content: MdInlineItemList // ```shell // // ``` +// or +// ~~~shell +// +// ~~~ MdFencedCodeBlock = - l_fence: '```' + l_fence: ('```' | '~~~') code_list: MdCodeNameList - l_hard_line: MdHardLine - content: MdTextual - r_hard_line: MdHardLine - r_fence: '```' + content: MdInlineItemList + r_fence: ('```' | '~~~') -MdCodeNameList = (MdTextual (',' MdTextual)*) +MdCodeNameList = MdTextual* -// html block -MdHtmlBlock = MdTextual +// html block - content is stored as raw text (multiple textual tokens) +MdHtmlBlock = content: MdInlineItemList +// Link reference definition per CommonMark §4.7 +// [label]: destination "title" or [label]: destination 'title' or [label]: destination (title) +// Labels are case-insensitive and whitespace-normalized +MdLinkReferenceDefinition = + '[' + label: MdLinkLabel + ']' + ':' + destination: MdLinkDestination + title: MdLinkTitle? + +// Label for link reference definitions (CommonMark §4.7) +// Up to 999 chars, no unescaped brackets, whitespace-normalized +// Labels can contain multiple tokens (e.g., "foo-bar" is: foo, -, bar) +MdLinkLabel = content: MdInlineItemList + +// Destination URL for link reference definitions +// Can be angle-bracketed or bare url (no whitespace) +// Destinations can contain multiple tokens +MdLinkDestination = content: MdInlineItemList + +// Optional title for link reference definitions +// Quoted with ", ', or () +// Titles can contain multiple tokens +MdLinkTitle = content: MdInlineItemList + +// Legacy MdLinkBlock retained for backwards compatibility MdLinkBlock = label: MdTextual url: MdTextual title: MdTextual? -MdQuote = AnyMdBlock +MdQuote = + marker: '>' + content: MdBlockList MdBulletListItem = MdBulletList -MdOrderListItem = MdBulletList +MdOrderedListItem = MdBulletList MdBulletList = MdBullet* // - Hey! // ^^^^^^ +// 1. Hey! +// ^^^^^^^ MdBullet = - bullet: ('-' | '*') - space: 'md_textual_literal' - content: MdInlineItemList - -MdOrderList = AnyCodeBlock* + bullet: ('-' | '*' | '+' | 'md_ordered_list_marker') + content: MdBlockList // Any block paragraph // // Another block paragraph MdParagraph = list: MdInlineItemList - hard_line: MdHardLine + hard_line: MdHardLine? MdInlineItemList = AnyMdInline* @@ -156,6 +187,11 @@ AnyMdInline = | MdInlineItalic | MdInlineLink | MdInlineImage + | MdReferenceLink + | MdReferenceImage + | MdAutolink + | MdInlineHtml + | MdEntityReference | MdHtmlBlock | MdHardLine | MdSoftBreak @@ -191,39 +227,68 @@ MdInlineLink = text: MdInlineItemList ']' '(' - source: MdInlineItemList + destination: MdInlineItemList + title: MdLinkTitle? ')' -// [![alt](image)](link) -// ^^^^^^^^^^^^^^^^^^^^^ +// ![alt](image) +// ^^^^^^^^^^^^^^ MdInlineImage = + '!' + '[' + alt: MdInlineItemList + ']' + '(' + destination: MdInlineItemList + title: MdLinkTitle? + ')' + +// Reference-style link per CommonMark §6.5 +// Full reference: [text][label] +// Collapsed reference: [text][] +// Shortcut reference: [text] +MdReferenceLink = '[' + text: MdInlineItemList + ']' + label: MdReferenceLinkLabel? + +// Reference-style image per CommonMark §6.6 +// Full reference: ![alt][label] +// Collapsed reference: ![alt][] +// Shortcut reference: ![alt] +MdReferenceImage = '!' - alt: MdInlineImageAlt - source: MdInlineImageSource + '[' + alt: MdInlineItemList ']' - link: MdInlineImageLink? + label: MdReferenceLinkLabel? -// [![alt](image)](link) -// ^^^^^ -MdInlineImageAlt = +// Label part of a reference link/image +// Either [label] (full) or [] (collapsed) +// Absent for shortcut references +MdReferenceLinkLabel = '[' - content: MdInlineItemList + label: MdInlineItemList ']' -// [![alt](image)](link) -// ^^^^^^^ -MdInlineImageSource = - '(' - content: MdInlineItemList - ')' +// or +// Autolinks per CommonMark §6.4 (URI) and §6.5 (email) +MdAutolink = + '<' + value: MdInlineItemList + '>' -// [![alt](image)](link) -// ^^^^^^ -MdInlineImageLink = - '(' - content: MdInlineItemList - ')' +// Raw inline HTML per CommonMark §6.8 +// Includes: open tags, closing tags, comments, processing instructions, +// declarations, and CDATA sections +MdInlineHtml = value: MdInlineItemList + +// Entity and numeric character references per CommonMark §6.2 +// Named entities: &name; (2-31 alphanumeric chars starting with letter) +// Decimal numeric: &#digits; (1-7 decimal digits) +// Hexadecimal: &#xhex; or &#Xhex; (1-6 hex digits) +MdEntityReference = value: 'md_entity_literal' // *** // --- @@ -231,10 +296,12 @@ MdInlineImageLink = // https://spec.commonmark.org/0.31.2/#container-blocks-and-leaf-blocks MdThematicBreakBlock = value: 'md_thematic_break_literal' +// Explicit newline node for inter-block newlines. +// This preserves NEWLINEs in the CST without creating "newline-only paragraphs". +// Used when a NEWLINE appears between blocks and isn't part of inline content. +MdNewline = value: 'NEWLINE' + MdHardLine = value: 'md_hard_line_literal' MdSoftBreak = value: 'md_soft_break_literal' MdTextual = value: 'md_textual_literal' MdIndent = value: 'md_indent_chunk_literal' - - - diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index 25328dc0c364..a56d83d9f31c 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -13,6 +13,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { ("<", "L_ANGLE"), (">", "R_ANGLE"), ("~", "TILDE"), + ("$", "DOLLAR"), ("#", "HASH"), ("&", "AMP"), ("|", "PIPE"), @@ -59,6 +60,8 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "dir", "local", "global", + "slotted", + "deep", "any", "current", "past", @@ -95,6 +98,10 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "url", "if", "else", + "attr", + "type", + "raw-string", + "number", "src", "font-palette-values", "font-feature-values", @@ -259,6 +266,8 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "composes", "position-try", "view-transition", + "function", + "returns", // "font-face", // Don't add to the end of this list, add new keywords above the "HERE" @@ -293,6 +302,8 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { ], nodes: &[ "CSS_ROOT", + "CSS_ROOT_ITEM_LIST", + "CSS_SNIPPET_ROOT", "CSS_RULE_LIST", "CSS_QUALIFIED_RULE", "CSS_NESTED_QUALIFIED_RULE", @@ -311,12 +322,14 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_NUMBER", "CSS_PARAMETER", "CSS_PERCENTAGE", + "CSS_PERCENT_SIGN", "CSS_RATIO", "CSS_FUNCTION", "CSS_STRING", "CSS_VAR_FUNCTION", "CSS_VAR_FUNCTION_VALUE", "CSS_ATTRIBUTE_LIST", + "CSS_ATTR_FALLBACK_VALUE", "CSS_DECLARATION_LIST", "CSS_COMPONENT_VALUE_LIST", "CSS_GENERIC_COMPONENT_VALUE_LIST", @@ -383,6 +396,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { // Values "CSS_PARENTHESIZED_EXPRESSION", "CSS_LIST_OF_COMPONENT_VALUES_EXPRESSION", + "CSS_COMMA_SEPARATED_VALUE", "CSS_BINARY_EXPRESSION", "CSS_URL_VALUE_RAW", "CSS_URL_FUNCTION", @@ -398,6 +412,22 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_IF_TEST_BOOLEAN_AND_EXPR", "CSS_IF_TEST_BOOLEAN_OR_EXPR", "CSS_IF_TEST_BOOLEAN_EXPR_IN_PARENS", + "CSS_ATTR_FUNCTION", + "CSS_ATTR_NAME_LIST", + "CSS_TYPE_FUNCTION", + "CSS_REGULAR_ATTR_UNIT", + "CSS_UNKNOWN_ATTR_UNIT", + "CSS_SYNTAX_TYPE", + "CSS_REGULAR_SYNTAX_TYPE_NAME", + "CSS_UNKNOWN_SYNTAX_TYPE_NAME", + "CSS_SYNTAX_COMPONENT_LIST", + "CSS_SYNTAX_COMPONENT", + "CSS_SYNTAX_COMPONENT_WITHOUT_MULTIPLIER", + "CSS_SYNTAX_MULTIPLIER", + "CSS_UNIVERSAL_SYNTAX", + "CSS_RAW_STRING_DECLARATOR", + "CSS_NUMBER_DECLARATOR", + "CSS_ATTR_UNIT", "CSS_URL_MODIFIER_LIST", "CSS_COLOR", "CSS_BORDER", @@ -417,6 +447,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_FONT_PALETTE_VALUES_AT_RULE_DECLARATOR", "CSS_POSITION_TRY_AT_RULE_DECLARATOR", "CSS_VIEW_TRANSITION_AT_RULE_DECLARATOR", + "CSS_FUNCTION_AT_RULE_DECLARATOR", "CSS_MEDIA_AT_RULE_DECLARATOR", "CSS_CONTAINER_AT_RULE_DECLARATOR", "CSS_SUPPORTS_AT_RULE_DECLARATOR", @@ -427,6 +458,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_COUNTER_STYLE_AT_RULE", "CSS_PROPERTY_AT_RULE", "CSS_CONTAINER_AT_RULE", + "CSS_FUNCTION_AT_RULE", "CSS_CONTAINER_NOT_QUERY", "CSS_CONTAINER_AND_QUERY", "CSS_CONTAINER_OR_QUERY", @@ -528,6 +560,16 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_VALUE_AT_RULE_GENERIC_PROPERTY", "CSS_VALUE_AT_RULE_GENERIC_VALUE", "CSS_VIEW_TRANSITION_AT_RULE", + "CSS_FUNCTION_PARAMETER", + "CSS_FUNCTION_PARAMETER_DEFAULT_VALUE", + "CSS_FUNCTION_PARAMETER_LIST", + "CSS_RETURNS_STATEMENT", + // SCSS + "SCSS_DECLARATION", + "SCSS_NAMESPACED_IDENTIFIER", + "SCSS_VARIABLE_MODIFIER_LIST", + "SCSS_VARIABLE_MODIFIER", + "SCSS_IDENTIFIER", // Tailwind CSS 4.0 nodes "TW_THEME_AT_RULE", "TW_UTILITY_AT_RULE", @@ -578,6 +620,12 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { "CSS_BOGUS_IF_BRANCH", "CSS_BOGUS_IF_TEST", "CSS_BOGUS_IF_TEST_BOOLEAN_EXPR", + "CSS_BOGUS_ATTR_UNIT", + "CSS_BOGUS_SYNTAX", + "CSS_BOGUS_SYNTAX_SINGLE_COMPONENT", + "CSS_BOGUS_ATTR_NAME", + "CSS_BOGUS_FUNCTION_PARAMETER", + "CSS_BOGUS_TYPE", // Grit metavariable "CSS_METAVARIABLE", ], diff --git a/xtask/codegen/src/formatter.rs b/xtask/codegen/src/formatter.rs index 808dce548e53..ef867c694679 100644 --- a/xtask/codegen/src/formatter.rs +++ b/xtask/codegen/src/formatter.rs @@ -21,12 +21,12 @@ struct GitRepo { } impl GitRepo { - fn open() -> Self { + fn open(allow_dirty: bool, allow_staged: bool) -> Self { let root = project_root(); let repo = Repository::discover(&root).expect("failed to open git repo"); - let mut allow_staged = false; - let mut allow_dirty = false; + let mut allow_staged = allow_staged; + let mut allow_dirty = allow_dirty; for arg in env::args() { match arg.as_str() { "--allow-staged" => { @@ -241,8 +241,8 @@ enum NodeKind { Union { variants: Vec }, } -pub fn generate_formatters() { - let repo = GitRepo::open(); +pub fn generate_formatters(allow_dirty: bool, allow_staged: bool) { + let repo = GitRepo::open(allow_dirty, allow_staged); for language in ALL_LANGUAGE_KIND { generate_formatter(&repo, language); @@ -562,6 +562,7 @@ enum NodeDialect { Jsx, Json, Css, + Scss, Grit, Graphql, Html, @@ -569,6 +570,8 @@ enum NodeDialect { Svelte, Vue, Tailwind, + Yaml, + Markdown, } impl NodeDialect { @@ -579,9 +582,12 @@ impl NodeDialect { Self::Jsx, Self::Json, Self::Css, + Self::Scss, Self::Grit, Self::Graphql, Self::Html, + Self::Yaml, + Self::Markdown, ] } @@ -596,6 +602,7 @@ impl NodeDialect { Self::Jsx => "jsx", Self::Json => "json", Self::Css => "css", + Self::Scss => "scss", Self::Grit => "grit", Self::Graphql => "graphql", Self::Html => "html", @@ -603,6 +610,8 @@ impl NodeDialect { Self::Svelte => "svelte", Self::Vue => "vue", Self::Tailwind => "tailwind", + Self::Yaml => "yaml", + Self::Markdown => "markdown", } } @@ -613,6 +622,7 @@ impl NodeDialect { "Ts" => Self::Ts, "Json" => Self::Json, "Css" => Self::Css, + "Scss" => Self::Scss, "Grit" => Self::Grit, "Graphql" => Self::Graphql, "Html" => Self::Html, @@ -620,6 +630,8 @@ impl NodeDialect { "Svelte" => Self::Svelte, "Vue" => Self::Vue, "Tw" => Self::Tailwind, + "Yaml" => Self::Yaml, + "Md" => Self::Markdown, _ => { eprintln!("missing prefix {name}"); Self::Js @@ -915,7 +927,7 @@ impl LanguageKind { Self::Grit => "GritFormatter", Self::Html => "HtmlFormatter", Self::Yaml => "YamlFormatter", - Self::Markdown => "DemoFormatter", + Self::Markdown => "MarkdownFormatter", Self::Tailwind => "TailwindFormatter", }; @@ -931,7 +943,7 @@ impl LanguageKind { Self::Grit => "GritFormatContext", Self::Html => "HtmlFormatContext", Self::Yaml => "YamlFormatContext", - Self::Markdown => "DemoFormatterContext", + Self::Markdown => "MarkdownFormatContext", Self::Tailwind => "TailwindFormatContext", }; diff --git a/xtask/codegen/src/generate_analyzer.rs b/xtask/codegen/src/generate_analyzer.rs index fbf85eb0c1b4..65c5ff3ecf4e 100644 --- a/xtask/codegen/src/generate_analyzer.rs +++ b/xtask/codegen/src/generate_analyzer.rs @@ -136,7 +136,7 @@ pub fn generate_analyzer() -> Result<()> { )?; generate_analyzer_crate( "biome_html_analyze", - &["lint"], + &["lint", "assist"], update_html_registry_builder, )?; Ok(()) diff --git a/xtask/codegen/src/generate_bindings.rs b/xtask/codegen/src/generate_bindings.rs index e48b17761a19..212e01018b56 100644 --- a/xtask/codegen/src/generate_bindings.rs +++ b/xtask/codegen/src/generate_bindings.rs @@ -404,7 +404,12 @@ pub(crate) fn generate_workspace_bindings(mode: Mode) -> Result<()> { ) .build(); - let formatted = format_node(JsFormatOptions::new(JsFileSource::ts()), module.syntax()).unwrap(); + let formatted = format_node( + JsFormatOptions::new(JsFileSource::ts()), + module.syntax(), + false, + ) + .unwrap(); let printed = formatted.print().unwrap(); let code = printed.into_code(); diff --git a/xtask/codegen/src/generate_syntax_kinds.rs b/xtask/codegen/src/generate_syntax_kinds.rs index 29e5b4d20de3..64c3b4837f2a 100644 --- a/xtask/codegen/src/generate_syntax_kinds.rs +++ b/xtask/codegen/src/generate_syntax_kinds.rs @@ -102,7 +102,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", JS_STRING_LITERAL => "string literal", _ => return None, }; @@ -116,7 +116,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", CSS_STRING_LITERAL => "string literal", _ => return None, }; @@ -130,7 +130,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", JSON_STRING_LITERAL => "string literal", _ => return None, }; @@ -144,7 +144,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", _ => return None, }; Some(tok) @@ -157,7 +157,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", GRIT_STRING_LITERAL => "string literal", _ => return None, }; @@ -171,7 +171,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", HTML_STRING_LITERAL => "string literal", _ => return None, }; @@ -185,7 +185,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", GRAPHQL_STRING_LITERAL => "string literal", _ => return None, }; @@ -199,7 +199,7 @@ pub fn generate_syntax_kinds(grammar: KindsSrc, language_kind: LanguageKind) -> let tok = match self { #(#punctuation => #punctuation_strings,)* #(#full_keywords => #all_keyword_to_strings,)* - EOF => "EOF", + EOF => "", FLOW_START => "start of a flow node", FLOW_END => "end of a flow node", MAPPING_START => "start of a block mapping", diff --git a/xtask/codegen/src/html_kinds_src.rs b/xtask/codegen/src/html_kinds_src.rs index 678405461f8c..016932363db9 100644 --- a/xtask/codegen/src/html_kinds_src.rs +++ b/xtask/codegen/src/html_kinds_src.rs @@ -28,6 +28,8 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { ("#", "HASH"), ("(", "L_PAREN"), (")", "R_PAREN"), + ("...", "DOT3"), + ("|", "PIPE"), ], keywords: &[ "null", @@ -52,6 +54,14 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "await", "catch", "snippet", + "bind", + "transition", + "use", + "animate", + "in", + "out", + "style", + "class", ], literals: &["HTML_STRING_LITERAL", "HTML_LITERAL"], tokens: &["ERROR_TOKEN", "NEWLINE", "WHITESPACE", "IDENT"], @@ -67,6 +77,8 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "HTML_ATTRIBUTE_INITIALIZER_CLAUSE", "HTML_STRING", "HTML_TAG_NAME", + "HTML_COMPONENT_NAME", + "HTML_MEMBER_NAME", "HTML_ATTRIBUTE_NAME", "HTML_ELEMENT_LIST", "HTML_ATTRIBUTE_LIST", @@ -77,6 +89,7 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "HTML_DOUBLE_TEXT_EXPRESSION", "HTML_SINGLE_TEXT_EXPRESSION", "HTML_TEXT_EXPRESSION", + "HTML_SPREAD_ATTRIBUTE", "HTML_ATTRIBUTE_DOUBLE_TEXT_EXPRESSION", "HTML_ATTRIBUTE_SINGLE_TEXT_EXPRESSION", // Astro nodes @@ -117,6 +130,22 @@ pub const HTML_KINDS_SRC: KindsSrc = KindsSrc { "SVELTE_SNIPPET_BLOCK", "SVELTE_SNIPPET_OPENING_BLOCK", "SVELTE_SNIPPET_CLOSING_BLOCK", + "SVELTE_CURLY_DESTRUCTURED_NAME", + "SVELTE_SQUARE_DESTRUCTURED_NAME", + "SVELTE_BINDING_ASSIGNMENT_BINDING_LIST", + "SVELTE_REST_BINDING", + "SVELTE_BIND_DIRECTIVE", + "SVELTE_TRANSITION_DIRECTIVE", + "SVELTE_IN_DIRECTIVE", + "SVELTE_OUT_DIRECTIVE", + "SVELTE_USE_DIRECTIVE", + "SVELTE_ANIMATE_DIRECTIVE", + "SVELTE_STYLE_DIRECTIVE", + "SVELTE_CLASS_DIRECTIVE", + "SVELTE_DIRECTIVE_VALUE", + "SVELTE_DIRECTIVE_MODIFIER", + "SVELTE_DIRECTIVE_MODIFIER_LIST", + "SVELTE_LITERAL", // Vue nodes "VUE_DIRECTIVE", "VUE_DIRECTIVE_ARGUMENT", diff --git a/xtask/codegen/src/js_kinds_src.rs b/xtask/codegen/src/js_kinds_src.rs index 38edff725ab0..7a428d7fcf20 100644 --- a/xtask/codegen/src/js_kinds_src.rs +++ b/xtask/codegen/src/js_kinds_src.rs @@ -188,7 +188,8 @@ pub const JS_KINDS_SRC: KindsSrc = KindsSrc { "JS_MODULE_ITEM_LIST", "JS_SCRIPT", "TS_DECLARATION_MODULE", - "JS_EXPRESSION_SNIPPED", + "JS_EXPRESSION_SNIPPET", + "JS_EXPRESSION_TEMPLATE_ROOT", "JS_DIRECTIVE", "JS_DIRECTIVE_LIST", "JS_STATEMENT_LIST", diff --git a/xtask/codegen/src/json_kinds_src.rs b/xtask/codegen/src/json_kinds_src.rs index c81e1aea9fc3..b490fa534fa7 100644 --- a/xtask/codegen/src/json_kinds_src.rs +++ b/xtask/codegen/src/json_kinds_src.rs @@ -21,6 +21,7 @@ pub const JSON_KINDS_SRC: KindsSrc = KindsSrc { "IDENT", "COMMENT", "MULTILINE_COMMENT", + "GRIT_METAVARIABLE", ], nodes: &[ "JSON_ROOT", @@ -34,9 +35,11 @@ pub const JSON_KINDS_SRC: KindsSrc = KindsSrc { "JSON_MEMBER", "JSON_MEMBER_NAME", "JSON_ARRAY_ELEMENT_LIST", + // Grit metavariable node + "JSON_METAVARIABLE", // Bogus nodes "JSON_BOGUS", - "JSON_BOGUS_MEMBER_NAME", + "JSON_BOGUS_NAME", "JSON_BOGUS_VALUE", ], }; diff --git a/xtask/codegen/src/lib.rs b/xtask/codegen/src/lib.rs index 01dace0e41a4..ee78062c1561 100644 --- a/xtask/codegen/src/lib.rs +++ b/xtask/codegen/src/lib.rs @@ -90,7 +90,13 @@ pub fn to_capitalized(s: &str) -> String { pub enum TaskCommand { /// Generates formatters for each language #[bpaf(command)] - Formatter, + Formatter { + #[bpaf(long("allow-dirty"))] + allow_dirty: bool, + + #[bpaf(long("allow-staged"))] + allow_staged: bool, + }, /// Generate factory functions for the analyzer and the configuration of the analyzers #[bpaf(command)] Analyzer, diff --git a/xtask/codegen/src/main.rs b/xtask/codegen/src/main.rs index 517d6a14c9cb..67c5c14919b2 100644 --- a/xtask/codegen/src/main.rs +++ b/xtask/codegen/src/main.rs @@ -33,8 +33,11 @@ fn main() -> Result<()> { let result = task_command().fallback_to_usage().run(); match result { - TaskCommand::Formatter => { - generate_formatters(); + TaskCommand::Formatter { + allow_dirty, + allow_staged, + } => { + generate_formatters(allow_dirty, allow_staged); } TaskCommand::Analyzer => { generate_analyzer()?; @@ -81,7 +84,7 @@ fn main() -> Result<()> { TaskCommand::All => { generate_tables()?; generate_ast(Overwrite, vec![])?; - generate_formatters(); + generate_formatters(false, false); generate_analyzer()?; #[cfg(feature = "configuration")] generate_rules_configuration(Overwrite)?; diff --git a/xtask/codegen/src/markdown_kinds_src.rs b/xtask/codegen/src/markdown_kinds_src.rs index 1f99a289e34f..13154a184249 100644 --- a/xtask/codegen/src/markdown_kinds_src.rs +++ b/xtask/codegen/src/markdown_kinds_src.rs @@ -13,15 +13,18 @@ pub const MARKDOWN_KINDS_SRC: KindsSrc = KindsSrc { ("!", "BANG"), ("-", "MINUS"), ("*", "STAR"), + ("+", "PLUS"), ("**", "DOUBLE_STAR"), ("`", "BACKTICK"), ("```", "TRIPLE_BACKTICK"), ("~", "TILDE"), + ("~~~", "TRIPLE_TILDE"), (" ", "WHITESPACE3"), ("_", "UNDERSCORE"), ("__", "DOUBLE_UNDERSCORE"), ("#", "HASH"), (",", "COMMA"), + (":", "COLON"), ], keywords: &["null"], literals: &[ @@ -31,13 +34,17 @@ pub const MARKDOWN_KINDS_SRC: KindsSrc = KindsSrc { "MD_STRING_LITERAL", "MD_INDENT_CHUNK_LITERAL", "MD_THEMATIC_BREAK_LITERAL", + "MD_SETEXT_UNDERLINE_LITERAL", + "MD_ORDERED_LIST_MARKER", "MD_ERROR_LITERAL", + "MD_ENTITY_LITERAL", ], tokens: &["ERROR_TOKEN", "NEWLINE", "WHITESPACE", "TAB"], nodes: &[ // Bogus nodes "BOGUS", "MD_BOGUS", + "MD_BOGUS_BULLET", // node "MD_DOCUMENT", "MD_BLOCK_LIST", @@ -49,11 +56,14 @@ pub const MARKDOWN_KINDS_SRC: KindsSrc = KindsSrc { "MD_CODE_NAME_LIST", "MD_HTML_BLOCK", "MD_LINK_BLOCK", + "MD_LINK_REFERENCE_DEFINITION", + "MD_LINK_LABEL", + "MD_LINK_DESTINATION", + "MD_LINK_TITLE", "MD_QUOTE", - "MD_ORDER_LIST_ITEM", + "MD_ORDERED_LIST_ITEM", "MD_BULLET_LIST_ITEM", "MD_BULLET_LIST", - "MD_ORDER_LIST", "MD_PARAGRAPH", "MD_INLINE_ITEM_LIST", "MD_INLINE_EMPHASIS", @@ -62,6 +72,12 @@ pub const MARKDOWN_KINDS_SRC: KindsSrc = KindsSrc { "MD_BULLET", "MD_INLINE_LINK", "MD_INLINE_IMAGE", + "MD_REFERENCE_LINK", + "MD_REFERENCE_IMAGE", + "MD_REFERENCE_LINK_LABEL", + "MD_AUTOLINK", + "MD_INLINE_HTML", + "MD_ENTITY_REFERENCE", "MD_INLINE_IMAGE_ALT", "MD_INDENTED_CODE_LINE", "MD_INLINE_IMAGE_LINK", @@ -74,5 +90,6 @@ pub const MARKDOWN_KINDS_SRC: KindsSrc = KindsSrc { "MD_STRING", "MD_INDENT", "MD_THEMATIC_BREAK_BLOCK", + "MD_NEWLINE", ], }; diff --git a/xtask/coverage/Cargo.toml b/xtask/coverage/Cargo.toml index d5acb91d40d9..20a6b151606d 100644 --- a/xtask/coverage/Cargo.toml +++ b/xtask/coverage/Cargo.toml @@ -5,29 +5,31 @@ edition = "2024" publish = false [dependencies] -ascii_table = "4.0.8" -backtrace = "0.3.76" -biome_console = { workspace = true } -biome_diagnostics = { workspace = true } -biome_js_parser = { workspace = true } -biome_js_semantic = { workspace = true } -biome_js_syntax = { workspace = true } -biome_parser = { workspace = true } -biome_rowan = { workspace = true } -biome_string_case = { workspace = true } -camino = { workspace = true } -colored = "3.0.0" -indicatif = { version = "0.18.3", features = ["improved_unicode"] } -pico-args = { version = "0.5.0", features = ["eq-separator"] } -regex = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -serde_yaml = "0.9.34" -tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter", "std"] } -walkdir = { workspace = true } -xtask_glue = { workspace = true } -yastl = "0.1.2" +ascii_table = "4.0.8" +backtrace = "0.3.76" +biome_console = { workspace = true } +biome_diagnostics = { workspace = true } +biome_js_parser = { workspace = true } +biome_js_semantic = { workspace = true } +biome_js_syntax = { workspace = true } +biome_markdown_parser = { workspace = true, features = ["test_utils"] } +biome_markdown_syntax = { workspace = true } +biome_parser = { workspace = true } +biome_rowan = { workspace = true } +biome_string_case = { workspace = true } +camino = { workspace = true } +colored = "3.0.0" +indicatif = { version = "0.18.3", features = ["improved_unicode"] } +pico-args = { version = "0.5.0", features = ["eq-separator"] } +regex = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +serde_yaml = "0.9.34" +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter", "std"] } +walkdir = { workspace = true } +xtask_glue = { workspace = true } +yastl = "0.1.2" [lints] workspace = true diff --git a/xtask/coverage/src/lib.rs b/xtask/coverage/src/lib.rs index e7eb89ad53ea..f9c73ecd2991 100644 --- a/xtask/coverage/src/lib.rs +++ b/xtask/coverage/src/lib.rs @@ -1,6 +1,7 @@ pub mod compare; pub mod js; pub mod jsx; +pub mod markdown; mod reporters; pub mod results; mod runner; @@ -19,6 +20,7 @@ use crate::runner::{TestRunContext, TestSuite, run_test_suite}; use biome_parser::diagnostic::ParseDiagnostic; use biome_string_case::StrOnlyExtension; use jsx::jsx_babel::BabelJsxTestSuite; +use markdown::commonmark::CommonMarkTestSuite; use serde::{Deserialize, Serialize}; use std::any::Any; use symbols::msts::SymbolsMicrosoftTestSuite; @@ -164,6 +166,7 @@ const ALL_JS_SUITES: &str = "js"; const ALL_TS_SUITES: &str = "ts"; const ALL_JSX_SUITES: &str = "jsx"; const ALL_SYMBOLS_SUITES: &str = "symbols"; +const ALL_MARKDOWN_SUITES: &str = "markdown"; fn get_test_suites(suites: Option<&str>) -> Vec> { let suites = suites.unwrap_or("*").to_lowercase_cow(); @@ -177,13 +180,15 @@ fn get_test_suites(suites: Option<&str>) -> Vec> { ALL_TS_SUITES | "typescript" => ids.extend(["ts/microsoft", "ts/babel"]), ALL_JSX_SUITES => ids.extend(["jsx/babel"]), ALL_SYMBOLS_SUITES => ids.extend(["symbols/microsoft"]), - ALL_SUITES => ids.extend(["js", "ts", "jsx", "symbols"]), + ALL_MARKDOWN_SUITES => ids.extend(["markdown/commonmark"]), + ALL_SUITES => ids.extend(["js", "ts", "jsx", "symbols", "markdown"]), "js/262" => suites.push(Box::new(Test262TestSuite)), "ts/microsoft" => suites.push(Box::new(MicrosoftTypescriptTestSuite)), "ts/babel" => suites.push(Box::new(BabelTypescriptTestSuite)), "jsx/babel" => suites.push(Box::new(BabelJsxTestSuite)), "symbols/microsoft" => suites.push(Box::new(SymbolsMicrosoftTestSuite)), + "markdown/commonmark" => suites.push(Box::new(CommonMarkTestSuite)), _ => {} } diff --git a/xtask/coverage/src/main.rs b/xtask/coverage/src/main.rs index 58c8d62a4b7b..4e07297a81cc 100644 --- a/xtask/coverage/src/main.rs +++ b/xtask/coverage/src/main.rs @@ -48,10 +48,12 @@ OPTIONS js: will run all javascript suites; Same as \"js/262\"; ts: will run all typescript suites; Same as \"ts/microsoft,ts/babel\"; jsx: will run all jsx suites; Same as \"jsx/babel\"; + markdown: will run all markdown suites; Same as \"markdown/commonmark\"; js/262: will run https://github.com/tc39/test262/tree/main/test; ts/microsoft: will run https://github.com/microsoft/Typescript/tree/main/tests/cases ts/babel: will run https://github.com/babel/babel/tree/main/packages/babel-parser/test/fixtures/typescript jsx/babel: will run https://github.com/babel/babel/tree/main/packages/babel-parser/test/fixtures/jsx/basic + markdown/commonmark: will run CommonMark spec tests (https://spec.commonmark.org/) Default is \"*\". --filter= Filters out tests that don't match the query. --help Prints this help. diff --git a/xtask/coverage/src/markdown/commonmark.rs b/xtask/coverage/src/markdown/commonmark.rs new file mode 100644 index 000000000000..7685d6478f7c --- /dev/null +++ b/xtask/coverage/src/markdown/commonmark.rs @@ -0,0 +1,149 @@ +// CommonMark spec compliance tests (https://spec.commonmark.org/) +// +// Spec provenance: +// Version: 0.31.2 +// URL: https://spec.commonmark.org/0.31.2/spec.json +// Downloaded: 2026-01-24 +// Examples: 652 +// +// To update the spec, run: +// just update-commonmark-spec +// +// After updating, verify with: +// just test-markdown-conformance + +use crate::runner::{TestCase, TestRunOutcome, TestSuite}; +use biome_markdown_parser::{document_to_html, parse_markdown}; +use biome_markdown_syntax::MdDocument; +use biome_parser::diagnostic::ParseDiagnostic; +use biome_rowan::{AstNode, TextRange}; +use serde::Deserialize; +use std::io; +use std::path::Path; + +const SPEC_JSON: &str = include_str!("spec.json"); + +#[derive(Debug, Deserialize)] +struct SpecTest { + markdown: String, + html: String, + example: u32, + section: String, +} + +struct CommonMarkTestCase { + name: String, + markdown: String, + expected_html: String, +} + +impl TestCase for CommonMarkTestCase { + fn name(&self) -> &str { + &self.name + } + + fn run(&self) -> TestRunOutcome { + let parsed = parse_markdown(&self.markdown); + + let Some(document) = MdDocument::cast(parsed.syntax()) else { + return TestRunOutcome::IncorrectlyErrored { + errors: vec![ParseDiagnostic::new( + format!("Bogus node: {:?}", parsed.syntax().kind()), + TextRange::empty(0.into()), + )], + files: Default::default(), + }; + }; + + let actual = document_to_html( + &document, + parsed.list_tightness(), + parsed.list_item_indents(), + parsed.quote_indents(), + ); + + let expected = normalize_html(&self.expected_html); + let actual_normalized = normalize_html(&actual); + + if expected == actual_normalized { + TestRunOutcome::Passed(Default::default()) + } else { + TestRunOutcome::IncorrectlyErrored { + errors: vec![ParseDiagnostic::new( + format!( + "HTML mismatch\nExpected:\n{}\nActual:\n{}", + self.expected_html, actual + ), + TextRange::empty(0.into()), + )], + files: Default::default(), + } + } + } +} + +// Normalize HTML for comparison, preserving whitespace inside
     blocks.
    +fn normalize_html(html: &str) -> String {
    +    let mut result = Vec::new();
    +    let mut in_pre = false;
    +
    +    for line in html.lines() {
    +        if line.contains("") {
    +            in_pre = false;
    +        }
    +    }
    +
    +    result.join("\n").trim().to_string() + "\n"
    +}
    +
    +pub(crate) struct CommonMarkTestSuite;
    +
    +impl TestSuite for CommonMarkTestSuite {
    +    fn name(&self) -> &str {
    +        "markdown/commonmark"
    +    }
    +
    +    fn base_path(&self) -> &str {
    +        "xtask/coverage/src/markdown"
    +    }
    +
    +    fn checkout(&self) -> io::Result<()> {
    +        Ok(())
    +    }
    +
    +    fn is_test(&self, _path: &Path) -> bool {
    +        false
    +    }
    +
    +    fn load_test(&self, _path: &Path) -> Option> {
    +        None
    +    }
    +
    +    fn load_all(&self) -> Option>> {
    +        let tests: Vec =
    +            serde_json::from_str(SPEC_JSON).expect("Failed to parse spec.json");
    +
    +        let cases = tests
    +            .into_iter()
    +            .map(|spec| {
    +                Box::new(CommonMarkTestCase {
    +                    name: format!("example_{:03}_{}", spec.example, spec.section),
    +                    markdown: spec.markdown,
    +                    expected_html: spec.html,
    +                }) as Box
    +            })
    +            .collect();
    +
    +        Some(cases)
    +    }
    +}
    diff --git a/xtask/coverage/src/markdown/mod.rs b/xtask/coverage/src/markdown/mod.rs
    new file mode 100644
    index 000000000000..0fa90a17d950
    --- /dev/null
    +++ b/xtask/coverage/src/markdown/mod.rs
    @@ -0,0 +1 @@
    +pub mod commonmark;
    diff --git a/xtask/coverage/src/markdown/spec.json b/xtask/coverage/src/markdown/spec.json
    new file mode 100644
    index 000000000000..1f89e66f2ada
    --- /dev/null
    +++ b/xtask/coverage/src/markdown/spec.json
    @@ -0,0 +1,5218 @@
    +[
    +  {
    +    "markdown": "\tfoo\tbaz\t\tbim\n",
    +    "html": "
    foo\tbaz\t\tbim\n
    \n", + "example": 1, + "start_line": 355, + "end_line": 360, + "section": "Tabs" + }, + { + "markdown": " \tfoo\tbaz\t\tbim\n", + "html": "
    foo\tbaz\t\tbim\n
    \n", + "example": 2, + "start_line": 362, + "end_line": 367, + "section": "Tabs" + }, + { + "markdown": " a\ta\n ὐ\ta\n", + "html": "
    a\ta\nὐ\ta\n
    \n", + "example": 3, + "start_line": 369, + "end_line": 376, + "section": "Tabs" + }, + { + "markdown": " - foo\n\n\tbar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 4, + "start_line": 382, + "end_line": 393, + "section": "Tabs" + }, + { + "markdown": "- foo\n\n\t\tbar\n", + "html": "
      \n
    • \n

      foo

      \n
        bar\n
      \n
    • \n
    \n", + "example": 5, + "start_line": 395, + "end_line": 407, + "section": "Tabs" + }, + { + "markdown": ">\t\tfoo\n", + "html": "
    \n
      foo\n
    \n
    \n", + "example": 6, + "start_line": 418, + "end_line": 425, + "section": "Tabs" + }, + { + "markdown": "-\t\tfoo\n", + "html": "
      \n
    • \n
        foo\n
      \n
    • \n
    \n", + "example": 7, + "start_line": 427, + "end_line": 436, + "section": "Tabs" + }, + { + "markdown": " foo\n\tbar\n", + "html": "
    foo\nbar\n
    \n", + "example": 8, + "start_line": 439, + "end_line": 446, + "section": "Tabs" + }, + { + "markdown": " - foo\n - bar\n\t - baz\n", + "html": "
      \n
    • foo\n
        \n
      • bar\n
          \n
        • baz
        • \n
        \n
      • \n
      \n
    • \n
    \n", + "example": 9, + "start_line": 448, + "end_line": 464, + "section": "Tabs" + }, + { + "markdown": "#\tFoo\n", + "html": "

    Foo

    \n", + "example": 10, + "start_line": 466, + "end_line": 470, + "section": "Tabs" + }, + { + "markdown": "*\t*\t*\t\n", + "html": "
    \n", + "example": 11, + "start_line": 472, + "end_line": 476, + "section": "Tabs" + }, + { + "markdown": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n", + "html": "

    !"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~

    \n", + "example": 12, + "start_line": 489, + "end_line": 493, + "section": "Backslash escapes" + }, + { + "markdown": "\\\t\\A\\a\\ \\3\\φ\\«\n", + "html": "

    \\\t\\A\\a\\ \\3\\φ\\«

    \n", + "example": 13, + "start_line": 499, + "end_line": 503, + "section": "Backslash escapes" + }, + { + "markdown": "\\*not emphasized*\n\\
    not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\ö not a character entity\n", + "html": "

    *not emphasized*\n<br/> not a tag\n[not a link](/foo)\n`not code`\n1. not a list\n* not a list\n# not a heading\n[foo]: /url "not a reference"\n&ouml; not a character entity

    \n", + "example": 14, + "start_line": 509, + "end_line": 529, + "section": "Backslash escapes" + }, + { + "markdown": "\\\\*emphasis*\n", + "html": "

    \\emphasis

    \n", + "example": 15, + "start_line": 534, + "end_line": 538, + "section": "Backslash escapes" + }, + { + "markdown": "foo\\\nbar\n", + "html": "

    foo
    \nbar

    \n", + "example": 16, + "start_line": 543, + "end_line": 549, + "section": "Backslash escapes" + }, + { + "markdown": "`` \\[\\` ``\n", + "html": "

    \\[\\`

    \n", + "example": 17, + "start_line": 555, + "end_line": 559, + "section": "Backslash escapes" + }, + { + "markdown": " \\[\\]\n", + "html": "
    \\[\\]\n
    \n", + "example": 18, + "start_line": 562, + "end_line": 567, + "section": "Backslash escapes" + }, + { + "markdown": "~~~\n\\[\\]\n~~~\n", + "html": "
    \\[\\]\n
    \n", + "example": 19, + "start_line": 570, + "end_line": 577, + "section": "Backslash escapes" + }, + { + "markdown": "\n", + "html": "

    https://example.com?find=\\*

    \n", + "example": 20, + "start_line": 580, + "end_line": 584, + "section": "Backslash escapes" + }, + { + "markdown": "
    \n", + "html": "\n", + "example": 21, + "start_line": 587, + "end_line": 591, + "section": "Backslash escapes" + }, + { + "markdown": "[foo](/bar\\* \"ti\\*tle\")\n", + "html": "

    foo

    \n", + "example": 22, + "start_line": 597, + "end_line": 601, + "section": "Backslash escapes" + }, + { + "markdown": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n", + "html": "

    foo

    \n", + "example": 23, + "start_line": 604, + "end_line": 610, + "section": "Backslash escapes" + }, + { + "markdown": "``` foo\\+bar\nfoo\n```\n", + "html": "
    foo\n
    \n", + "example": 24, + "start_line": 613, + "end_line": 620, + "section": "Backslash escapes" + }, + { + "markdown": "  & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸\n", + "html": "

      & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸

    \n", + "example": 25, + "start_line": 649, + "end_line": 657, + "section": "Entity and numeric character references" + }, + { + "markdown": "# Ӓ Ϡ �\n", + "html": "

    # Ӓ Ϡ �

    \n", + "example": 26, + "start_line": 668, + "end_line": 672, + "section": "Entity and numeric character references" + }, + { + "markdown": "" ആ ಫ\n", + "html": "

    " ആ ಫ

    \n", + "example": 27, + "start_line": 681, + "end_line": 685, + "section": "Entity and numeric character references" + }, + { + "markdown": "  &x; &#; &#x;\n�\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n", + "html": "

    &nbsp &x; &#; &#x;\n&#87654321;\n&#abcdef0;\n&ThisIsNotDefined; &hi?;

    \n", + "example": 28, + "start_line": 690, + "end_line": 700, + "section": "Entity and numeric character references" + }, + { + "markdown": "©\n", + "html": "

    &copy

    \n", + "example": 29, + "start_line": 707, + "end_line": 711, + "section": "Entity and numeric character references" + }, + { + "markdown": "&MadeUpEntity;\n", + "html": "

    &MadeUpEntity;

    \n", + "example": 30, + "start_line": 717, + "end_line": 721, + "section": "Entity and numeric character references" + }, + { + "markdown": "\n", + "html": "\n", + "example": 31, + "start_line": 728, + "end_line": 732, + "section": "Entity and numeric character references" + }, + { + "markdown": "[foo](/föö \"föö\")\n", + "html": "

    foo

    \n", + "example": 32, + "start_line": 735, + "end_line": 739, + "section": "Entity and numeric character references" + }, + { + "markdown": "[foo]\n\n[foo]: /föö \"föö\"\n", + "html": "

    foo

    \n", + "example": 33, + "start_line": 742, + "end_line": 748, + "section": "Entity and numeric character references" + }, + { + "markdown": "``` föö\nfoo\n```\n", + "html": "
    foo\n
    \n", + "example": 34, + "start_line": 751, + "end_line": 758, + "section": "Entity and numeric character references" + }, + { + "markdown": "`föö`\n", + "html": "

    f&ouml;&ouml;

    \n", + "example": 35, + "start_line": 764, + "end_line": 768, + "section": "Entity and numeric character references" + }, + { + "markdown": " föfö\n", + "html": "
    f&ouml;f&ouml;\n
    \n", + "example": 36, + "start_line": 771, + "end_line": 776, + "section": "Entity and numeric character references" + }, + { + "markdown": "*foo*\n*foo*\n", + "html": "

    *foo*\nfoo

    \n", + "example": 37, + "start_line": 783, + "end_line": 789, + "section": "Entity and numeric character references" + }, + { + "markdown": "* foo\n\n* foo\n", + "html": "

    * foo

    \n
      \n
    • foo
    • \n
    \n", + "example": 38, + "start_line": 791, + "end_line": 800, + "section": "Entity and numeric character references" + }, + { + "markdown": "foo bar\n", + "html": "

    foo\n\nbar

    \n", + "example": 39, + "start_line": 802, + "end_line": 808, + "section": "Entity and numeric character references" + }, + { + "markdown": " foo\n", + "html": "

    \tfoo

    \n", + "example": 40, + "start_line": 810, + "end_line": 814, + "section": "Entity and numeric character references" + }, + { + "markdown": "[a](url "tit")\n", + "html": "

    [a](url "tit")

    \n", + "example": 41, + "start_line": 817, + "end_line": 821, + "section": "Entity and numeric character references" + }, + { + "markdown": "- `one\n- two`\n", + "html": "
      \n
    • `one
    • \n
    • two`
    • \n
    \n", + "example": 42, + "start_line": 840, + "end_line": 848, + "section": "Precedence" + }, + { + "markdown": "***\n---\n___\n", + "html": "
    \n
    \n
    \n", + "example": 43, + "start_line": 879, + "end_line": 887, + "section": "Thematic breaks" + }, + { + "markdown": "+++\n", + "html": "

    +++

    \n", + "example": 44, + "start_line": 892, + "end_line": 896, + "section": "Thematic breaks" + }, + { + "markdown": "===\n", + "html": "

    ===

    \n", + "example": 45, + "start_line": 899, + "end_line": 903, + "section": "Thematic breaks" + }, + { + "markdown": "--\n**\n__\n", + "html": "

    --\n**\n__

    \n", + "example": 46, + "start_line": 908, + "end_line": 916, + "section": "Thematic breaks" + }, + { + "markdown": " ***\n ***\n ***\n", + "html": "
    \n
    \n
    \n", + "example": 47, + "start_line": 921, + "end_line": 929, + "section": "Thematic breaks" + }, + { + "markdown": " ***\n", + "html": "
    ***\n
    \n", + "example": 48, + "start_line": 934, + "end_line": 939, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n ***\n", + "html": "

    Foo\n***

    \n", + "example": 49, + "start_line": 942, + "end_line": 948, + "section": "Thematic breaks" + }, + { + "markdown": "_____________________________________\n", + "html": "
    \n", + "example": 50, + "start_line": 953, + "end_line": 957, + "section": "Thematic breaks" + }, + { + "markdown": " - - -\n", + "html": "
    \n", + "example": 51, + "start_line": 962, + "end_line": 966, + "section": "Thematic breaks" + }, + { + "markdown": " ** * ** * ** * **\n", + "html": "
    \n", + "example": 52, + "start_line": 969, + "end_line": 973, + "section": "Thematic breaks" + }, + { + "markdown": "- - - -\n", + "html": "
    \n", + "example": 53, + "start_line": 976, + "end_line": 980, + "section": "Thematic breaks" + }, + { + "markdown": "- - - - \n", + "html": "
    \n", + "example": 54, + "start_line": 985, + "end_line": 989, + "section": "Thematic breaks" + }, + { + "markdown": "_ _ _ _ a\n\na------\n\n---a---\n", + "html": "

    _ _ _ _ a

    \n

    a------

    \n

    ---a---

    \n", + "example": 55, + "start_line": 994, + "end_line": 1004, + "section": "Thematic breaks" + }, + { + "markdown": " *-*\n", + "html": "

    -

    \n", + "example": 56, + "start_line": 1010, + "end_line": 1014, + "section": "Thematic breaks" + }, + { + "markdown": "- foo\n***\n- bar\n", + "html": "
      \n
    • foo
    • \n
    \n
    \n
      \n
    • bar
    • \n
    \n", + "example": 57, + "start_line": 1019, + "end_line": 1031, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n***\nbar\n", + "html": "

    Foo

    \n
    \n

    bar

    \n", + "example": 58, + "start_line": 1036, + "end_line": 1044, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n---\nbar\n", + "html": "

    Foo

    \n

    bar

    \n", + "example": 59, + "start_line": 1053, + "end_line": 1060, + "section": "Thematic breaks" + }, + { + "markdown": "* Foo\n* * *\n* Bar\n", + "html": "
      \n
    • Foo
    • \n
    \n
    \n
      \n
    • Bar
    • \n
    \n", + "example": 60, + "start_line": 1066, + "end_line": 1078, + "section": "Thematic breaks" + }, + { + "markdown": "- Foo\n- * * *\n", + "html": "
      \n
    • Foo
    • \n
    • \n
      \n
    • \n
    \n", + "example": 61, + "start_line": 1083, + "end_line": 1093, + "section": "Thematic breaks" + }, + { + "markdown": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n", + "html": "

    foo

    \n

    foo

    \n

    foo

    \n

    foo

    \n
    foo
    \n
    foo
    \n", + "example": 62, + "start_line": 1112, + "end_line": 1126, + "section": "ATX headings" + }, + { + "markdown": "####### foo\n", + "html": "

    ####### foo

    \n", + "example": 63, + "start_line": 1131, + "end_line": 1135, + "section": "ATX headings" + }, + { + "markdown": "#5 bolt\n\n#hashtag\n", + "html": "

    #5 bolt

    \n

    #hashtag

    \n", + "example": 64, + "start_line": 1146, + "end_line": 1153, + "section": "ATX headings" + }, + { + "markdown": "\\## foo\n", + "html": "

    ## foo

    \n", + "example": 65, + "start_line": 1158, + "end_line": 1162, + "section": "ATX headings" + }, + { + "markdown": "# foo *bar* \\*baz\\*\n", + "html": "

    foo bar *baz*

    \n", + "example": 66, + "start_line": 1167, + "end_line": 1171, + "section": "ATX headings" + }, + { + "markdown": "# foo \n", + "html": "

    foo

    \n", + "example": 67, + "start_line": 1176, + "end_line": 1180, + "section": "ATX headings" + }, + { + "markdown": " ### foo\n ## foo\n # foo\n", + "html": "

    foo

    \n

    foo

    \n

    foo

    \n", + "example": 68, + "start_line": 1185, + "end_line": 1193, + "section": "ATX headings" + }, + { + "markdown": " # foo\n", + "html": "
    # foo\n
    \n", + "example": 69, + "start_line": 1198, + "end_line": 1203, + "section": "ATX headings" + }, + { + "markdown": "foo\n # bar\n", + "html": "

    foo\n# bar

    \n", + "example": 70, + "start_line": 1206, + "end_line": 1212, + "section": "ATX headings" + }, + { + "markdown": "## foo ##\n ### bar ###\n", + "html": "

    foo

    \n

    bar

    \n", + "example": 71, + "start_line": 1217, + "end_line": 1223, + "section": "ATX headings" + }, + { + "markdown": "# foo ##################################\n##### foo ##\n", + "html": "

    foo

    \n
    foo
    \n", + "example": 72, + "start_line": 1228, + "end_line": 1234, + "section": "ATX headings" + }, + { + "markdown": "### foo ### \n", + "html": "

    foo

    \n", + "example": 73, + "start_line": 1239, + "end_line": 1243, + "section": "ATX headings" + }, + { + "markdown": "### foo ### b\n", + "html": "

    foo ### b

    \n", + "example": 74, + "start_line": 1250, + "end_line": 1254, + "section": "ATX headings" + }, + { + "markdown": "# foo#\n", + "html": "

    foo#

    \n", + "example": 75, + "start_line": 1259, + "end_line": 1263, + "section": "ATX headings" + }, + { + "markdown": "### foo \\###\n## foo #\\##\n# foo \\#\n", + "html": "

    foo ###

    \n

    foo ###

    \n

    foo #

    \n", + "example": 76, + "start_line": 1269, + "end_line": 1277, + "section": "ATX headings" + }, + { + "markdown": "****\n## foo\n****\n", + "html": "
    \n

    foo

    \n
    \n", + "example": 77, + "start_line": 1283, + "end_line": 1291, + "section": "ATX headings" + }, + { + "markdown": "Foo bar\n# baz\nBar foo\n", + "html": "

    Foo bar

    \n

    baz

    \n

    Bar foo

    \n", + "example": 78, + "start_line": 1294, + "end_line": 1302, + "section": "ATX headings" + }, + { + "markdown": "## \n#\n### ###\n", + "html": "

    \n

    \n

    \n", + "example": 79, + "start_line": 1307, + "end_line": 1315, + "section": "ATX headings" + }, + { + "markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n", + "html": "

    Foo bar

    \n

    Foo bar

    \n", + "example": 80, + "start_line": 1347, + "end_line": 1356, + "section": "Setext headings" + }, + { + "markdown": "Foo *bar\nbaz*\n====\n", + "html": "

    Foo bar\nbaz

    \n", + "example": 81, + "start_line": 1361, + "end_line": 1368, + "section": "Setext headings" + }, + { + "markdown": " Foo *bar\nbaz*\t\n====\n", + "html": "

    Foo bar\nbaz

    \n", + "example": 82, + "start_line": 1375, + "end_line": 1382, + "section": "Setext headings" + }, + { + "markdown": "Foo\n-------------------------\n\nFoo\n=\n", + "html": "

    Foo

    \n

    Foo

    \n", + "example": 83, + "start_line": 1387, + "end_line": 1396, + "section": "Setext headings" + }, + { + "markdown": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n", + "html": "

    Foo

    \n

    Foo

    \n

    Foo

    \n", + "example": 84, + "start_line": 1402, + "end_line": 1415, + "section": "Setext headings" + }, + { + "markdown": " Foo\n ---\n\n Foo\n---\n", + "html": "
    Foo\n---\n\nFoo\n
    \n
    \n", + "example": 85, + "start_line": 1420, + "end_line": 1433, + "section": "Setext headings" + }, + { + "markdown": "Foo\n ---- \n", + "html": "

    Foo

    \n", + "example": 86, + "start_line": 1439, + "end_line": 1444, + "section": "Setext headings" + }, + { + "markdown": "Foo\n ---\n", + "html": "

    Foo\n---

    \n", + "example": 87, + "start_line": 1449, + "end_line": 1455, + "section": "Setext headings" + }, + { + "markdown": "Foo\n= =\n\nFoo\n--- -\n", + "html": "

    Foo\n= =

    \n

    Foo

    \n
    \n", + "example": 88, + "start_line": 1460, + "end_line": 1471, + "section": "Setext headings" + }, + { + "markdown": "Foo \n-----\n", + "html": "

    Foo

    \n", + "example": 89, + "start_line": 1476, + "end_line": 1481, + "section": "Setext headings" + }, + { + "markdown": "Foo\\\n----\n", + "html": "

    Foo\\

    \n", + "example": 90, + "start_line": 1486, + "end_line": 1491, + "section": "Setext headings" + }, + { + "markdown": "`Foo\n----\n`\n\n\n", + "html": "

    `Foo

    \n

    `

    \n

    <a title="a lot

    \n

    of dashes"/>

    \n", + "example": 91, + "start_line": 1497, + "end_line": 1510, + "section": "Setext headings" + }, + { + "markdown": "> Foo\n---\n", + "html": "
    \n

    Foo

    \n
    \n
    \n", + "example": 92, + "start_line": 1516, + "end_line": 1524, + "section": "Setext headings" + }, + { + "markdown": "> foo\nbar\n===\n", + "html": "
    \n

    foo\nbar\n===

    \n
    \n", + "example": 93, + "start_line": 1527, + "end_line": 1537, + "section": "Setext headings" + }, + { + "markdown": "- Foo\n---\n", + "html": "
      \n
    • Foo
    • \n
    \n
    \n", + "example": 94, + "start_line": 1540, + "end_line": 1548, + "section": "Setext headings" + }, + { + "markdown": "Foo\nBar\n---\n", + "html": "

    Foo\nBar

    \n", + "example": 95, + "start_line": 1555, + "end_line": 1562, + "section": "Setext headings" + }, + { + "markdown": "---\nFoo\n---\nBar\n---\nBaz\n", + "html": "
    \n

    Foo

    \n

    Bar

    \n

    Baz

    \n", + "example": 96, + "start_line": 1568, + "end_line": 1580, + "section": "Setext headings" + }, + { + "markdown": "\n====\n", + "html": "

    ====

    \n", + "example": 97, + "start_line": 1585, + "end_line": 1590, + "section": "Setext headings" + }, + { + "markdown": "---\n---\n", + "html": "
    \n
    \n", + "example": 98, + "start_line": 1597, + "end_line": 1603, + "section": "Setext headings" + }, + { + "markdown": "- foo\n-----\n", + "html": "
      \n
    • foo
    • \n
    \n
    \n", + "example": 99, + "start_line": 1606, + "end_line": 1614, + "section": "Setext headings" + }, + { + "markdown": " foo\n---\n", + "html": "
    foo\n
    \n
    \n", + "example": 100, + "start_line": 1617, + "end_line": 1624, + "section": "Setext headings" + }, + { + "markdown": "> foo\n-----\n", + "html": "
    \n

    foo

    \n
    \n
    \n", + "example": 101, + "start_line": 1627, + "end_line": 1635, + "section": "Setext headings" + }, + { + "markdown": "\\> foo\n------\n", + "html": "

    > foo

    \n", + "example": 102, + "start_line": 1641, + "end_line": 1646, + "section": "Setext headings" + }, + { + "markdown": "Foo\n\nbar\n---\nbaz\n", + "html": "

    Foo

    \n

    bar

    \n

    baz

    \n", + "example": 103, + "start_line": 1672, + "end_line": 1682, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n\n---\n\nbaz\n", + "html": "

    Foo\nbar

    \n
    \n

    baz

    \n", + "example": 104, + "start_line": 1688, + "end_line": 1700, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n* * *\nbaz\n", + "html": "

    Foo\nbar

    \n
    \n

    baz

    \n", + "example": 105, + "start_line": 1706, + "end_line": 1716, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n\\---\nbaz\n", + "html": "

    Foo\nbar\n---\nbaz

    \n", + "example": 106, + "start_line": 1721, + "end_line": 1731, + "section": "Setext headings" + }, + { + "markdown": " a simple\n indented code block\n", + "html": "
    a simple\n  indented code block\n
    \n", + "example": 107, + "start_line": 1749, + "end_line": 1756, + "section": "Indented code blocks" + }, + { + "markdown": " - foo\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 108, + "start_line": 1763, + "end_line": 1774, + "section": "Indented code blocks" + }, + { + "markdown": "1. foo\n\n - bar\n", + "html": "
      \n
    1. \n

      foo

      \n
        \n
      • bar
      • \n
      \n
    2. \n
    \n", + "example": 109, + "start_line": 1777, + "end_line": 1790, + "section": "Indented code blocks" + }, + { + "markdown": "
    \n *hi*\n\n - one\n", + "html": "
    <a/>\n*hi*\n\n- one\n
    \n", + "example": 110, + "start_line": 1797, + "end_line": 1808, + "section": "Indented code blocks" + }, + { + "markdown": " chunk1\n\n chunk2\n \n \n \n chunk3\n", + "html": "
    chunk1\n\nchunk2\n\n\n\nchunk3\n
    \n", + "example": 111, + "start_line": 1813, + "end_line": 1830, + "section": "Indented code blocks" + }, + { + "markdown": " chunk1\n \n chunk2\n", + "html": "
    chunk1\n  \n  chunk2\n
    \n", + "example": 112, + "start_line": 1836, + "end_line": 1845, + "section": "Indented code blocks" + }, + { + "markdown": "Foo\n bar\n\n", + "html": "

    Foo\nbar

    \n", + "example": 113, + "start_line": 1851, + "end_line": 1858, + "section": "Indented code blocks" + }, + { + "markdown": " foo\nbar\n", + "html": "
    foo\n
    \n

    bar

    \n", + "example": 114, + "start_line": 1865, + "end_line": 1872, + "section": "Indented code blocks" + }, + { + "markdown": "# Heading\n foo\nHeading\n------\n foo\n----\n", + "html": "

    Heading

    \n
    foo\n
    \n

    Heading

    \n
    foo\n
    \n
    \n", + "example": 115, + "start_line": 1878, + "end_line": 1893, + "section": "Indented code blocks" + }, + { + "markdown": " foo\n bar\n", + "html": "
        foo\nbar\n
    \n", + "example": 116, + "start_line": 1898, + "end_line": 1905, + "section": "Indented code blocks" + }, + { + "markdown": "\n \n foo\n \n\n", + "html": "
    foo\n
    \n", + "example": 117, + "start_line": 1911, + "end_line": 1920, + "section": "Indented code blocks" + }, + { + "markdown": " foo \n", + "html": "
    foo  \n
    \n", + "example": 118, + "start_line": 1925, + "end_line": 1930, + "section": "Indented code blocks" + }, + { + "markdown": "```\n<\n >\n```\n", + "html": "
    <\n >\n
    \n", + "example": 119, + "start_line": 1980, + "end_line": 1989, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~\n<\n >\n~~~\n", + "html": "
    <\n >\n
    \n", + "example": 120, + "start_line": 1994, + "end_line": 2003, + "section": "Fenced code blocks" + }, + { + "markdown": "``\nfoo\n``\n", + "html": "

    foo

    \n", + "example": 121, + "start_line": 2007, + "end_line": 2013, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n~~~\n```\n", + "html": "
    aaa\n~~~\n
    \n", + "example": 122, + "start_line": 2018, + "end_line": 2027, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~\naaa\n```\n~~~\n", + "html": "
    aaa\n```\n
    \n", + "example": 123, + "start_line": 2030, + "end_line": 2039, + "section": "Fenced code blocks" + }, + { + "markdown": "````\naaa\n```\n``````\n", + "html": "
    aaa\n```\n
    \n", + "example": 124, + "start_line": 2044, + "end_line": 2053, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~\naaa\n~~~\n~~~~\n", + "html": "
    aaa\n~~~\n
    \n", + "example": 125, + "start_line": 2056, + "end_line": 2065, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n", + "html": "
    \n", + "example": 126, + "start_line": 2071, + "end_line": 2075, + "section": "Fenced code blocks" + }, + { + "markdown": "`````\n\n```\naaa\n", + "html": "
    \n```\naaa\n
    \n", + "example": 127, + "start_line": 2078, + "end_line": 2088, + "section": "Fenced code blocks" + }, + { + "markdown": "> ```\n> aaa\n\nbbb\n", + "html": "
    \n
    aaa\n
    \n
    \n

    bbb

    \n", + "example": 128, + "start_line": 2091, + "end_line": 2102, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n\n \n```\n", + "html": "
    \n  \n
    \n", + "example": 129, + "start_line": 2107, + "end_line": 2116, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n```\n", + "html": "
    \n", + "example": 130, + "start_line": 2121, + "end_line": 2126, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\naaa\n```\n", + "html": "
    aaa\naaa\n
    \n", + "example": 131, + "start_line": 2133, + "end_line": 2142, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\naaa\n aaa\naaa\n ```\n", + "html": "
    aaa\naaa\naaa\n
    \n", + "example": 132, + "start_line": 2145, + "end_line": 2156, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\n aaa\n aaa\n ```\n", + "html": "
    aaa\n aaa\naaa\n
    \n", + "example": 133, + "start_line": 2159, + "end_line": 2170, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\n ```\n", + "html": "
    ```\naaa\n```\n
    \n", + "example": 134, + "start_line": 2175, + "end_line": 2184, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n ```\n", + "html": "
    aaa\n
    \n", + "example": 135, + "start_line": 2190, + "end_line": 2197, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\naaa\n ```\n", + "html": "
    aaa\n
    \n", + "example": 136, + "start_line": 2200, + "end_line": 2207, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n ```\n", + "html": "
    aaa\n    ```\n
    \n", + "example": 137, + "start_line": 2212, + "end_line": 2220, + "section": "Fenced code blocks" + }, + { + "markdown": "``` ```\naaa\n", + "html": "

    \naaa

    \n", + "example": 138, + "start_line": 2226, + "end_line": 2232, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~~~\naaa\n~~~ ~~\n", + "html": "
    aaa\n~~~ ~~\n
    \n", + "example": 139, + "start_line": 2235, + "end_line": 2243, + "section": "Fenced code blocks" + }, + { + "markdown": "foo\n```\nbar\n```\nbaz\n", + "html": "

    foo

    \n
    bar\n
    \n

    baz

    \n", + "example": 140, + "start_line": 2249, + "end_line": 2260, + "section": "Fenced code blocks" + }, + { + "markdown": "foo\n---\n~~~\nbar\n~~~\n# baz\n", + "html": "

    foo

    \n
    bar\n
    \n

    baz

    \n", + "example": 141, + "start_line": 2266, + "end_line": 2278, + "section": "Fenced code blocks" + }, + { + "markdown": "```ruby\ndef foo(x)\n return 3\nend\n```\n", + "html": "
    def foo(x)\n  return 3\nend\n
    \n", + "example": 142, + "start_line": 2288, + "end_line": 2299, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n", + "html": "
    def foo(x)\n  return 3\nend\n
    \n", + "example": 143, + "start_line": 2302, + "end_line": 2313, + "section": "Fenced code blocks" + }, + { + "markdown": "````;\n````\n", + "html": "
    \n", + "example": 144, + "start_line": 2316, + "end_line": 2321, + "section": "Fenced code blocks" + }, + { + "markdown": "``` aa ```\nfoo\n", + "html": "

    aa\nfoo

    \n", + "example": 145, + "start_line": 2326, + "end_line": 2332, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~ aa ``` ~~~\nfoo\n~~~\n", + "html": "
    foo\n
    \n", + "example": 146, + "start_line": 2337, + "end_line": 2344, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n``` aaa\n```\n", + "html": "
    ``` aaa\n
    \n", + "example": 147, + "start_line": 2349, + "end_line": 2356, + "section": "Fenced code blocks" + }, + { + "markdown": "
    \n
    \n**Hello**,\n\n_world_.\n
    \n
    \n", + "html": "
    \n
    \n**Hello**,\n

    world.\n

    \n
    \n", + "example": 148, + "start_line": 2428, + "end_line": 2443, + "section": "HTML blocks" + }, + { + "markdown": "\n \n \n \n
    \n hi\n
    \n\nokay.\n", + "html": "\n \n \n \n
    \n hi\n
    \n

    okay.

    \n", + "example": 149, + "start_line": 2457, + "end_line": 2476, + "section": "HTML blocks" + }, + { + "markdown": "
    \n*foo*\n", + "example": 151, + "start_line": 2492, + "end_line": 2498, + "section": "HTML blocks" + }, + { + "markdown": "
    \n\n*Markdown*\n\n
    \n", + "html": "
    \n

    Markdown

    \n
    \n", + "example": 152, + "start_line": 2503, + "end_line": 2513, + "section": "HTML blocks" + }, + { + "markdown": "
    \n
    \n", + "html": "
    \n
    \n", + "example": 153, + "start_line": 2519, + "end_line": 2527, + "section": "HTML blocks" + }, + { + "markdown": "
    \n
    \n", + "html": "
    \n
    \n", + "example": 154, + "start_line": 2530, + "end_line": 2538, + "section": "HTML blocks" + }, + { + "markdown": "
    \n*foo*\n\n*bar*\n", + "html": "
    \n*foo*\n

    bar

    \n", + "example": 155, + "start_line": 2542, + "end_line": 2551, + "section": "HTML blocks" + }, + { + "markdown": "
    \n", + "html": "\n", + "example": 159, + "start_line": 2591, + "end_line": 2595, + "section": "HTML blocks" + }, + { + "markdown": "
    \nfoo\n
    \n", + "html": "
    \nfoo\n
    \n", + "example": 160, + "start_line": 2598, + "end_line": 2606, + "section": "HTML blocks" + }, + { + "markdown": "
    \n``` c\nint x = 33;\n```\n", + "html": "
    \n``` c\nint x = 33;\n```\n", + "example": 161, + "start_line": 2615, + "end_line": 2625, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 162, + "start_line": 2632, + "end_line": 2640, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 163, + "start_line": 2645, + "end_line": 2653, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 164, + "start_line": 2656, + "end_line": 2664, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n", + "html": "\n*bar*\n", + "example": 165, + "start_line": 2667, + "end_line": 2673, + "section": "HTML blocks" + }, + { + "markdown": "\n*foo*\n\n", + "html": "\n*foo*\n\n", + "example": 166, + "start_line": 2682, + "end_line": 2690, + "section": "HTML blocks" + }, + { + "markdown": "\n\n*foo*\n\n\n", + "html": "\n

    foo

    \n
    \n", + "example": 167, + "start_line": 2697, + "end_line": 2707, + "section": "HTML blocks" + }, + { + "markdown": "*foo*\n", + "html": "

    foo

    \n", + "example": 168, + "start_line": 2715, + "end_line": 2719, + "section": "HTML blocks" + }, + { + "markdown": "
    \nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
    \nokay\n", + "html": "
    \nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
    \n

    okay

    \n", + "example": 169, + "start_line": 2731, + "end_line": 2747, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

    okay

    \n", + "example": 170, + "start_line": 2752, + "end_line": 2766, + "section": "HTML blocks" + }, + { + "markdown": "\n", + "html": "\n", + "example": 171, + "start_line": 2771, + "end_line": 2787, + "section": "HTML blocks" + }, + { + "markdown": "\nh1 {color:red;}\n\np {color:blue;}\n\nokay\n", + "html": "\nh1 {color:red;}\n\np {color:blue;}\n\n

    okay

    \n", + "example": 172, + "start_line": 2791, + "end_line": 2807, + "section": "HTML blocks" + }, + { + "markdown": "\n\nfoo\n", + "html": "\n\nfoo\n", + "example": 173, + "start_line": 2814, + "end_line": 2824, + "section": "HTML blocks" + }, + { + "markdown": ">
    \n> foo\n\nbar\n", + "html": "
    \n
    \nfoo\n
    \n

    bar

    \n", + "example": 174, + "start_line": 2827, + "end_line": 2838, + "section": "HTML blocks" + }, + { + "markdown": "-
    \n- foo\n", + "html": "
      \n
    • \n
      \n
    • \n
    • foo
    • \n
    \n", + "example": 175, + "start_line": 2841, + "end_line": 2851, + "section": "HTML blocks" + }, + { + "markdown": "\n*foo*\n", + "html": "\n

    foo

    \n", + "example": 176, + "start_line": 2856, + "end_line": 2862, + "section": "HTML blocks" + }, + { + "markdown": "*bar*\n*baz*\n", + "html": "*bar*\n

    baz

    \n", + "example": 177, + "start_line": 2865, + "end_line": 2871, + "section": "HTML blocks" + }, + { + "markdown": "1. *bar*\n", + "html": "1. *bar*\n", + "example": 178, + "start_line": 2877, + "end_line": 2885, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

    okay

    \n", + "example": 179, + "start_line": 2890, + "end_line": 2902, + "section": "HTML blocks" + }, + { + "markdown": "';\n\n?>\nokay\n", + "html": "';\n\n?>\n

    okay

    \n", + "example": 180, + "start_line": 2908, + "end_line": 2922, + "section": "HTML blocks" + }, + { + "markdown": "\n", + "html": "\n", + "example": 181, + "start_line": 2927, + "end_line": 2931, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

    okay

    \n", + "example": 182, + "start_line": 2936, + "end_line": 2964, + "section": "HTML blocks" + }, + { + "markdown": " \n\n \n", + "html": " \n
    <!-- foo -->\n
    \n", + "example": 183, + "start_line": 2970, + "end_line": 2978, + "section": "HTML blocks" + }, + { + "markdown": "
    \n\n
    \n", + "html": "
    \n
    <div>\n
    \n", + "example": 184, + "start_line": 2981, + "end_line": 2989, + "section": "HTML blocks" + }, + { + "markdown": "Foo\n
    \nbar\n
    \n", + "html": "

    Foo

    \n
    \nbar\n
    \n", + "example": 185, + "start_line": 2995, + "end_line": 3005, + "section": "HTML blocks" + }, + { + "markdown": "
    \nbar\n
    \n*foo*\n", + "html": "
    \nbar\n
    \n*foo*\n", + "example": 186, + "start_line": 3012, + "end_line": 3022, + "section": "HTML blocks" + }, + { + "markdown": "Foo\n\nbaz\n", + "html": "

    Foo\n\nbaz

    \n", + "example": 187, + "start_line": 3027, + "end_line": 3035, + "section": "HTML blocks" + }, + { + "markdown": "
    \n\n*Emphasized* text.\n\n
    \n", + "html": "
    \n

    Emphasized text.

    \n
    \n", + "example": 188, + "start_line": 3068, + "end_line": 3078, + "section": "HTML blocks" + }, + { + "markdown": "
    \n*Emphasized* text.\n
    \n", + "html": "
    \n*Emphasized* text.\n
    \n", + "example": 189, + "start_line": 3081, + "end_line": 3089, + "section": "HTML blocks" + }, + { + "markdown": "\n\n\n\n\n\n\n\n
    \nHi\n
    \n", + "html": "\n\n\n\n
    \nHi\n
    \n", + "example": 190, + "start_line": 3103, + "end_line": 3123, + "section": "HTML blocks" + }, + { + "markdown": "\n\n \n\n \n\n \n\n
    \n Hi\n
    \n", + "html": "\n \n
    <td>\n  Hi\n</td>\n
    \n \n
    \n", + "example": 191, + "start_line": 3130, + "end_line": 3151, + "section": "HTML blocks" + }, + { + "markdown": "[foo]: /url \"title\"\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 192, + "start_line": 3179, + "end_line": 3185, + "section": "Link reference definitions" + }, + { + "markdown": " [foo]: \n /url \n 'the title' \n\n[foo]\n", + "html": "

    foo

    \n", + "example": 193, + "start_line": 3188, + "end_line": 3196, + "section": "Link reference definitions" + }, + { + "markdown": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n", + "html": "

    Foo*bar]

    \n", + "example": 194, + "start_line": 3199, + "end_line": 3205, + "section": "Link reference definitions" + }, + { + "markdown": "[Foo bar]:\n\n'title'\n\n[Foo bar]\n", + "html": "

    Foo bar

    \n", + "example": 195, + "start_line": 3208, + "end_line": 3216, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 196, + "start_line": 3221, + "end_line": 3235, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n", + "html": "

    [foo]: /url 'title

    \n

    with blank line'

    \n

    [foo]

    \n", + "example": 197, + "start_line": 3240, + "end_line": 3250, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]:\n/url\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 198, + "start_line": 3255, + "end_line": 3262, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]:\n\n[foo]\n", + "html": "

    [foo]:

    \n

    [foo]

    \n", + "example": 199, + "start_line": 3267, + "end_line": 3274, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: <>\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 200, + "start_line": 3279, + "end_line": 3285, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: (baz)\n\n[foo]\n", + "html": "

    [foo]: (baz)

    \n

    [foo]

    \n", + "example": 201, + "start_line": 3290, + "end_line": 3297, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 202, + "start_line": 3303, + "end_line": 3309, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n[foo]: url\n", + "html": "

    foo

    \n", + "example": 203, + "start_line": 3314, + "end_line": 3320, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n[foo]: first\n[foo]: second\n", + "html": "

    foo

    \n", + "example": 204, + "start_line": 3326, + "end_line": 3333, + "section": "Link reference definitions" + }, + { + "markdown": "[FOO]: /url\n\n[Foo]\n", + "html": "

    Foo

    \n", + "example": 205, + "start_line": 3339, + "end_line": 3345, + "section": "Link reference definitions" + }, + { + "markdown": "[ΑΓΩ]: /φου\n\n[αγω]\n", + "html": "

    αγω

    \n", + "example": 206, + "start_line": 3348, + "end_line": 3354, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n", + "html": "", + "example": 207, + "start_line": 3363, + "end_line": 3366, + "section": "Link reference definitions" + }, + { + "markdown": "[\nfoo\n]: /url\nbar\n", + "html": "

    bar

    \n", + "example": 208, + "start_line": 3371, + "end_line": 3378, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url \"title\" ok\n", + "html": "

    [foo]: /url "title" ok

    \n", + "example": 209, + "start_line": 3384, + "end_line": 3388, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n\"title\" ok\n", + "html": "

    "title" ok

    \n", + "example": 210, + "start_line": 3393, + "end_line": 3398, + "section": "Link reference definitions" + }, + { + "markdown": " [foo]: /url \"title\"\n\n[foo]\n", + "html": "
    [foo]: /url "title"\n
    \n

    [foo]

    \n", + "example": 211, + "start_line": 3404, + "end_line": 3412, + "section": "Link reference definitions" + }, + { + "markdown": "```\n[foo]: /url\n```\n\n[foo]\n", + "html": "
    [foo]: /url\n
    \n

    [foo]

    \n", + "example": 212, + "start_line": 3418, + "end_line": 3428, + "section": "Link reference definitions" + }, + { + "markdown": "Foo\n[bar]: /baz\n\n[bar]\n", + "html": "

    Foo\n[bar]: /baz

    \n

    [bar]

    \n", + "example": 213, + "start_line": 3433, + "end_line": 3442, + "section": "Link reference definitions" + }, + { + "markdown": "# [Foo]\n[foo]: /url\n> bar\n", + "html": "

    Foo

    \n
    \n

    bar

    \n
    \n", + "example": 214, + "start_line": 3448, + "end_line": 3457, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\nbar\n===\n[foo]\n", + "html": "

    bar

    \n

    foo

    \n", + "example": 215, + "start_line": 3459, + "end_line": 3467, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n===\n[foo]\n", + "html": "

    ===\nfoo

    \n", + "example": 216, + "start_line": 3469, + "end_line": 3476, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n", + "html": "

    foo,\nbar,\nbaz

    \n", + "example": 217, + "start_line": 3482, + "end_line": 3495, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n> [foo]: /url\n", + "html": "

    foo

    \n
    \n
    \n", + "example": 218, + "start_line": 3503, + "end_line": 3511, + "section": "Link reference definitions" + }, + { + "markdown": "aaa\n\nbbb\n", + "html": "

    aaa

    \n

    bbb

    \n", + "example": 219, + "start_line": 3525, + "end_line": 3532, + "section": "Paragraphs" + }, + { + "markdown": "aaa\nbbb\n\nccc\nddd\n", + "html": "

    aaa\nbbb

    \n

    ccc\nddd

    \n", + "example": 220, + "start_line": 3537, + "end_line": 3548, + "section": "Paragraphs" + }, + { + "markdown": "aaa\n\n\nbbb\n", + "html": "

    aaa

    \n

    bbb

    \n", + "example": 221, + "start_line": 3553, + "end_line": 3561, + "section": "Paragraphs" + }, + { + "markdown": " aaa\n bbb\n", + "html": "

    aaa\nbbb

    \n", + "example": 222, + "start_line": 3566, + "end_line": 3572, + "section": "Paragraphs" + }, + { + "markdown": "aaa\n bbb\n ccc\n", + "html": "

    aaa\nbbb\nccc

    \n", + "example": 223, + "start_line": 3578, + "end_line": 3586, + "section": "Paragraphs" + }, + { + "markdown": " aaa\nbbb\n", + "html": "

    aaa\nbbb

    \n", + "example": 224, + "start_line": 3592, + "end_line": 3598, + "section": "Paragraphs" + }, + { + "markdown": " aaa\nbbb\n", + "html": "
    aaa\n
    \n

    bbb

    \n", + "example": 225, + "start_line": 3601, + "end_line": 3608, + "section": "Paragraphs" + }, + { + "markdown": "aaa \nbbb \n", + "html": "

    aaa
    \nbbb

    \n", + "example": 226, + "start_line": 3615, + "end_line": 3621, + "section": "Paragraphs" + }, + { + "markdown": " \n\naaa\n \n\n# aaa\n\n \n", + "html": "

    aaa

    \n

    aaa

    \n", + "example": 227, + "start_line": 3632, + "end_line": 3644, + "section": "Blank lines" + }, + { + "markdown": "> # Foo\n> bar\n> baz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 228, + "start_line": 3700, + "end_line": 3710, + "section": "Block quotes" + }, + { + "markdown": "># Foo\n>bar\n> baz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 229, + "start_line": 3715, + "end_line": 3725, + "section": "Block quotes" + }, + { + "markdown": " > # Foo\n > bar\n > baz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 230, + "start_line": 3730, + "end_line": 3740, + "section": "Block quotes" + }, + { + "markdown": " > # Foo\n > bar\n > baz\n", + "html": "
    > # Foo\n> bar\n> baz\n
    \n", + "example": 231, + "start_line": 3745, + "end_line": 3754, + "section": "Block quotes" + }, + { + "markdown": "> # Foo\n> bar\nbaz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 232, + "start_line": 3760, + "end_line": 3770, + "section": "Block quotes" + }, + { + "markdown": "> bar\nbaz\n> foo\n", + "html": "
    \n

    bar\nbaz\nfoo

    \n
    \n", + "example": 233, + "start_line": 3776, + "end_line": 3786, + "section": "Block quotes" + }, + { + "markdown": "> foo\n---\n", + "html": "
    \n

    foo

    \n
    \n
    \n", + "example": 234, + "start_line": 3800, + "end_line": 3808, + "section": "Block quotes" + }, + { + "markdown": "> - foo\n- bar\n", + "html": "
    \n
      \n
    • foo
    • \n
    \n
    \n
      \n
    • bar
    • \n
    \n", + "example": 235, + "start_line": 3820, + "end_line": 3832, + "section": "Block quotes" + }, + { + "markdown": "> foo\n bar\n", + "html": "
    \n
    foo\n
    \n
    \n
    bar\n
    \n", + "example": 236, + "start_line": 3838, + "end_line": 3848, + "section": "Block quotes" + }, + { + "markdown": "> ```\nfoo\n```\n", + "html": "
    \n
    \n
    \n

    foo

    \n
    \n", + "example": 237, + "start_line": 3851, + "end_line": 3861, + "section": "Block quotes" + }, + { + "markdown": "> foo\n - bar\n", + "html": "
    \n

    foo\n- bar

    \n
    \n", + "example": 238, + "start_line": 3867, + "end_line": 3875, + "section": "Block quotes" + }, + { + "markdown": ">\n", + "html": "
    \n
    \n", + "example": 239, + "start_line": 3891, + "end_line": 3896, + "section": "Block quotes" + }, + { + "markdown": ">\n> \n> \n", + "html": "
    \n
    \n", + "example": 240, + "start_line": 3899, + "end_line": 3906, + "section": "Block quotes" + }, + { + "markdown": ">\n> foo\n> \n", + "html": "
    \n

    foo

    \n
    \n", + "example": 241, + "start_line": 3911, + "end_line": 3919, + "section": "Block quotes" + }, + { + "markdown": "> foo\n\n> bar\n", + "html": "
    \n

    foo

    \n
    \n
    \n

    bar

    \n
    \n", + "example": 242, + "start_line": 3924, + "end_line": 3935, + "section": "Block quotes" + }, + { + "markdown": "> foo\n> bar\n", + "html": "
    \n

    foo\nbar

    \n
    \n", + "example": 243, + "start_line": 3946, + "end_line": 3954, + "section": "Block quotes" + }, + { + "markdown": "> foo\n>\n> bar\n", + "html": "
    \n

    foo

    \n

    bar

    \n
    \n", + "example": 244, + "start_line": 3959, + "end_line": 3968, + "section": "Block quotes" + }, + { + "markdown": "foo\n> bar\n", + "html": "

    foo

    \n
    \n

    bar

    \n
    \n", + "example": 245, + "start_line": 3973, + "end_line": 3981, + "section": "Block quotes" + }, + { + "markdown": "> aaa\n***\n> bbb\n", + "html": "
    \n

    aaa

    \n
    \n
    \n
    \n

    bbb

    \n
    \n", + "example": 246, + "start_line": 3987, + "end_line": 3999, + "section": "Block quotes" + }, + { + "markdown": "> bar\nbaz\n", + "html": "
    \n

    bar\nbaz

    \n
    \n", + "example": 247, + "start_line": 4005, + "end_line": 4013, + "section": "Block quotes" + }, + { + "markdown": "> bar\n\nbaz\n", + "html": "
    \n

    bar

    \n
    \n

    baz

    \n", + "example": 248, + "start_line": 4016, + "end_line": 4025, + "section": "Block quotes" + }, + { + "markdown": "> bar\n>\nbaz\n", + "html": "
    \n

    bar

    \n
    \n

    baz

    \n", + "example": 249, + "start_line": 4028, + "end_line": 4037, + "section": "Block quotes" + }, + { + "markdown": "> > > foo\nbar\n", + "html": "
    \n
    \n
    \n

    foo\nbar

    \n
    \n
    \n
    \n", + "example": 250, + "start_line": 4044, + "end_line": 4056, + "section": "Block quotes" + }, + { + "markdown": ">>> foo\n> bar\n>>baz\n", + "html": "
    \n
    \n
    \n

    foo\nbar\nbaz

    \n
    \n
    \n
    \n", + "example": 251, + "start_line": 4059, + "end_line": 4073, + "section": "Block quotes" + }, + { + "markdown": "> code\n\n> not code\n", + "html": "
    \n
    code\n
    \n
    \n
    \n

    not code

    \n
    \n", + "example": 252, + "start_line": 4081, + "end_line": 4093, + "section": "Block quotes" + }, + { + "markdown": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n", + "html": "

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n", + "example": 253, + "start_line": 4135, + "end_line": 4150, + "section": "List items" + }, + { + "markdown": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 254, + "start_line": 4157, + "end_line": 4176, + "section": "List items" + }, + { + "markdown": "- one\n\n two\n", + "html": "
      \n
    • one
    • \n
    \n

    two

    \n", + "example": 255, + "start_line": 4190, + "end_line": 4199, + "section": "List items" + }, + { + "markdown": "- one\n\n two\n", + "html": "
      \n
    • \n

      one

      \n

      two

      \n
    • \n
    \n", + "example": 256, + "start_line": 4202, + "end_line": 4213, + "section": "List items" + }, + { + "markdown": " - one\n\n two\n", + "html": "
      \n
    • one
    • \n
    \n
     two\n
    \n", + "example": 257, + "start_line": 4216, + "end_line": 4226, + "section": "List items" + }, + { + "markdown": " - one\n\n two\n", + "html": "
      \n
    • \n

      one

      \n

      two

      \n
    • \n
    \n", + "example": 258, + "start_line": 4229, + "end_line": 4240, + "section": "List items" + }, + { + "markdown": " > > 1. one\n>>\n>> two\n", + "html": "
    \n
    \n
      \n
    1. \n

      one

      \n

      two

      \n
    2. \n
    \n
    \n
    \n", + "example": 259, + "start_line": 4251, + "end_line": 4266, + "section": "List items" + }, + { + "markdown": ">>- one\n>>\n > > two\n", + "html": "
    \n
    \n
      \n
    • one
    • \n
    \n

    two

    \n
    \n
    \n", + "example": 260, + "start_line": 4278, + "end_line": 4291, + "section": "List items" + }, + { + "markdown": "-one\n\n2.two\n", + "html": "

    -one

    \n

    2.two

    \n", + "example": 261, + "start_line": 4297, + "end_line": 4304, + "section": "List items" + }, + { + "markdown": "- foo\n\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 262, + "start_line": 4310, + "end_line": 4322, + "section": "List items" + }, + { + "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n", + "html": "
      \n
    1. \n

      foo

      \n
      bar\n
      \n

      baz

      \n
      \n

      bam

      \n
      \n
    2. \n
    \n", + "example": 263, + "start_line": 4327, + "end_line": 4349, + "section": "List items" + }, + { + "markdown": "- Foo\n\n bar\n\n\n baz\n", + "html": "
      \n
    • \n

      Foo

      \n
      bar\n\n\nbaz\n
      \n
    • \n
    \n", + "example": 264, + "start_line": 4355, + "end_line": 4373, + "section": "List items" + }, + { + "markdown": "123456789. ok\n", + "html": "
      \n
    1. ok
    2. \n
    \n", + "example": 265, + "start_line": 4377, + "end_line": 4383, + "section": "List items" + }, + { + "markdown": "1234567890. not ok\n", + "html": "

    1234567890. not ok

    \n", + "example": 266, + "start_line": 4386, + "end_line": 4390, + "section": "List items" + }, + { + "markdown": "0. ok\n", + "html": "
      \n
    1. ok
    2. \n
    \n", + "example": 267, + "start_line": 4395, + "end_line": 4401, + "section": "List items" + }, + { + "markdown": "003. ok\n", + "html": "
      \n
    1. ok
    2. \n
    \n", + "example": 268, + "start_line": 4404, + "end_line": 4410, + "section": "List items" + }, + { + "markdown": "-1. not ok\n", + "html": "

    -1. not ok

    \n", + "example": 269, + "start_line": 4415, + "end_line": 4419, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n
      bar\n
      \n
    • \n
    \n", + "example": 270, + "start_line": 4438, + "end_line": 4450, + "section": "List items" + }, + { + "markdown": " 10. foo\n\n bar\n", + "html": "
      \n
    1. \n

      foo

      \n
      bar\n
      \n
    2. \n
    \n", + "example": 271, + "start_line": 4455, + "end_line": 4467, + "section": "List items" + }, + { + "markdown": " indented code\n\nparagraph\n\n more code\n", + "html": "
    indented code\n
    \n

    paragraph

    \n
    more code\n
    \n", + "example": 272, + "start_line": 4474, + "end_line": 4486, + "section": "List items" + }, + { + "markdown": "1. indented code\n\n paragraph\n\n more code\n", + "html": "
      \n
    1. \n
      indented code\n
      \n

      paragraph

      \n
      more code\n
      \n
    2. \n
    \n", + "example": 273, + "start_line": 4489, + "end_line": 4505, + "section": "List items" + }, + { + "markdown": "1. indented code\n\n paragraph\n\n more code\n", + "html": "
      \n
    1. \n
       indented code\n
      \n

      paragraph

      \n
      more code\n
      \n
    2. \n
    \n", + "example": 274, + "start_line": 4511, + "end_line": 4527, + "section": "List items" + }, + { + "markdown": " foo\n\nbar\n", + "html": "

    foo

    \n

    bar

    \n", + "example": 275, + "start_line": 4538, + "end_line": 4545, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
      \n
    • foo
    • \n
    \n

    bar

    \n", + "example": 276, + "start_line": 4548, + "end_line": 4557, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 277, + "start_line": 4565, + "end_line": 4576, + "section": "List items" + }, + { + "markdown": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n", + "html": "
      \n
    • foo
    • \n
    • \n
      bar\n
      \n
    • \n
    • \n
      baz\n
      \n
    • \n
    \n", + "example": 278, + "start_line": 4592, + "end_line": 4613, + "section": "List items" + }, + { + "markdown": "- \n foo\n", + "html": "
      \n
    • foo
    • \n
    \n", + "example": 279, + "start_line": 4618, + "end_line": 4625, + "section": "List items" + }, + { + "markdown": "-\n\n foo\n", + "html": "
      \n
    • \n
    \n

    foo

    \n", + "example": 280, + "start_line": 4632, + "end_line": 4641, + "section": "List items" + }, + { + "markdown": "- foo\n-\n- bar\n", + "html": "
      \n
    • foo
    • \n
    • \n
    • bar
    • \n
    \n", + "example": 281, + "start_line": 4646, + "end_line": 4656, + "section": "List items" + }, + { + "markdown": "- foo\n- \n- bar\n", + "html": "
      \n
    • foo
    • \n
    • \n
    • bar
    • \n
    \n", + "example": 282, + "start_line": 4661, + "end_line": 4671, + "section": "List items" + }, + { + "markdown": "1. foo\n2.\n3. bar\n", + "html": "
      \n
    1. foo
    2. \n
    3. \n
    4. bar
    5. \n
    \n", + "example": 283, + "start_line": 4676, + "end_line": 4686, + "section": "List items" + }, + { + "markdown": "*\n", + "html": "
      \n
    • \n
    \n", + "example": 284, + "start_line": 4691, + "end_line": 4697, + "section": "List items" + }, + { + "markdown": "foo\n*\n\nfoo\n1.\n", + "html": "

    foo\n*

    \n

    foo\n1.

    \n", + "example": 285, + "start_line": 4701, + "end_line": 4712, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 286, + "start_line": 4723, + "end_line": 4742, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 287, + "start_line": 4747, + "end_line": 4766, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 288, + "start_line": 4771, + "end_line": 4790, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
    1.  A paragraph\n    with two lines.\n\n        indented code\n\n    > A block quote.\n
    \n", + "example": 289, + "start_line": 4795, + "end_line": 4810, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 290, + "start_line": 4825, + "end_line": 4844, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n", + "html": "
      \n
    1. A paragraph\nwith two lines.
    2. \n
    \n", + "example": 291, + "start_line": 4849, + "end_line": 4857, + "section": "List items" + }, + { + "markdown": "> 1. > Blockquote\ncontinued here.\n", + "html": "
    \n
      \n
    1. \n
      \n

      Blockquote\ncontinued here.

      \n
      \n
    2. \n
    \n
    \n", + "example": 292, + "start_line": 4862, + "end_line": 4876, + "section": "List items" + }, + { + "markdown": "> 1. > Blockquote\n> continued here.\n", + "html": "
    \n
      \n
    1. \n
      \n

      Blockquote\ncontinued here.

      \n
      \n
    2. \n
    \n
    \n", + "example": 293, + "start_line": 4879, + "end_line": 4893, + "section": "List items" + }, + { + "markdown": "- foo\n - bar\n - baz\n - boo\n", + "html": "
      \n
    • foo\n
        \n
      • bar\n
          \n
        • baz\n
            \n
          • boo
          • \n
          \n
        • \n
        \n
      • \n
      \n
    • \n
    \n", + "example": 294, + "start_line": 4907, + "end_line": 4928, + "section": "List items" + }, + { + "markdown": "- foo\n - bar\n - baz\n - boo\n", + "html": "
      \n
    • foo
    • \n
    • bar
    • \n
    • baz
    • \n
    • boo
    • \n
    \n", + "example": 295, + "start_line": 4933, + "end_line": 4945, + "section": "List items" + }, + { + "markdown": "10) foo\n - bar\n", + "html": "
      \n
    1. foo\n
        \n
      • bar
      • \n
      \n
    2. \n
    \n", + "example": 296, + "start_line": 4950, + "end_line": 4961, + "section": "List items" + }, + { + "markdown": "10) foo\n - bar\n", + "html": "
      \n
    1. foo
    2. \n
    \n
      \n
    • bar
    • \n
    \n", + "example": 297, + "start_line": 4966, + "end_line": 4976, + "section": "List items" + }, + { + "markdown": "- - foo\n", + "html": "
      \n
    • \n
        \n
      • foo
      • \n
      \n
    • \n
    \n", + "example": 298, + "start_line": 4981, + "end_line": 4991, + "section": "List items" + }, + { + "markdown": "1. - 2. foo\n", + "html": "
      \n
    1. \n
        \n
      • \n
          \n
        1. foo
        2. \n
        \n
      • \n
      \n
    2. \n
    \n", + "example": 299, + "start_line": 4994, + "end_line": 5008, + "section": "List items" + }, + { + "markdown": "- # Foo\n- Bar\n ---\n baz\n", + "html": "
      \n
    • \n

      Foo

      \n
    • \n
    • \n

      Bar

      \nbaz
    • \n
    \n", + "example": 300, + "start_line": 5013, + "end_line": 5027, + "section": "List items" + }, + { + "markdown": "- foo\n- bar\n+ baz\n", + "html": "
      \n
    • foo
    • \n
    • bar
    • \n
    \n
      \n
    • baz
    • \n
    \n", + "example": 301, + "start_line": 5249, + "end_line": 5261, + "section": "Lists" + }, + { + "markdown": "1. foo\n2. bar\n3) baz\n", + "html": "
      \n
    1. foo
    2. \n
    3. bar
    4. \n
    \n
      \n
    1. baz
    2. \n
    \n", + "example": 302, + "start_line": 5264, + "end_line": 5276, + "section": "Lists" + }, + { + "markdown": "Foo\n- bar\n- baz\n", + "html": "

    Foo

    \n
      \n
    • bar
    • \n
    • baz
    • \n
    \n", + "example": 303, + "start_line": 5283, + "end_line": 5293, + "section": "Lists" + }, + { + "markdown": "The number of windows in my house is\n14. The number of doors is 6.\n", + "html": "

    The number of windows in my house is\n14. The number of doors is 6.

    \n", + "example": 304, + "start_line": 5360, + "end_line": 5366, + "section": "Lists" + }, + { + "markdown": "The number of windows in my house is\n1. The number of doors is 6.\n", + "html": "

    The number of windows in my house is

    \n
      \n
    1. The number of doors is 6.
    2. \n
    \n", + "example": 305, + "start_line": 5370, + "end_line": 5378, + "section": "Lists" + }, + { + "markdown": "- foo\n\n- bar\n\n\n- baz\n", + "html": "
      \n
    • \n

      foo

      \n
    • \n
    • \n

      bar

      \n
    • \n
    • \n

      baz

      \n
    • \n
    \n", + "example": 306, + "start_line": 5384, + "end_line": 5403, + "section": "Lists" + }, + { + "markdown": "- foo\n - bar\n - baz\n\n\n bim\n", + "html": "
      \n
    • foo\n
        \n
      • bar\n
          \n
        • \n

          baz

          \n

          bim

          \n
        • \n
        \n
      • \n
      \n
    • \n
    \n", + "example": 307, + "start_line": 5405, + "end_line": 5427, + "section": "Lists" + }, + { + "markdown": "- foo\n- bar\n\n\n\n- baz\n- bim\n", + "html": "
      \n
    • foo
    • \n
    • bar
    • \n
    \n\n
      \n
    • baz
    • \n
    • bim
    • \n
    \n", + "example": 308, + "start_line": 5435, + "end_line": 5453, + "section": "Lists" + }, + { + "markdown": "- foo\n\n notcode\n\n- foo\n\n\n\n code\n", + "html": "
      \n
    • \n

      foo

      \n

      notcode

      \n
    • \n
    • \n

      foo

      \n
    • \n
    \n\n
    code\n
    \n", + "example": 309, + "start_line": 5456, + "end_line": 5479, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n", + "html": "
      \n
    • a
    • \n
    • b
    • \n
    • c
    • \n
    • d
    • \n
    • e
    • \n
    • f
    • \n
    • g
    • \n
    \n", + "example": 310, + "start_line": 5487, + "end_line": 5505, + "section": "Lists" + }, + { + "markdown": "1. a\n\n 2. b\n\n 3. c\n", + "html": "
      \n
    1. \n

      a

      \n
    2. \n
    3. \n

      b

      \n
    4. \n
    5. \n

      c

      \n
    6. \n
    \n", + "example": 311, + "start_line": 5508, + "end_line": 5526, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n - d\n - e\n", + "html": "
      \n
    • a
    • \n
    • b
    • \n
    • c
    • \n
    • d\n- e
    • \n
    \n", + "example": 312, + "start_line": 5532, + "end_line": 5546, + "section": "Lists" + }, + { + "markdown": "1. a\n\n 2. b\n\n 3. c\n", + "html": "
      \n
    1. \n

      a

      \n
    2. \n
    3. \n

      b

      \n
    4. \n
    \n
    3. c\n
    \n", + "example": 313, + "start_line": 5552, + "end_line": 5569, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n- c\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n

      b

      \n
    • \n
    • \n

      c

      \n
    • \n
    \n", + "example": 314, + "start_line": 5575, + "end_line": 5592, + "section": "Lists" + }, + { + "markdown": "* a\n*\n\n* c\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n
    • \n

      c

      \n
    • \n
    \n", + "example": 315, + "start_line": 5597, + "end_line": 5612, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n c\n- d\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n

      b

      \n

      c

      \n
    • \n
    • \n

      d

      \n
    • \n
    \n", + "example": 316, + "start_line": 5619, + "end_line": 5638, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n [ref]: /url\n- d\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n

      b

      \n
    • \n
    • \n

      d

      \n
    • \n
    \n", + "example": 317, + "start_line": 5641, + "end_line": 5659, + "section": "Lists" + }, + { + "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n", + "html": "
      \n
    • a
    • \n
    • \n
      b\n\n\n
      \n
    • \n
    • c
    • \n
    \n", + "example": 318, + "start_line": 5664, + "end_line": 5683, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n\n c\n- d\n", + "html": "
      \n
    • a\n
        \n
      • \n

        b

        \n

        c

        \n
      • \n
      \n
    • \n
    • d
    • \n
    \n", + "example": 319, + "start_line": 5690, + "end_line": 5708, + "section": "Lists" + }, + { + "markdown": "* a\n > b\n >\n* c\n", + "html": "
      \n
    • a\n
      \n

      b

      \n
      \n
    • \n
    • c
    • \n
    \n", + "example": 320, + "start_line": 5714, + "end_line": 5728, + "section": "Lists" + }, + { + "markdown": "- a\n > b\n ```\n c\n ```\n- d\n", + "html": "
      \n
    • a\n
      \n

      b

      \n
      \n
      c\n
      \n
    • \n
    • d
    • \n
    \n", + "example": 321, + "start_line": 5734, + "end_line": 5752, + "section": "Lists" + }, + { + "markdown": "- a\n", + "html": "
      \n
    • a
    • \n
    \n", + "example": 322, + "start_line": 5757, + "end_line": 5763, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n", + "html": "
      \n
    • a\n
        \n
      • b
      • \n
      \n
    • \n
    \n", + "example": 323, + "start_line": 5766, + "end_line": 5777, + "section": "Lists" + }, + { + "markdown": "1. ```\n foo\n ```\n\n bar\n", + "html": "
      \n
    1. \n
      foo\n
      \n

      bar

      \n
    2. \n
    \n", + "example": 324, + "start_line": 5783, + "end_line": 5797, + "section": "Lists" + }, + { + "markdown": "* foo\n * bar\n\n baz\n", + "html": "
      \n
    • \n

      foo

      \n
        \n
      • bar
      • \n
      \n

      baz

      \n
    • \n
    \n", + "example": 325, + "start_line": 5802, + "end_line": 5817, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n\n- d\n - e\n - f\n", + "html": "
      \n
    • \n

      a

      \n
        \n
      • b
      • \n
      • c
      • \n
      \n
    • \n
    • \n

      d

      \n
        \n
      • e
      • \n
      • f
      • \n
      \n
    • \n
    \n", + "example": 326, + "start_line": 5820, + "end_line": 5845, + "section": "Lists" + }, + { + "markdown": "`hi`lo`\n", + "html": "

    hilo`

    \n", + "example": 327, + "start_line": 5854, + "end_line": 5858, + "section": "Inlines" + }, + { + "markdown": "`foo`\n", + "html": "

    foo

    \n", + "example": 328, + "start_line": 5886, + "end_line": 5890, + "section": "Code spans" + }, + { + "markdown": "`` foo ` bar ``\n", + "html": "

    foo ` bar

    \n", + "example": 329, + "start_line": 5897, + "end_line": 5901, + "section": "Code spans" + }, + { + "markdown": "` `` `\n", + "html": "

    ``

    \n", + "example": 330, + "start_line": 5907, + "end_line": 5911, + "section": "Code spans" + }, + { + "markdown": "` `` `\n", + "html": "

    ``

    \n", + "example": 331, + "start_line": 5915, + "end_line": 5919, + "section": "Code spans" + }, + { + "markdown": "` a`\n", + "html": "

    a

    \n", + "example": 332, + "start_line": 5924, + "end_line": 5928, + "section": "Code spans" + }, + { + "markdown": "` b `\n", + "html": "

     b 

    \n", + "example": 333, + "start_line": 5933, + "end_line": 5937, + "section": "Code spans" + }, + { + "markdown": "` `\n` `\n", + "html": "

     \n

    \n", + "example": 334, + "start_line": 5941, + "end_line": 5947, + "section": "Code spans" + }, + { + "markdown": "``\nfoo\nbar \nbaz\n``\n", + "html": "

    foo bar baz

    \n", + "example": 335, + "start_line": 5952, + "end_line": 5960, + "section": "Code spans" + }, + { + "markdown": "``\nfoo \n``\n", + "html": "

    foo

    \n", + "example": 336, + "start_line": 5962, + "end_line": 5968, + "section": "Code spans" + }, + { + "markdown": "`foo bar \nbaz`\n", + "html": "

    foo bar baz

    \n", + "example": 337, + "start_line": 5973, + "end_line": 5978, + "section": "Code spans" + }, + { + "markdown": "`foo\\`bar`\n", + "html": "

    foo\\bar`

    \n", + "example": 338, + "start_line": 5990, + "end_line": 5994, + "section": "Code spans" + }, + { + "markdown": "``foo`bar``\n", + "html": "

    foo`bar

    \n", + "example": 339, + "start_line": 6001, + "end_line": 6005, + "section": "Code spans" + }, + { + "markdown": "` foo `` bar `\n", + "html": "

    foo `` bar

    \n", + "example": 340, + "start_line": 6007, + "end_line": 6011, + "section": "Code spans" + }, + { + "markdown": "*foo`*`\n", + "html": "

    *foo*

    \n", + "example": 341, + "start_line": 6019, + "end_line": 6023, + "section": "Code spans" + }, + { + "markdown": "[not a `link](/foo`)\n", + "html": "

    [not a link](/foo)

    \n", + "example": 342, + "start_line": 6028, + "end_line": 6032, + "section": "Code spans" + }, + { + "markdown": "``\n", + "html": "

    <a href="">`

    \n", + "example": 343, + "start_line": 6038, + "end_line": 6042, + "section": "Code spans" + }, + { + "markdown": "
    `\n", + "html": "

    `

    \n", + "example": 344, + "start_line": 6047, + "end_line": 6051, + "section": "Code spans" + }, + { + "markdown": "``\n", + "html": "

    <https://foo.bar.baz>`

    \n", + "example": 345, + "start_line": 6056, + "end_line": 6060, + "section": "Code spans" + }, + { + "markdown": "`\n", + "html": "

    https://foo.bar.`baz`

    \n", + "example": 346, + "start_line": 6065, + "end_line": 6069, + "section": "Code spans" + }, + { + "markdown": "```foo``\n", + "html": "

    ```foo``

    \n", + "example": 347, + "start_line": 6075, + "end_line": 6079, + "section": "Code spans" + }, + { + "markdown": "`foo\n", + "html": "

    `foo

    \n", + "example": 348, + "start_line": 6082, + "end_line": 6086, + "section": "Code spans" + }, + { + "markdown": "`foo``bar``\n", + "html": "

    `foobar

    \n", + "example": 349, + "start_line": 6091, + "end_line": 6095, + "section": "Code spans" + }, + { + "markdown": "*foo bar*\n", + "html": "

    foo bar

    \n", + "example": 350, + "start_line": 6308, + "end_line": 6312, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a * foo bar*\n", + "html": "

    a * foo bar*

    \n", + "example": 351, + "start_line": 6318, + "end_line": 6322, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a*\"foo\"*\n", + "html": "

    a*"foo"*

    \n", + "example": 352, + "start_line": 6329, + "end_line": 6333, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "* a *\n", + "html": "

    * a *

    \n", + "example": 353, + "start_line": 6338, + "end_line": 6342, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*$*alpha.\n\n*£*bravo.\n\n*€*charlie.\n", + "html": "

    *$*alpha.

    \n

    *£*bravo.

    \n

    *€*charlie.

    \n", + "example": 354, + "start_line": 6347, + "end_line": 6357, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo*bar*\n", + "html": "

    foobar

    \n", + "example": 355, + "start_line": 6362, + "end_line": 6366, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5*6*78\n", + "html": "

    5678

    \n", + "example": 356, + "start_line": 6369, + "end_line": 6373, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo bar_\n", + "html": "

    foo bar

    \n", + "example": 357, + "start_line": 6378, + "end_line": 6382, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_ foo bar_\n", + "html": "

    _ foo bar_

    \n", + "example": 358, + "start_line": 6388, + "end_line": 6392, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a_\"foo\"_\n", + "html": "

    a_"foo"_

    \n", + "example": 359, + "start_line": 6398, + "end_line": 6402, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo_bar_\n", + "html": "

    foo_bar_

    \n", + "example": 360, + "start_line": 6407, + "end_line": 6411, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5_6_78\n", + "html": "

    5_6_78

    \n", + "example": 361, + "start_line": 6414, + "end_line": 6418, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "пристаням_стремятся_\n", + "html": "

    пристаням_стремятся_

    \n", + "example": 362, + "start_line": 6421, + "end_line": 6425, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "aa_\"bb\"_cc\n", + "html": "

    aa_"bb"_cc

    \n", + "example": 363, + "start_line": 6431, + "end_line": 6435, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo-_(bar)_\n", + "html": "

    foo-(bar)

    \n", + "example": 364, + "start_line": 6442, + "end_line": 6446, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo*\n", + "html": "

    _foo*

    \n", + "example": 365, + "start_line": 6454, + "end_line": 6458, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo bar *\n", + "html": "

    *foo bar *

    \n", + "example": 366, + "start_line": 6464, + "end_line": 6468, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo bar\n*\n", + "html": "

    *foo bar\n*

    \n", + "example": 367, + "start_line": 6473, + "end_line": 6479, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(*foo)\n", + "html": "

    *(*foo)

    \n", + "example": 368, + "start_line": 6486, + "end_line": 6490, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(*foo*)*\n", + "html": "

    (foo)

    \n", + "example": 369, + "start_line": 6496, + "end_line": 6500, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo*bar\n", + "html": "

    foobar

    \n", + "example": 370, + "start_line": 6505, + "end_line": 6509, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo bar _\n", + "html": "

    _foo bar _

    \n", + "example": 371, + "start_line": 6518, + "end_line": 6522, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(_foo)\n", + "html": "

    _(_foo)

    \n", + "example": 372, + "start_line": 6528, + "end_line": 6532, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(_foo_)_\n", + "html": "

    (foo)

    \n", + "example": 373, + "start_line": 6537, + "end_line": 6541, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo_bar\n", + "html": "

    _foo_bar

    \n", + "example": 374, + "start_line": 6546, + "end_line": 6550, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_пристаням_стремятся\n", + "html": "

    _пристаням_стремятся

    \n", + "example": 375, + "start_line": 6553, + "end_line": 6557, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo_bar_baz_\n", + "html": "

    foo_bar_baz

    \n", + "example": 376, + "start_line": 6560, + "end_line": 6564, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(bar)_.\n", + "html": "

    (bar).

    \n", + "example": 377, + "start_line": 6571, + "end_line": 6575, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo bar**\n", + "html": "

    foo bar

    \n", + "example": 378, + "start_line": 6580, + "end_line": 6584, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "** foo bar**\n", + "html": "

    ** foo bar**

    \n", + "example": 379, + "start_line": 6590, + "end_line": 6594, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a**\"foo\"**\n", + "html": "

    a**"foo"**

    \n", + "example": 380, + "start_line": 6601, + "end_line": 6605, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo**bar**\n", + "html": "

    foobar

    \n", + "example": 381, + "start_line": 6610, + "end_line": 6614, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo bar__\n", + "html": "

    foo bar

    \n", + "example": 382, + "start_line": 6619, + "end_line": 6623, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__ foo bar__\n", + "html": "

    __ foo bar__

    \n", + "example": 383, + "start_line": 6629, + "end_line": 6633, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__\nfoo bar__\n", + "html": "

    __\nfoo bar__

    \n", + "example": 384, + "start_line": 6637, + "end_line": 6643, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a__\"foo\"__\n", + "html": "

    a__"foo"__

    \n", + "example": 385, + "start_line": 6649, + "end_line": 6653, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo__bar__\n", + "html": "

    foo__bar__

    \n", + "example": 386, + "start_line": 6658, + "end_line": 6662, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5__6__78\n", + "html": "

    5__6__78

    \n", + "example": 387, + "start_line": 6665, + "end_line": 6669, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "пристаням__стремятся__\n", + "html": "

    пристаням__стремятся__

    \n", + "example": 388, + "start_line": 6672, + "end_line": 6676, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo, __bar__, baz__\n", + "html": "

    foo, bar, baz

    \n", + "example": 389, + "start_line": 6679, + "end_line": 6683, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo-__(bar)__\n", + "html": "

    foo-(bar)

    \n", + "example": 390, + "start_line": 6690, + "end_line": 6694, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo bar **\n", + "html": "

    **foo bar **

    \n", + "example": 391, + "start_line": 6703, + "end_line": 6707, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**(**foo)\n", + "html": "

    **(**foo)

    \n", + "example": 392, + "start_line": 6716, + "end_line": 6720, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(**foo**)*\n", + "html": "

    (foo)

    \n", + "example": 393, + "start_line": 6726, + "end_line": 6730, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n", + "html": "

    Gomphocarpus (Gomphocarpus physocarpus, syn.\nAsclepias physocarpa)

    \n", + "example": 394, + "start_line": 6733, + "end_line": 6739, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo \"*bar*\" foo**\n", + "html": "

    foo "bar" foo

    \n", + "example": 395, + "start_line": 6742, + "end_line": 6746, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo**bar\n", + "html": "

    foobar

    \n", + "example": 396, + "start_line": 6751, + "end_line": 6755, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo bar __\n", + "html": "

    __foo bar __

    \n", + "example": 397, + "start_line": 6763, + "end_line": 6767, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__(__foo)\n", + "html": "

    __(__foo)

    \n", + "example": 398, + "start_line": 6773, + "end_line": 6777, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(__foo__)_\n", + "html": "

    (foo)

    \n", + "example": 399, + "start_line": 6783, + "end_line": 6787, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__bar\n", + "html": "

    __foo__bar

    \n", + "example": 400, + "start_line": 6792, + "end_line": 6796, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__пристаням__стремятся\n", + "html": "

    __пристаням__стремятся

    \n", + "example": 401, + "start_line": 6799, + "end_line": 6803, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__bar__baz__\n", + "html": "

    foo__bar__baz

    \n", + "example": 402, + "start_line": 6806, + "end_line": 6810, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__(bar)__.\n", + "html": "

    (bar).

    \n", + "example": 403, + "start_line": 6817, + "end_line": 6821, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo [bar](/url)*\n", + "html": "

    foo bar

    \n", + "example": 404, + "start_line": 6829, + "end_line": 6833, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo\nbar*\n", + "html": "

    foo\nbar

    \n", + "example": 405, + "start_line": 6836, + "end_line": 6842, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo __bar__ baz_\n", + "html": "

    foo bar baz

    \n", + "example": 406, + "start_line": 6848, + "end_line": 6852, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo _bar_ baz_\n", + "html": "

    foo bar baz

    \n", + "example": 407, + "start_line": 6855, + "end_line": 6859, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo_ bar_\n", + "html": "

    foo bar

    \n", + "example": 408, + "start_line": 6862, + "end_line": 6866, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo *bar**\n", + "html": "

    foo bar

    \n", + "example": 409, + "start_line": 6869, + "end_line": 6873, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar** baz*\n", + "html": "

    foo bar baz

    \n", + "example": 410, + "start_line": 6876, + "end_line": 6880, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar**baz*\n", + "html": "

    foobarbaz

    \n", + "example": 411, + "start_line": 6882, + "end_line": 6886, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar*\n", + "html": "

    foo**bar

    \n", + "example": 412, + "start_line": 6906, + "end_line": 6910, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo** bar*\n", + "html": "

    foo bar

    \n", + "example": 413, + "start_line": 6919, + "end_line": 6923, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar***\n", + "html": "

    foo bar

    \n", + "example": 414, + "start_line": 6926, + "end_line": 6930, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar***\n", + "html": "

    foobar

    \n", + "example": 415, + "start_line": 6933, + "end_line": 6937, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo***bar***baz\n", + "html": "

    foobarbaz

    \n", + "example": 416, + "start_line": 6944, + "end_line": 6948, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo******bar*********baz\n", + "html": "

    foobar***baz

    \n", + "example": 417, + "start_line": 6950, + "end_line": 6954, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar *baz* bim** bop*\n", + "html": "

    foo bar baz bim bop

    \n", + "example": 418, + "start_line": 6959, + "end_line": 6963, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo [*bar*](/url)*\n", + "html": "

    foo bar

    \n", + "example": 419, + "start_line": 6966, + "end_line": 6970, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "** is not an empty emphasis\n", + "html": "

    ** is not an empty emphasis

    \n", + "example": 420, + "start_line": 6975, + "end_line": 6979, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**** is not an empty strong emphasis\n", + "html": "

    **** is not an empty strong emphasis

    \n", + "example": 421, + "start_line": 6982, + "end_line": 6986, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo [bar](/url)**\n", + "html": "

    foo bar

    \n", + "example": 422, + "start_line": 6995, + "end_line": 6999, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo\nbar**\n", + "html": "

    foo\nbar

    \n", + "example": 423, + "start_line": 7002, + "end_line": 7008, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo _bar_ baz__\n", + "html": "

    foo bar baz

    \n", + "example": 424, + "start_line": 7014, + "end_line": 7018, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo __bar__ baz__\n", + "html": "

    foo bar baz

    \n", + "example": 425, + "start_line": 7021, + "end_line": 7025, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo__ bar__\n", + "html": "

    foo bar

    \n", + "example": 426, + "start_line": 7028, + "end_line": 7032, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo **bar****\n", + "html": "

    foo bar

    \n", + "example": 427, + "start_line": 7035, + "end_line": 7039, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar* baz**\n", + "html": "

    foo bar baz

    \n", + "example": 428, + "start_line": 7042, + "end_line": 7046, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo*bar*baz**\n", + "html": "

    foobarbaz

    \n", + "example": 429, + "start_line": 7049, + "end_line": 7053, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo* bar**\n", + "html": "

    foo bar

    \n", + "example": 430, + "start_line": 7056, + "end_line": 7060, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar***\n", + "html": "

    foo bar

    \n", + "example": 431, + "start_line": 7063, + "end_line": 7067, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar **baz**\nbim* bop**\n", + "html": "

    foo bar baz\nbim bop

    \n", + "example": 432, + "start_line": 7072, + "end_line": 7078, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo [*bar*](/url)**\n", + "html": "

    foo bar

    \n", + "example": 433, + "start_line": 7081, + "end_line": 7085, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__ is not an empty emphasis\n", + "html": "

    __ is not an empty emphasis

    \n", + "example": 434, + "start_line": 7090, + "end_line": 7094, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____ is not an empty strong emphasis\n", + "html": "

    ____ is not an empty strong emphasis

    \n", + "example": 435, + "start_line": 7097, + "end_line": 7101, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo ***\n", + "html": "

    foo ***

    \n", + "example": 436, + "start_line": 7107, + "end_line": 7111, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *\\**\n", + "html": "

    foo *

    \n", + "example": 437, + "start_line": 7114, + "end_line": 7118, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *_*\n", + "html": "

    foo _

    \n", + "example": 438, + "start_line": 7121, + "end_line": 7125, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *****\n", + "html": "

    foo *****

    \n", + "example": 439, + "start_line": 7128, + "end_line": 7132, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo **\\***\n", + "html": "

    foo *

    \n", + "example": 440, + "start_line": 7135, + "end_line": 7139, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo **_**\n", + "html": "

    foo _

    \n", + "example": 441, + "start_line": 7142, + "end_line": 7146, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo*\n", + "html": "

    *foo

    \n", + "example": 442, + "start_line": 7153, + "end_line": 7157, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**\n", + "html": "

    foo*

    \n", + "example": 443, + "start_line": 7160, + "end_line": 7164, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo**\n", + "html": "

    *foo

    \n", + "example": 444, + "start_line": 7167, + "end_line": 7171, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "****foo*\n", + "html": "

    ***foo

    \n", + "example": 445, + "start_line": 7174, + "end_line": 7178, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo***\n", + "html": "

    foo*

    \n", + "example": 446, + "start_line": 7181, + "end_line": 7185, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo****\n", + "html": "

    foo***

    \n", + "example": 447, + "start_line": 7188, + "end_line": 7192, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo ___\n", + "html": "

    foo ___

    \n", + "example": 448, + "start_line": 7198, + "end_line": 7202, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _\\__\n", + "html": "

    foo _

    \n", + "example": 449, + "start_line": 7205, + "end_line": 7209, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _*_\n", + "html": "

    foo *

    \n", + "example": 450, + "start_line": 7212, + "end_line": 7216, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _____\n", + "html": "

    foo _____

    \n", + "example": 451, + "start_line": 7219, + "end_line": 7223, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo __\\___\n", + "html": "

    foo _

    \n", + "example": 452, + "start_line": 7226, + "end_line": 7230, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo __*__\n", + "html": "

    foo *

    \n", + "example": 453, + "start_line": 7233, + "end_line": 7237, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo_\n", + "html": "

    _foo

    \n", + "example": 454, + "start_line": 7240, + "end_line": 7244, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo__\n", + "html": "

    foo_

    \n", + "example": 455, + "start_line": 7251, + "end_line": 7255, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "___foo__\n", + "html": "

    _foo

    \n", + "example": 456, + "start_line": 7258, + "end_line": 7262, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo_\n", + "html": "

    ___foo

    \n", + "example": 457, + "start_line": 7265, + "end_line": 7269, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo___\n", + "html": "

    foo_

    \n", + "example": 458, + "start_line": 7272, + "end_line": 7276, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo____\n", + "html": "

    foo___

    \n", + "example": 459, + "start_line": 7279, + "end_line": 7283, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo**\n", + "html": "

    foo

    \n", + "example": 460, + "start_line": 7289, + "end_line": 7293, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*_foo_*\n", + "html": "

    foo

    \n", + "example": 461, + "start_line": 7296, + "end_line": 7300, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__\n", + "html": "

    foo

    \n", + "example": 462, + "start_line": 7303, + "end_line": 7307, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_*foo*_\n", + "html": "

    foo

    \n", + "example": 463, + "start_line": 7310, + "end_line": 7314, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "****foo****\n", + "html": "

    foo

    \n", + "example": 464, + "start_line": 7320, + "end_line": 7324, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo____\n", + "html": "

    foo

    \n", + "example": 465, + "start_line": 7327, + "end_line": 7331, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "******foo******\n", + "html": "

    foo

    \n", + "example": 466, + "start_line": 7338, + "end_line": 7342, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo***\n", + "html": "

    foo

    \n", + "example": 467, + "start_line": 7347, + "end_line": 7351, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_____foo_____\n", + "html": "

    foo

    \n", + "example": 468, + "start_line": 7354, + "end_line": 7358, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo _bar* baz_\n", + "html": "

    foo _bar baz_

    \n", + "example": 469, + "start_line": 7363, + "end_line": 7367, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo __bar *baz bim__ bam*\n", + "html": "

    foo bar *baz bim bam

    \n", + "example": 470, + "start_line": 7370, + "end_line": 7374, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo **bar baz**\n", + "html": "

    **foo bar baz

    \n", + "example": 471, + "start_line": 7379, + "end_line": 7383, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo *bar baz*\n", + "html": "

    *foo bar baz

    \n", + "example": 472, + "start_line": 7386, + "end_line": 7390, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*[bar*](/url)\n", + "html": "

    *bar*

    \n", + "example": 473, + "start_line": 7395, + "end_line": 7399, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo [bar_](/url)\n", + "html": "

    _foo bar_

    \n", + "example": 474, + "start_line": 7402, + "end_line": 7406, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*\n", + "html": "

    *

    \n", + "example": 475, + "start_line": 7409, + "end_line": 7413, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**\n", + "html": "

    **

    \n", + "example": 476, + "start_line": 7416, + "end_line": 7420, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__\n", + "html": "

    __

    \n", + "example": 477, + "start_line": 7423, + "end_line": 7427, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*a `*`*\n", + "html": "

    a *

    \n", + "example": 478, + "start_line": 7430, + "end_line": 7434, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_a `_`_\n", + "html": "

    a _

    \n", + "example": 479, + "start_line": 7437, + "end_line": 7441, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**a\n", + "html": "

    **ahttps://foo.bar/?q=**

    \n", + "example": 480, + "start_line": 7444, + "end_line": 7448, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__a\n", + "html": "

    __ahttps://foo.bar/?q=__

    \n", + "example": 481, + "start_line": 7451, + "end_line": 7455, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "[link](/uri \"title\")\n", + "html": "

    link

    \n", + "example": 482, + "start_line": 7539, + "end_line": 7543, + "section": "Links" + }, + { + "markdown": "[link](/uri)\n", + "html": "

    link

    \n", + "example": 483, + "start_line": 7549, + "end_line": 7553, + "section": "Links" + }, + { + "markdown": "[](./target.md)\n", + "html": "

    \n", + "example": 484, + "start_line": 7555, + "end_line": 7559, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    link

    \n", + "example": 485, + "start_line": 7562, + "end_line": 7566, + "section": "Links" + }, + { + "markdown": "[link](<>)\n", + "html": "

    link

    \n", + "example": 486, + "start_line": 7569, + "end_line": 7573, + "section": "Links" + }, + { + "markdown": "[]()\n", + "html": "

    \n", + "example": 487, + "start_line": 7576, + "end_line": 7580, + "section": "Links" + }, + { + "markdown": "[link](/my uri)\n", + "html": "

    [link](/my uri)

    \n", + "example": 488, + "start_line": 7585, + "end_line": 7589, + "section": "Links" + }, + { + "markdown": "[link](
    )\n", + "html": "

    link

    \n", + "example": 489, + "start_line": 7591, + "end_line": 7595, + "section": "Links" + }, + { + "markdown": "[link](foo\nbar)\n", + "html": "

    [link](foo\nbar)

    \n", + "example": 490, + "start_line": 7600, + "end_line": 7606, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    [link]()

    \n", + "example": 491, + "start_line": 7608, + "end_line": 7614, + "section": "Links" + }, + { + "markdown": "[a]()\n", + "html": "

    a

    \n", + "example": 492, + "start_line": 7619, + "end_line": 7623, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    [link](<foo>)

    \n", + "example": 493, + "start_line": 7627, + "end_line": 7631, + "section": "Links" + }, + { + "markdown": "[a](\n[a](c)\n", + "html": "

    [a](<b)c\n[a](<b)c>\n[a](c)

    \n", + "example": 494, + "start_line": 7636, + "end_line": 7644, + "section": "Links" + }, + { + "markdown": "[link](\\(foo\\))\n", + "html": "

    link

    \n", + "example": 495, + "start_line": 7648, + "end_line": 7652, + "section": "Links" + }, + { + "markdown": "[link](foo(and(bar)))\n", + "html": "

    link

    \n", + "example": 496, + "start_line": 7657, + "end_line": 7661, + "section": "Links" + }, + { + "markdown": "[link](foo(and(bar))\n", + "html": "

    [link](foo(and(bar))

    \n", + "example": 497, + "start_line": 7666, + "end_line": 7670, + "section": "Links" + }, + { + "markdown": "[link](foo\\(and\\(bar\\))\n", + "html": "

    link

    \n", + "example": 498, + "start_line": 7673, + "end_line": 7677, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    link

    \n", + "example": 499, + "start_line": 7680, + "end_line": 7684, + "section": "Links" + }, + { + "markdown": "[link](foo\\)\\:)\n", + "html": "

    link

    \n", + "example": 500, + "start_line": 7690, + "end_line": 7694, + "section": "Links" + }, + { + "markdown": "[link](#fragment)\n\n[link](https://example.com#fragment)\n\n[link](https://example.com?foo=3#frag)\n", + "html": "

    link

    \n

    link

    \n

    link

    \n", + "example": 501, + "start_line": 7699, + "end_line": 7709, + "section": "Links" + }, + { + "markdown": "[link](foo\\bar)\n", + "html": "

    link

    \n", + "example": 502, + "start_line": 7715, + "end_line": 7719, + "section": "Links" + }, + { + "markdown": "[link](foo%20bä)\n", + "html": "

    link

    \n", + "example": 503, + "start_line": 7731, + "end_line": 7735, + "section": "Links" + }, + { + "markdown": "[link](\"title\")\n", + "html": "

    link

    \n", + "example": 504, + "start_line": 7742, + "end_line": 7746, + "section": "Links" + }, + { + "markdown": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n", + "html": "

    link\nlink\nlink

    \n", + "example": 505, + "start_line": 7751, + "end_line": 7759, + "section": "Links" + }, + { + "markdown": "[link](/url \"title \\\""\")\n", + "html": "

    link

    \n", + "example": 506, + "start_line": 7765, + "end_line": 7769, + "section": "Links" + }, + { + "markdown": "[link](/url \"title\")\n", + "html": "

    link

    \n", + "example": 507, + "start_line": 7776, + "end_line": 7780, + "section": "Links" + }, + { + "markdown": "[link](/url \"title \"and\" title\")\n", + "html": "

    [link](/url "title "and" title")

    \n", + "example": 508, + "start_line": 7785, + "end_line": 7789, + "section": "Links" + }, + { + "markdown": "[link](/url 'title \"and\" title')\n", + "html": "

    link

    \n", + "example": 509, + "start_line": 7794, + "end_line": 7798, + "section": "Links" + }, + { + "markdown": "[link]( /uri\n \"title\" )\n", + "html": "

    link

    \n", + "example": 510, + "start_line": 7819, + "end_line": 7824, + "section": "Links" + }, + { + "markdown": "[link] (/uri)\n", + "html": "

    [link] (/uri)

    \n", + "example": 511, + "start_line": 7830, + "end_line": 7834, + "section": "Links" + }, + { + "markdown": "[link [foo [bar]]](/uri)\n", + "html": "

    link [foo [bar]]

    \n", + "example": 512, + "start_line": 7840, + "end_line": 7844, + "section": "Links" + }, + { + "markdown": "[link] bar](/uri)\n", + "html": "

    [link] bar](/uri)

    \n", + "example": 513, + "start_line": 7847, + "end_line": 7851, + "section": "Links" + }, + { + "markdown": "[link [bar](/uri)\n", + "html": "

    [link bar

    \n", + "example": 514, + "start_line": 7854, + "end_line": 7858, + "section": "Links" + }, + { + "markdown": "[link \\[bar](/uri)\n", + "html": "

    link [bar

    \n", + "example": 515, + "start_line": 7861, + "end_line": 7865, + "section": "Links" + }, + { + "markdown": "[link *foo **bar** `#`*](/uri)\n", + "html": "

    link foo bar #

    \n", + "example": 516, + "start_line": 7870, + "end_line": 7874, + "section": "Links" + }, + { + "markdown": "[![moon](moon.jpg)](/uri)\n", + "html": "

    \"moon\"

    \n", + "example": 517, + "start_line": 7877, + "end_line": 7881, + "section": "Links" + }, + { + "markdown": "[foo [bar](/uri)](/uri)\n", + "html": "

    [foo bar](/uri)

    \n", + "example": 518, + "start_line": 7886, + "end_line": 7890, + "section": "Links" + }, + { + "markdown": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n", + "html": "

    [foo [bar baz](/uri)](/uri)

    \n", + "example": 519, + "start_line": 7893, + "end_line": 7897, + "section": "Links" + }, + { + "markdown": "![[[foo](uri1)](uri2)](uri3)\n", + "html": "

    \"[foo](uri2)\"

    \n", + "example": 520, + "start_line": 7900, + "end_line": 7904, + "section": "Links" + }, + { + "markdown": "*[foo*](/uri)\n", + "html": "

    *foo*

    \n", + "example": 521, + "start_line": 7910, + "end_line": 7914, + "section": "Links" + }, + { + "markdown": "[foo *bar](baz*)\n", + "html": "

    foo *bar

    \n", + "example": 522, + "start_line": 7917, + "end_line": 7921, + "section": "Links" + }, + { + "markdown": "*foo [bar* baz]\n", + "html": "

    foo [bar baz]

    \n", + "example": 523, + "start_line": 7927, + "end_line": 7931, + "section": "Links" + }, + { + "markdown": "[foo \n", + "html": "

    [foo

    \n", + "example": 524, + "start_line": 7937, + "end_line": 7941, + "section": "Links" + }, + { + "markdown": "[foo`](/uri)`\n", + "html": "

    [foo](/uri)

    \n", + "example": 525, + "start_line": 7944, + "end_line": 7948, + "section": "Links" + }, + { + "markdown": "[foo\n", + "html": "

    [foohttps://example.com/?search=](uri)

    \n", + "example": 526, + "start_line": 7951, + "end_line": 7955, + "section": "Links" + }, + { + "markdown": "[foo][bar]\n\n[bar]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 527, + "start_line": 7989, + "end_line": 7995, + "section": "Links" + }, + { + "markdown": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n", + "html": "

    link [foo [bar]]

    \n", + "example": 528, + "start_line": 8004, + "end_line": 8010, + "section": "Links" + }, + { + "markdown": "[link \\[bar][ref]\n\n[ref]: /uri\n", + "html": "

    link [bar

    \n", + "example": 529, + "start_line": 8013, + "end_line": 8019, + "section": "Links" + }, + { + "markdown": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n", + "html": "

    link foo bar #

    \n", + "example": 530, + "start_line": 8024, + "end_line": 8030, + "section": "Links" + }, + { + "markdown": "[![moon](moon.jpg)][ref]\n\n[ref]: /uri\n", + "html": "

    \"moon\"

    \n", + "example": 531, + "start_line": 8033, + "end_line": 8039, + "section": "Links" + }, + { + "markdown": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n", + "html": "

    [foo bar]ref

    \n", + "example": 532, + "start_line": 8044, + "end_line": 8050, + "section": "Links" + }, + { + "markdown": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n", + "html": "

    [foo bar baz]ref

    \n", + "example": 533, + "start_line": 8053, + "end_line": 8059, + "section": "Links" + }, + { + "markdown": "*[foo*][ref]\n\n[ref]: /uri\n", + "html": "

    *foo*

    \n", + "example": 534, + "start_line": 8068, + "end_line": 8074, + "section": "Links" + }, + { + "markdown": "[foo *bar][ref]*\n\n[ref]: /uri\n", + "html": "

    foo *bar*

    \n", + "example": 535, + "start_line": 8077, + "end_line": 8083, + "section": "Links" + }, + { + "markdown": "[foo \n\n[ref]: /uri\n", + "html": "

    [foo

    \n", + "example": 536, + "start_line": 8089, + "end_line": 8095, + "section": "Links" + }, + { + "markdown": "[foo`][ref]`\n\n[ref]: /uri\n", + "html": "

    [foo][ref]

    \n", + "example": 537, + "start_line": 8098, + "end_line": 8104, + "section": "Links" + }, + { + "markdown": "[foo\n\n[ref]: /uri\n", + "html": "

    [foohttps://example.com/?search=][ref]

    \n", + "example": 538, + "start_line": 8107, + "end_line": 8113, + "section": "Links" + }, + { + "markdown": "[foo][BaR]\n\n[bar]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 539, + "start_line": 8118, + "end_line": 8124, + "section": "Links" + }, + { + "markdown": "[ẞ]\n\n[SS]: /url\n", + "html": "

    \n", + "example": 540, + "start_line": 8129, + "end_line": 8135, + "section": "Links" + }, + { + "markdown": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n", + "html": "

    Baz

    \n", + "example": 541, + "start_line": 8141, + "end_line": 8148, + "section": "Links" + }, + { + "markdown": "[foo] [bar]\n\n[bar]: /url \"title\"\n", + "html": "

    [foo] bar

    \n", + "example": 542, + "start_line": 8154, + "end_line": 8160, + "section": "Links" + }, + { + "markdown": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n", + "html": "

    [foo]\nbar

    \n", + "example": 543, + "start_line": 8163, + "end_line": 8171, + "section": "Links" + }, + { + "markdown": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n", + "html": "

    bar

    \n", + "example": 544, + "start_line": 8204, + "end_line": 8212, + "section": "Links" + }, + { + "markdown": "[bar][foo\\!]\n\n[foo!]: /url\n", + "html": "

    [bar][foo!]

    \n", + "example": 545, + "start_line": 8219, + "end_line": 8225, + "section": "Links" + }, + { + "markdown": "[foo][ref[]\n\n[ref[]: /uri\n", + "html": "

    [foo][ref[]

    \n

    [ref[]: /uri

    \n", + "example": 546, + "start_line": 8231, + "end_line": 8238, + "section": "Links" + }, + { + "markdown": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n", + "html": "

    [foo][ref[bar]]

    \n

    [ref[bar]]: /uri

    \n", + "example": 547, + "start_line": 8241, + "end_line": 8248, + "section": "Links" + }, + { + "markdown": "[[[foo]]]\n\n[[[foo]]]: /url\n", + "html": "

    [[[foo]]]

    \n

    [[[foo]]]: /url

    \n", + "example": 548, + "start_line": 8251, + "end_line": 8258, + "section": "Links" + }, + { + "markdown": "[foo][ref\\[]\n\n[ref\\[]: /uri\n", + "html": "

    foo

    \n", + "example": 549, + "start_line": 8261, + "end_line": 8267, + "section": "Links" + }, + { + "markdown": "[bar\\\\]: /uri\n\n[bar\\\\]\n", + "html": "

    bar\\

    \n", + "example": 550, + "start_line": 8272, + "end_line": 8278, + "section": "Links" + }, + { + "markdown": "[]\n\n[]: /uri\n", + "html": "

    []

    \n

    []: /uri

    \n", + "example": 551, + "start_line": 8284, + "end_line": 8291, + "section": "Links" + }, + { + "markdown": "[\n ]\n\n[\n ]: /uri\n", + "html": "

    [\n]

    \n

    [\n]: /uri

    \n", + "example": 552, + "start_line": 8294, + "end_line": 8305, + "section": "Links" + }, + { + "markdown": "[foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 553, + "start_line": 8317, + "end_line": 8323, + "section": "Links" + }, + { + "markdown": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    foo bar

    \n", + "example": 554, + "start_line": 8326, + "end_line": 8332, + "section": "Links" + }, + { + "markdown": "[Foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    Foo

    \n", + "example": 555, + "start_line": 8337, + "end_line": 8343, + "section": "Links" + }, + { + "markdown": "[foo] \n[]\n\n[foo]: /url \"title\"\n", + "html": "

    foo\n[]

    \n", + "example": 556, + "start_line": 8350, + "end_line": 8358, + "section": "Links" + }, + { + "markdown": "[foo]\n\n[foo]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 557, + "start_line": 8370, + "end_line": 8376, + "section": "Links" + }, + { + "markdown": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    foo bar

    \n", + "example": 558, + "start_line": 8379, + "end_line": 8385, + "section": "Links" + }, + { + "markdown": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    [foo bar]

    \n", + "example": 559, + "start_line": 8388, + "end_line": 8394, + "section": "Links" + }, + { + "markdown": "[[bar [foo]\n\n[foo]: /url\n", + "html": "

    [[bar foo

    \n", + "example": 560, + "start_line": 8397, + "end_line": 8403, + "section": "Links" + }, + { + "markdown": "[Foo]\n\n[foo]: /url \"title\"\n", + "html": "

    Foo

    \n", + "example": 561, + "start_line": 8408, + "end_line": 8414, + "section": "Links" + }, + { + "markdown": "[foo] bar\n\n[foo]: /url\n", + "html": "

    foo bar

    \n", + "example": 562, + "start_line": 8419, + "end_line": 8425, + "section": "Links" + }, + { + "markdown": "\\[foo]\n\n[foo]: /url \"title\"\n", + "html": "

    [foo]

    \n", + "example": 563, + "start_line": 8431, + "end_line": 8437, + "section": "Links" + }, + { + "markdown": "[foo*]: /url\n\n*[foo*]\n", + "html": "

    *foo*

    \n", + "example": 564, + "start_line": 8443, + "end_line": 8449, + "section": "Links" + }, + { + "markdown": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n", + "html": "

    foo

    \n", + "example": 565, + "start_line": 8455, + "end_line": 8462, + "section": "Links" + }, + { + "markdown": "[foo][]\n\n[foo]: /url1\n", + "html": "

    foo

    \n", + "example": 566, + "start_line": 8464, + "end_line": 8470, + "section": "Links" + }, + { + "markdown": "[foo]()\n\n[foo]: /url1\n", + "html": "

    foo

    \n", + "example": 567, + "start_line": 8474, + "end_line": 8480, + "section": "Links" + }, + { + "markdown": "[foo](not a link)\n\n[foo]: /url1\n", + "html": "

    foo(not a link)

    \n", + "example": 568, + "start_line": 8482, + "end_line": 8488, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url\n", + "html": "

    [foo]bar

    \n", + "example": 569, + "start_line": 8493, + "end_line": 8499, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n", + "html": "

    foobaz

    \n", + "example": 570, + "start_line": 8505, + "end_line": 8512, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n", + "html": "

    [foo]bar

    \n", + "example": 571, + "start_line": 8518, + "end_line": 8525, + "section": "Links" + }, + { + "markdown": "![foo](/url \"title\")\n", + "html": "

    \"foo\"

    \n", + "example": 572, + "start_line": 8541, + "end_line": 8545, + "section": "Images" + }, + { + "markdown": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "html": "

    \"foo

    \n", + "example": 573, + "start_line": 8548, + "end_line": 8554, + "section": "Images" + }, + { + "markdown": "![foo ![bar](/url)](/url2)\n", + "html": "

    \"foo

    \n", + "example": 574, + "start_line": 8557, + "end_line": 8561, + "section": "Images" + }, + { + "markdown": "![foo [bar](/url)](/url2)\n", + "html": "

    \"foo

    \n", + "example": 575, + "start_line": 8564, + "end_line": 8568, + "section": "Images" + }, + { + "markdown": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "html": "

    \"foo

    \n", + "example": 576, + "start_line": 8578, + "end_line": 8584, + "section": "Images" + }, + { + "markdown": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n", + "html": "

    \"foo

    \n", + "example": 577, + "start_line": 8587, + "end_line": 8593, + "section": "Images" + }, + { + "markdown": "![foo](train.jpg)\n", + "html": "

    \"foo\"

    \n", + "example": 578, + "start_line": 8596, + "end_line": 8600, + "section": "Images" + }, + { + "markdown": "My ![foo bar](/path/to/train.jpg \"title\" )\n", + "html": "

    My \"foo

    \n", + "example": 579, + "start_line": 8603, + "end_line": 8607, + "section": "Images" + }, + { + "markdown": "![foo]()\n", + "html": "

    \"foo\"

    \n", + "example": 580, + "start_line": 8610, + "end_line": 8614, + "section": "Images" + }, + { + "markdown": "![](/url)\n", + "html": "

    \"\"

    \n", + "example": 581, + "start_line": 8617, + "end_line": 8621, + "section": "Images" + }, + { + "markdown": "![foo][bar]\n\n[bar]: /url\n", + "html": "

    \"foo\"

    \n", + "example": 582, + "start_line": 8626, + "end_line": 8632, + "section": "Images" + }, + { + "markdown": "![foo][bar]\n\n[BAR]: /url\n", + "html": "

    \"foo\"

    \n", + "example": 583, + "start_line": 8635, + "end_line": 8641, + "section": "Images" + }, + { + "markdown": "![foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    \"foo\"

    \n", + "example": 584, + "start_line": 8646, + "end_line": 8652, + "section": "Images" + }, + { + "markdown": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    \"foo

    \n", + "example": 585, + "start_line": 8655, + "end_line": 8661, + "section": "Images" + }, + { + "markdown": "![Foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    \"Foo\"

    \n", + "example": 586, + "start_line": 8666, + "end_line": 8672, + "section": "Images" + }, + { + "markdown": "![foo] \n[]\n\n[foo]: /url \"title\"\n", + "html": "

    \"foo\"\n[]

    \n", + "example": 587, + "start_line": 8678, + "end_line": 8686, + "section": "Images" + }, + { + "markdown": "![foo]\n\n[foo]: /url \"title\"\n", + "html": "

    \"foo\"

    \n", + "example": 588, + "start_line": 8691, + "end_line": 8697, + "section": "Images" + }, + { + "markdown": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    \"foo

    \n", + "example": 589, + "start_line": 8700, + "end_line": 8706, + "section": "Images" + }, + { + "markdown": "![[foo]]\n\n[[foo]]: /url \"title\"\n", + "html": "

    ![[foo]]

    \n

    [[foo]]: /url "title"

    \n", + "example": 590, + "start_line": 8711, + "end_line": 8718, + "section": "Images" + }, + { + "markdown": "![Foo]\n\n[foo]: /url \"title\"\n", + "html": "

    \"Foo\"

    \n", + "example": 591, + "start_line": 8723, + "end_line": 8729, + "section": "Images" + }, + { + "markdown": "!\\[foo]\n\n[foo]: /url \"title\"\n", + "html": "

    ![foo]

    \n", + "example": 592, + "start_line": 8735, + "end_line": 8741, + "section": "Images" + }, + { + "markdown": "\\![foo]\n\n[foo]: /url \"title\"\n", + "html": "

    !foo

    \n", + "example": 593, + "start_line": 8747, + "end_line": 8753, + "section": "Images" + }, + { + "markdown": "\n", + "html": "

    http://foo.bar.baz

    \n", + "example": 594, + "start_line": 8780, + "end_line": 8784, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    https://foo.bar.baz/test?q=hello&id=22&boolean

    \n", + "example": 595, + "start_line": 8787, + "end_line": 8791, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    irc://foo.bar:2233/baz

    \n", + "example": 596, + "start_line": 8794, + "end_line": 8798, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    MAILTO:FOO@BAR.BAZ

    \n", + "example": 597, + "start_line": 8803, + "end_line": 8807, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    a+b+c:d

    \n", + "example": 598, + "start_line": 8815, + "end_line": 8819, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    made-up-scheme://foo,bar

    \n", + "example": 599, + "start_line": 8822, + "end_line": 8826, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    https://../

    \n", + "example": 600, + "start_line": 8829, + "end_line": 8833, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    localhost:5001/foo

    \n", + "example": 601, + "start_line": 8836, + "end_line": 8840, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <https://foo.bar/baz bim>

    \n", + "example": 602, + "start_line": 8845, + "end_line": 8849, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    https://example.com/\\[\\

    \n", + "example": 603, + "start_line": 8854, + "end_line": 8858, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    foo@bar.example.com

    \n", + "example": 604, + "start_line": 8876, + "end_line": 8880, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    foo+special@Bar.baz-bar0.com

    \n", + "example": 605, + "start_line": 8883, + "end_line": 8887, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <foo+@bar.example.com>

    \n", + "example": 606, + "start_line": 8892, + "end_line": 8896, + "section": "Autolinks" + }, + { + "markdown": "<>\n", + "html": "

    <>

    \n", + "example": 607, + "start_line": 8901, + "end_line": 8905, + "section": "Autolinks" + }, + { + "markdown": "< https://foo.bar >\n", + "html": "

    < https://foo.bar >

    \n", + "example": 608, + "start_line": 8908, + "end_line": 8912, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <m:abc>

    \n", + "example": 609, + "start_line": 8915, + "end_line": 8919, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <foo.bar.baz>

    \n", + "example": 610, + "start_line": 8922, + "end_line": 8926, + "section": "Autolinks" + }, + { + "markdown": "https://example.com\n", + "html": "

    https://example.com

    \n", + "example": 611, + "start_line": 8929, + "end_line": 8933, + "section": "Autolinks" + }, + { + "markdown": "foo@bar.example.com\n", + "html": "

    foo@bar.example.com

    \n", + "example": 612, + "start_line": 8936, + "end_line": 8940, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 613, + "start_line": 9016, + "end_line": 9020, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 614, + "start_line": 9025, + "end_line": 9029, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 615, + "start_line": 9034, + "end_line": 9040, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 616, + "start_line": 9045, + "end_line": 9051, + "section": "Raw HTML" + }, + { + "markdown": "Foo \n", + "html": "

    Foo

    \n", + "example": 617, + "start_line": 9056, + "end_line": 9060, + "section": "Raw HTML" + }, + { + "markdown": "<33> <__>\n", + "html": "

    <33> <__>

    \n", + "example": 618, + "start_line": 9065, + "end_line": 9069, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    <a h*#ref="hi">

    \n", + "example": 619, + "start_line": 9074, + "end_line": 9078, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    <a href="hi'> <a href=hi'>

    \n", + "example": 620, + "start_line": 9083, + "end_line": 9087, + "section": "Raw HTML" + }, + { + "markdown": "< a><\nfoo>\n\n", + "html": "

    < a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />

    \n", + "example": 621, + "start_line": 9092, + "end_line": 9102, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    <a href='bar'title=title>

    \n", + "example": 622, + "start_line": 9107, + "end_line": 9111, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    \n", + "example": 623, + "start_line": 9116, + "end_line": 9120, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    </a href="foo">

    \n", + "example": 624, + "start_line": 9125, + "end_line": 9129, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 625, + "start_line": 9134, + "end_line": 9140, + "section": "Raw HTML" + }, + { + "markdown": "foo foo -->\n\nfoo foo -->\n", + "html": "

    foo foo -->

    \n

    foo foo -->

    \n", + "example": 626, + "start_line": 9142, + "end_line": 9149, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 627, + "start_line": 9154, + "end_line": 9158, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 628, + "start_line": 9163, + "end_line": 9167, + "section": "Raw HTML" + }, + { + "markdown": "foo &<]]>\n", + "html": "

    foo &<]]>

    \n", + "example": 629, + "start_line": 9172, + "end_line": 9176, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 630, + "start_line": 9182, + "end_line": 9186, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 631, + "start_line": 9191, + "end_line": 9195, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    <a href=""">

    \n", + "example": 632, + "start_line": 9198, + "end_line": 9202, + "section": "Raw HTML" + }, + { + "markdown": "foo \nbaz\n", + "html": "

    foo
    \nbaz

    \n", + "example": 633, + "start_line": 9212, + "end_line": 9218, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\nbaz\n", + "html": "

    foo
    \nbaz

    \n", + "example": 634, + "start_line": 9224, + "end_line": 9230, + "section": "Hard line breaks" + }, + { + "markdown": "foo \nbaz\n", + "html": "

    foo
    \nbaz

    \n", + "example": 635, + "start_line": 9235, + "end_line": 9241, + "section": "Hard line breaks" + }, + { + "markdown": "foo \n bar\n", + "html": "

    foo
    \nbar

    \n", + "example": 636, + "start_line": 9246, + "end_line": 9252, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\n bar\n", + "html": "

    foo
    \nbar

    \n", + "example": 637, + "start_line": 9255, + "end_line": 9261, + "section": "Hard line breaks" + }, + { + "markdown": "*foo \nbar*\n", + "html": "

    foo
    \nbar

    \n", + "example": 638, + "start_line": 9267, + "end_line": 9273, + "section": "Hard line breaks" + }, + { + "markdown": "*foo\\\nbar*\n", + "html": "

    foo
    \nbar

    \n", + "example": 639, + "start_line": 9276, + "end_line": 9282, + "section": "Hard line breaks" + }, + { + "markdown": "`code \nspan`\n", + "html": "

    code span

    \n", + "example": 640, + "start_line": 9287, + "end_line": 9292, + "section": "Hard line breaks" + }, + { + "markdown": "`code\\\nspan`\n", + "html": "

    code\\ span

    \n", + "example": 641, + "start_line": 9295, + "end_line": 9300, + "section": "Hard line breaks" + }, + { + "markdown": "
    \n", + "html": "

    \n", + "example": 642, + "start_line": 9305, + "end_line": 9311, + "section": "Hard line breaks" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 643, + "start_line": 9314, + "end_line": 9320, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\n", + "html": "

    foo\\

    \n", + "example": 644, + "start_line": 9327, + "end_line": 9331, + "section": "Hard line breaks" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 645, + "start_line": 9334, + "end_line": 9338, + "section": "Hard line breaks" + }, + { + "markdown": "### foo\\\n", + "html": "

    foo\\

    \n", + "example": 646, + "start_line": 9341, + "end_line": 9345, + "section": "Hard line breaks" + }, + { + "markdown": "### foo \n", + "html": "

    foo

    \n", + "example": 647, + "start_line": 9348, + "end_line": 9352, + "section": "Hard line breaks" + }, + { + "markdown": "foo\nbaz\n", + "html": "

    foo\nbaz

    \n", + "example": 648, + "start_line": 9363, + "end_line": 9369, + "section": "Soft line breaks" + }, + { + "markdown": "foo \n baz\n", + "html": "

    foo\nbaz

    \n", + "example": 649, + "start_line": 9375, + "end_line": 9381, + "section": "Soft line breaks" + }, + { + "markdown": "hello $.;'there\n", + "html": "

    hello $.;'there

    \n", + "example": 650, + "start_line": 9395, + "end_line": 9399, + "section": "Textual content" + }, + { + "markdown": "Foo χρῆν\n", + "html": "

    Foo χρῆν

    \n", + "example": 651, + "start_line": 9402, + "end_line": 9406, + "section": "Textual content" + }, + { + "markdown": "Multiple spaces\n", + "html": "

    Multiple spaces

    \n", + "example": 652, + "start_line": 9411, + "end_line": 9415, + "section": "Textual content" + } +] \ No newline at end of file diff --git a/xtask/coverage/src/reporters.rs b/xtask/coverage/src/reporters.rs index d1e0afe7d772..b89fe56d80d6 100644 --- a/xtask/coverage/src/reporters.rs +++ b/xtask/coverage/src/reporters.rs @@ -251,7 +251,7 @@ impl SummaryReporter { let coverage = if summary.coverage.is_nan() { "\u{221E}".to_string() } else { - format!("{:.2}", summary.coverage) + format!("{:.2}%", summary.coverage) }; let total = panicked + errored + passed; diff --git a/xtask/coverage/src/runner.rs b/xtask/coverage/src/runner.rs index 32db86fbe1d3..339a2406abb2 100644 --- a/xtask/coverage/src/runner.rs +++ b/xtask/coverage/src/runner.rs @@ -106,7 +106,7 @@ pub(crate) fn create_bogus_node_in_tree_diagnostic(node: JsSyntaxNode) -> ParseD .with_hint( "This bogus node is present in the parsed tree but the parser didn't emit a diagnostic for it. Change the parser to either emit a diagnostic, fix the grammar, or the parsing.") } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub(crate) struct TestCaseFiles { files: Vec, } @@ -185,6 +185,11 @@ pub(crate) trait TestSuite: Send + Sync { fn is_test(&self, path: &Path) -> bool; fn load_test(&self, path: &Path) -> Option>; fn checkout(&self) -> io::Result<()>; + + /// Override to provide tests without filesystem scanning. + fn load_all(&self) -> Option>> { + None + } } pub(crate) struct TestSuiteInstance { @@ -341,6 +346,17 @@ pub(crate) fn run_test_suite( } fn load_tests(suite: &dyn TestSuite, context: &mut TestRunContext) -> TestSuiteInstance { + if let Some(mut tests) = suite.load_all() { + if let Some(filter) = &context.filter { + tests.retain(|test| test.name().contains(filter.as_str())); + } + context.reporter.tests_discovered(suite, tests.len()); + for _ in &tests { + context.reporter.test_loaded(); + } + return TestSuiteInstance::new(suite, tests); + } + let paths = WalkDir::new(suite.base_path()) .into_iter() .filter_map(Result::ok) diff --git a/xtask/rules_check/src/lib.rs b/xtask/rules_check/src/lib.rs index 3df32cbc2e2e..8c44eabb6f61 100644 --- a/xtask/rules_check/src/lib.rs +++ b/xtask/rules_check/src/lib.rs @@ -300,8 +300,9 @@ fn assert_lint( .unwrap_or_else(|_| biome_html_syntax::HtmlFileSource::html()), ) } else { - test.document_file_source() + test.document_file_source_from_path() }; + match document_file_source { DocumentFileSource::Js(file_source) => { // Temporary support for astro, svelte and vue code blocks @@ -310,7 +311,7 @@ fn assert_lint( biome_service::file_handlers::AstroFileHandler::input(code), JsFileSource::ts(), ), - EmbeddingKind::Svelte => ( + EmbeddingKind::Svelte { .. } => ( biome_service::file_handlers::SvelteFileHandler::input(code), biome_service::file_handlers::SvelteFileHandler::file_source(code), ), @@ -385,29 +386,35 @@ fn assert_lint( file_source, configuration_provider: None, }; - biome_json_analyze::analyze(&root, filter, &options, json_services, |signal| { - if let Some(mut diag) = signal.diagnostic() { - for action in signal.actions() { - if !action.is_suppression() { - diag = diag.add_code_suggestion(action.into()); + biome_json_analyze::analyze( + &root, + filter, + &options, + json_services, + &[], + |signal| { + if let Some(mut diag) = signal.diagnostic() { + for action in signal.actions() { + if !action.is_suppression() { + diag = diag.add_code_suggestion(action.into()); + } } - } - let error = diag - .with_file_path(test.file_path()) - .with_file_source_code(code); - diagnostics.write_diagnostic(error); - } + let error = diag + .with_file_path(test.file_path()) + .with_file_source_code(code); + diagnostics.write_diagnostic(error); + } - ControlFlow::<()>::Continue(()) - }); + ControlFlow::<()>::Continue(()) + }, + ); } } DocumentFileSource::Css(file_source) => { - let parse_options = CssParserOptions::default() - .allow_css_modules() - .allow_tailwind_directives(); - let parse = biome_css_parser::parse_css(code, parse_options); + let parse_options = CssParserOptions::from(&file_source); + + let parse = biome_css_parser::parse_css(code, file_source, parse_options); if parse.has_errors() { for diag in parse.into_diagnostics() { @@ -508,7 +515,7 @@ fn assert_lint( let options = test.create_analyzer_options::(config)?; - biome_html_analyze::analyze(&root, filter, &options, |signal| { + biome_html_analyze::analyze(&root, filter, &options, source, |signal| { if let Some(mut diag) = signal.diagnostic() { for action in signal.actions() { if !action.is_suppression() { @@ -574,7 +581,9 @@ fn make_json_object_with_single_member>( make::token(biome_json_syntax::JsonSyntaxKind::L_CURLY), make::json_member_list( [make::json_member( - make::json_member_name(make::json_string_literal(name)), + biome_json_syntax::AnyJsonMemberName::JsonMemberName(make::json_member_name( + make::json_string_literal(name), + )), make::token(biome_json_syntax::JsonSyntaxKind::COLON), value.into(), )], @@ -592,7 +601,7 @@ fn get_first_member>(parent: V, expected_name: &str) -> Op .into_iter() .next()? .ok()?; - let member_name = member.name().ok()?.inner_string_text().ok()?.to_string(); + let member_name = member.name().ok()?.inner_string_text()?.ok()?.to_string(); if member_name.as_str() == expected_name { member.value().ok()