diff --git a/.claude/rules/filters/cross-project-consumers.md b/.claude/rules/filters/cross-project-consumers.md new file mode 100644 index 00000000000..34ed7d9124e --- /dev/null +++ b/.claude/rules/filters/cross-project-consumers.md @@ -0,0 +1,70 @@ +--- +paths: + - "src/resources/filters/**" + - "package/src/common/prepare-dist.ts" + - "package/src/common/package-filters.ts" +--- + +# Filters Consumed by Other Projects (do not delete without checking) + +Some Lua filters in `src/resources/filters/` are invoked **standalone** (as bare +`pandoc --lua-filter` / `--defaults` passes, outside Quarto's `render` pipeline) +by **other repositories** that ship or embed Quarto. "Nothing in quarto-cli +references this file" is therefore **not** sufficient proof that a filter is dead +code. Always run the checklist below before removing or renaming anything under +`src/resources/filters/`. + +## Cross-project consumers + +Both consumers build the crossref/xref index used for visual-editor autocomplete +by calling `pandoc` directly against Quarto's installed share tree +(`QUARTO_SHARE_PATH`), loading these filters: + +- `filters/quarto-init/quarto-init.lua` +- `filters/crossref/crossref.lua` +- `filters/qmd-reader.lua` (used as the `--from` reader when present) + +with `QUARTO_FILTER_PARAMS` set to `{ "crossref-index-file": ..., "crossref-input-type": "qmd" }`. + +| Consumer | File | What it does | +|----------|------|--------------| +| Visual editor (`quarto-dev/quarto`) | `packages/editor-server/src/core/xref.ts` (`indexSourceFile`) | Writes a `defaults.yml` listing `quarto-init.lua` + `crossref.lua`, runs pandoc to emit `index.json`. | +| RStudio (`rstudio/rstudio`) | `src/cpp/session/modules/quarto/SessionQuartoXRefs.cpp` | Same defaults + filters, from the C++ side. | + +The visual editor also bundles its **own** unrelated filters +(`parser.lua`, `sourcepos.lua`, `heading-ids.lua` in `editor-server`) — those are +not ours and are not relevant here. + +## Why "no in-repo reference" is not enough + +The installer does **not** ship `src/resources/filters/` verbatim. In +`package/src/common/prepare-dist.ts`: + +1. The copied `share/filters` source tree is **deleted** (`pathsToClean`). +2. `inlineFilters()` rebuilds `share/filters` from a **fixed list** + (`filtersToInline`: `main, pagebreak, quarto-init, crossref, customwriter, + qmd-reader, llms, leveloneanalysis`) plus the `modules/` directory. + `buildFilter` (`package-filters.ts`) inlines every `-- [import]` into one + self-contained file. + +So the shipped `crossref/crossref.lua` is already a **standalone, fully-inlined** +bundle — the build auto-generates the standalone crossref filter from +`crossref.lua`. A hand-maintained "standalone" duplicate is redundant. Anything +**not** in the inline list (and not under `modules/`) is never shipped and cannot +be a cross-project dependency via the share path. + +## Removal checklist + +Before deleting/renaming a filter under `src/resources/filters/`: + +1. Is it in the `filtersToInline` list (or reachable via `modules/`) in + `prepare-dist.ts`? If **not**, it was never shipped — confirm it is also unused + at render time before removing. +2. Is it (or its basename) referenced by `quarto-dev/quarto` or + `rstudio/rstudio`? Grep both for the basename and for `filters//`. + See `dev-docs/cross-project-dependencies.md`. +3. A GitHub-wide code search for the basename catches extension-ecosystem usage. + +Precedent: `crossref-standalone.lua` and `quarto-pre/quarto-pre.lua` were removed +after confirming (a) neither was in the inline list, so neither was ever shipped, +and (b) both consumers use `crossref.lua`/`quarto-init.lua`, not the removed files. diff --git a/dev-docs/cross-project-dependencies.md b/dev-docs/cross-project-dependencies.md new file mode 100644 index 00000000000..c2223edcf93 --- /dev/null +++ b/dev-docs/cross-project-dependencies.md @@ -0,0 +1,74 @@ +# Cross-Project Dependencies + +Other Posit repositories embed or ship Quarto and reach directly into +quarto-cli's installed resources and commands. These couplings are **not** +visible from within quarto-cli — nothing here references the consumer — so a +change that looks internal can break the visual editor or RStudio. This file +records the known touch-points so they can be checked before changing the +surfaces involved. + +The two consumers are: + +- **`quarto-dev/quarto`** — the visual editor / VS Code + Positron tooling + monorepo (`packages/editor-server`, `packages/quarto-core`, `apps/lsp`, ...). +- **`rstudio/rstudio`** — the RStudio IDE (visual editor backend in + `src/cpp/session/modules/`). + +> When you change one of the surfaces below, grep the consumer repo(s) for the +> path/basename and coordinate a companion change if needed. + +## 1. Standalone crossref/xref filter invocation + +To power visual-editor cross-reference autocomplete, both consumers run `pandoc` +directly against Quarto's installed share tree (`QUARTO_SHARE_PATH`) with a +`--defaults` file that loads these filters standalone (outside `quarto render`): + +- `filters/quarto-init/quarto-init.lua` +- `filters/crossref/crossref.lua` +- `filters/qmd-reader.lua` (used as `--from` when present) + +Environment: `QUARTO_FILTER_PARAMS` = `{ "crossref-index-file": "index.json", +"crossref-input-type": "qmd" }`, `QUARTO_SHARE_PATH` = the resource path. + +| Consumer | File | +|----------|------| +| `quarto-dev/quarto` | `packages/editor-server/src/core/xref.ts` (`indexSourceFile`) | +| `rstudio/rstudio` | `src/cpp/session/modules/quarto/SessionQuartoXRefs.cpp` | + +**Contract:** the names/relative paths of these filters, their filter-params +protocol, and the `index.json` output shape are a de-facto public API. Renaming, +removing, or moving any of them, or dropping them from the packaging inline list +(see below), breaks both consumers. See +`.claude/rules/filters/cross-project-consumers.md`. + +## 2. What actually ships in `share/filters` + +`src/resources/filters/` is **not** shipped verbatim. `package/src/common/prepare-dist.ts` +deletes the copied `share/filters` and rebuilds it via `inlineFilters()` from a +fixed list (`filtersToInline`) plus the `modules/` directory; `buildFilter` +(`package-filters.ts`) inlines each `-- [import]` into one self-contained file. +A filter that is **not** in the inline list and **not** under `modules/` is never +installed, so it cannot be a cross-project dependency via the share path — but it +also means "grep finds no reference" does not prove a *shipped* filter is unused. + +## 3. Editor support commands + +`quarto-core` / the editor tooling also shell out to `quarto` subcommands +(e.g. `quarto editor-support crossref`, implemented in +`src/command/editor-support/crossref.ts`). Changing the CLI surface or output +format of editor-support commands is a cross-project change. + +## Checklist when touching a shared surface + +1. Identify which consumer(s) use it (tables above). +2. Grep the consumer repo for the path/basename and the CLI/protocol shape. +3. For filters: confirm inline-list membership in `prepare-dist.ts`. +4. Coordinate a companion PR in the consumer if the contract changes. + +## History + +- Removing `crossref-standalone.lua` and `quarto-pre/quarto-pre.lua`: verified + safe because neither was in the packaging inline list (never shipped) and both + consumers load `crossref.lua`/`quarto-init.lua`, not the removed files. This + doc and the scoped rule were written as part of that investigation so the check + does not have to be re-derived. diff --git a/src/resources/filters/crossref/crossref-standalone.lua b/src/resources/filters/crossref/crossref-standalone.lua deleted file mode 100644 index 47762e0438d..00000000000 --- a/src/resources/filters/crossref/crossref-standalone.lua +++ /dev/null @@ -1,65 +0,0 @@ --- crossref-standalone.lua --- Copyright (C) 2020-2023 Posit Software, PBC - --- required version -PANDOC_VERSION:must_be_at_least '2.13' - - --- [import] -function import(script) - local path = PANDOC_SCRIPT_FILE:match("(.*[/\\])") - dofile(path .. script) -end - --- FIXME: needs updating for float-reftargets branch. - -import("../mainstateinit.lua") -import("index.lua") -import("preprocess.lua") -import("sections.lua") -import("figures.lua") -import("tables.lua") -import("equations.lua") -import("theorems.lua") -import("qmd.lua") -import("refs.lua") -import("meta.lua") -import("format.lua") -import("options.lua") -import("../common/lunacolors.lua") -import("../common/log.lua") -import("../common/pandoc.lua") -import("../common/format.lua") -import("../common/base64.lua") -import("../common/options.lua") -import("../common/refs.lua") -import("../common/filemetadata.lua") -import("../common/figures.lua") -import("../common/tables.lua") -import("../common/theorems.lua") -import("../common/meta.lua") -import("../common/table.lua") -import("../common/string.lua") -import("../common/debug.lua") --- [/import] - -initCrossrefIndex() - --- chain of filters -return { - init_crossref_options(), - crossref_mark_subfloats(), - crossref_preprocess_theorems(), - combineFilters({ - file_metadata(), - qmd(), - sections(), - crossref_figures(), - crossref_tables(), - equations(), - crossref_theorems(), - }), - resolveRefs(), - crossrefMetaInject(), - writeIndex() -} \ No newline at end of file diff --git a/src/resources/filters/quarto-pre/quarto-pre.lua b/src/resources/filters/quarto-pre/quarto-pre.lua deleted file mode 100644 index 6f02e02d053..00000000000 --- a/src/resources/filters/quarto-pre/quarto-pre.lua +++ /dev/null @@ -1,155 +0,0 @@ --- quarto-pre.lua --- Copyright (C) 2020-2022 Posit Software, PBC - --- required version -PANDOC_VERSION:must_be_at_least '2.13' - --- [import] -function import(script) - local path = PANDOC_SCRIPT_FILE:match("(.*[/\\])") - dofile(path .. script) -end - -import("../mainstateinit.lua") -import("../common/colors.lua") -import("../common/error.lua") -import("../common/base64.lua") -import("../common/latex.lua") -import("../common/meta.lua") -import("../common/options.lua") -import("../common/table.lua") -import("../common/pandoc.lua") -import("../common/filemetadata.lua") -import("../common/layout.lua") -import("../common/refs.lua") -import("../common/figures.lua") -import("../common/tables.lua") -import("../common/theorems.lua") -import("../common/debug.lua") -import("../common/format.lua") -import("../common/string.lua") -import("../common/list.lua") -import("../common/lunacolors.lua") -import("../common/log.lua") -import("../common/url.lua") -import("../common/paths.lua") -import("results.lua") -import("options.lua") -import("code-filename.lua") -import("shortcodes-handlers.lua") -import("outputs.lua") -import("figures.lua") -import("table-rawhtml.lua") -import("table-captions.lua") -import("table-colwidth.lua") -import("theorems.lua") -import("resourcefiles.lua") -import("bibliography-formats.lua") -import("book-numbering.lua") -import("book-links.lua") -import("meta.lua") -import("code.lua") -import("engine-escape.lua") -import("panel-sidebar.lua") -import("panel-input.lua") -import("panel-layout.lua") -import("hidden.lua") -import("line-numbers.lua") -import("output-location.lua") -import("include-paths.lua") -import("input-traits.lua") -import("project-paths.lua") - -import("../customnodes/shortcodes.lua") -import("../customnodes/content-hidden.lua") -import("../customnodes/callout.lua") -import("../customnodes/decoratedcodeblock.lua") -import("../customnodes/panel-tabset.lua") - --- [/import] - -initShortcodeHandlers() - -local filterList = { - - { name = "flags", filter = compute_flags() }, - - -- https://github.com/quarto-dev/quarto-cli/issues/5031 - -- recompute options object in case user filters have changed meta - -- this will need to change in the future; users will have to indicate - -- when they mutate options - { name = "pre-read-options-again", filter = init_options() }, - - { name = "pre-parse-pandoc3-figures", - filter = parse_pandoc3_figures(), - flags = { "has_pandoc3_figure" } - }, - - { name = "pre-bibliography-formats", filter = bibliography_formats() }, - - { name = "pre-shortcodes-filter", - filter = shortcodes_filter(), - flags = { "has_shortcodes" } }, - - { name = "pre-hidden", - filter = hidden(), - flags = { "has_hidden" } }, - - { name = "pre-content-hidden", - filter = content_hidden(), - flags = { "has_conditional_content" } }, - - { name = "pre-table-captions", - filter = table_captions(), - flags = { "has_table_captions" } }, - - { name = "pre-longtable-no-caption-fixup", - filter = longtable_no_caption_fixup(), - flags = { "has_longtable_no_caption_fixup" } }, - - { name = "pre-code-annotations", - filter = code_annotations(), - flags = { "has_code_annotations" } }, - - { name = "pre-code-annotations-meta", filter = code_meta() }, - - { name = "pre-unroll-cell-outputs", - filter = unroll_cell_outputs(), - flags = { "needs_output_unrolling" } }, - - { name = "pre-output-location", - filter = output_location() - }, - - { name = "pre-scope-resolution", - filter = resolve_scoped_elements() - }, - - { name = "pre-combined-figures-theorems-etc", filter = combineFilters({ - file_metadata(), - index_book_file_targets(), - book_numbering(), - include_paths(), - resource_files(), - quarto_pre_figures(), - quarto_pre_theorems(), - docx_callout_and_table_fixup(), - -- code_filename(), - line_numbers(), - engine_escape(), - bootstrap_panel_input(), - bootstrap_panel_layout(), - bootstrap_panel_sidebar(), - table_respecify_gt_css(), - -- table_colwidth(), - table_classes(), - input_traits(), - resolve_book_file_targets(), - project_paths() - }) }, - - { name = "pre-quarto-pre-meta-inject", filter = quarto_pre_meta_inject() }, - { name = "pre-write-results", filter = write_results() }, -} - -return filterList