From 72102cf447374c418f78b61f7e3a51fcb5601fa8 Mon Sep 17 00:00:00 2001 From: Dylan Piercey Date: Fri, 28 Aug 2026 06:32:58 -0700 Subject: [PATCH] docs(agent-feedback): file namespaced-attr, exports-map and imported-template findings --- .../2026-08-20-mtc-diagnostics-without-position.md | 6 +++++- .../items/2026-08-28-exports-map-marko-subpath.md | 12 ++++++++++++ ...-08-28-imported-template-uses-dynamic-renderer.md | 12 ++++++++++++ .../2026-08-28-namespaced-attr-split-as-modifier.md | 12 ++++++++++++ .../2026-08-28-repeated-attr-tag-body-params.md | 12 ++++++++++++ .../items/2026-08-28-ts-ignore-above-a-tag.md | 12 ++++++++++++ .../items/2026-08-28-type-libs-resolved-from-cwd.md | 12 ++++++++++++ 7 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 agent-feedback/items/2026-08-28-exports-map-marko-subpath.md create mode 100644 agent-feedback/items/2026-08-28-imported-template-uses-dynamic-renderer.md create mode 100644 agent-feedback/items/2026-08-28-namespaced-attr-split-as-modifier.md create mode 100644 agent-feedback/items/2026-08-28-repeated-attr-tag-body-params.md create mode 100644 agent-feedback/items/2026-08-28-ts-ignore-above-a-tag.md create mode 100644 agent-feedback/items/2026-08-28-type-libs-resolved-from-cwd.md diff --git a/agent-feedback/items/2026-08-20-mtc-diagnostics-without-position.md b/agent-feedback/items/2026-08-20-mtc-diagnostics-without-position.md index b981c113..16ea5996 100644 --- a/agent-feedback/items/2026-08-20-mtc-diagnostics-without-position.md +++ b/agent-feedback/items/2026-08-20-mtc-diagnostics-without-position.md @@ -5,8 +5,12 @@ effort: low site: packages/type-check/src/run.ts › reportDiagnostic --- + + # Drop or re-anchor a diagnostic whose generated span does not map back into the `.marko` source -When `extracted.sourceLocationAt` returns nothing, `reportDiagnostic` falls through to a branch that still prints the diagnostic but without a line, a column or a code frame, so one run mixes `src/routes/tx/+page.marko:5:20 - error TS2345` with a bare `src/routes/tx/+page.marko - error TS2873`. A reader cannot act on the second kind and learns to skip the channel: a location-less "This kind of expression is always falsy" was the only signal `mtc` gave for a template broken by ` 1>`, where the compiler's own message is `Ambiguous ">" in attribute`. `convertDiag` in `packages/language-server/src/service/script/index.ts` drops these instead of printing them, so the editor and `mtc` already disagree about which errors exist. Either drop them the same way, or anchor them at the top of the file so there is somewhere to look. +When `extracted.sourceLocationAt` returns nothing, `reportDiagnostic` falls through to a branch that still prints the diagnostic but without a line, a column or a code frame, so one run mixes `src/routes/tx/+page.marko:5:20 - error TS2345` with a bare `src/routes/tx/+page.marko - error TS2873`. A reader cannot act on the second kind and learns to skip the channel: a location-less "This kind of expression is always falsy" was the only signal `mtc` gave for a template broken by ` 1>`, where the compiler's own message is `Ambiguous ">" in attribute`. `convertDiag` in `packages/language-server/src/service/script/index.ts` drops these instead of printing them, so the editor and `mtc` already disagree about which errors exist. Either drop them the same way, or anchor them at the top of the file so there is somewhere to look. One reproducible source of these is the program-level hoist read in `packages/language-tools/src/extractors/script/index.ts` › `ScriptExtractor#writeProgram`: when a tag's attributes object fails to type-check, TypeScript stops inferring the scope type from the body, and the `var {rowId: __marko_internal_hoist__rowId} = Marko._.readScope(...)` line the extractor `write()`s rather than `copy()`s from source reports `TS2339 Property 'rowId' does not exist on type 'MergeScopes'` with no mapping, so it prints bare and names a tag variable the reader can see declared just above it. That one reaches both `` (through `forOfTag`/`forInTag`/`forToTag`) and custom tags (through `renderDynamicTag`), and it is pure cascade noise that clears once the attribute is fixed, so `copy()`ing the tag-variable name range into that destructure would both give it a position and make it droppable as a cascade. Check: type-check a `.marko` file containing `` and ` 1>`; `mtc` prints `src/x.marko - error TS2873` and `This kind of expression is always falsy.` with no line, column or frame today, and should either omit it or give it a `file:line:col` header and a frame like every other diagnostic in the run. + +Check: with `@marko/type-check` 3.2.0, type-check a file containing only `export interface Input { rows: { id: number }[] }`, ``, ` `, ``; `mtc` prints the expected TS2353 on `bogusattr` with a code frame and then a second, position-less `src/for-bad-attr.marko - error TS2339 Property 'rowId' does not exist on type 'MergeScopes'`, and removing `bogusattr` clears both. A custom tag does the same: `` against `export interface Input { n: number; content: Marko.Body }` reports its TS2322 plus the same unanchored TS2339. diff --git a/agent-feedback/items/2026-08-28-exports-map-marko-subpath.md b/agent-feedback/items/2026-08-28-exports-map-marko-subpath.md new file mode 100644 index 00000000..7aa4b43a --- /dev/null +++ b/agent-feedback/items/2026-08-28-exports-map-marko-subpath.md @@ -0,0 +1,12 @@ +--- +type: bug +impact: med +effort: med +site: packages/type-check/src/run.ts › compilerHost.resolveModuleNameLiterals +--- + +# Honour a package's `exports` map when resolving a `.marko` subpath in mtc and the TS plugin + +`resolveModuleNameLiterals` decides from the specifier's own text, taking its `.marko` branch only when the specifier carries a processor extension, so a bare specifier such as `@acme/ui/tags/acme-modal` falls through to `ts.bundlerModuleNameResolver`, which does honour `exports` but accepts only TS/JS/JSON targets. An `exports` pattern like `"./tags/*": "./dist/tags/*/index.marko"` is therefore TS2307 under `mtc` while Vite resolves it and builds, and a library author has no package.json-side workaround, since a `"types": "./dist/tags/*/index.d.marko"` condition is TS2307 as well. The cost is worse than the one diagnostic: the import becomes `any`, so the tag's attributes stop being checked (a bogus attribute is accepted silently) and `noImplicitAny` fires on its event handler parameters instead. The resolver is wrong in the other direction too, because the `.marko` node-module branch resolves `/package.json` and then joins the remaining subpath literally onto that directory, so a deep path that the package's `exports` does not expose type-checks clean and then fails the build with `Package subpath './dist/tags/acme-modal/index.marko' is not defined by "exports"`. Run the specifier through Node/TS `exports` resolution first and let the Marko processor claim the resulting file by its extension, rather than deciding on the specifier's extension; the same resolver is duplicated in `packages/language-server/src/ts-plugin/host.ts`, so a fix has to land in both (its comment there also says it resolves the module's `marko.json` while the code resolves `package.json`). + +Check: in a project on `@marko/type-check` 3.2.0 depending on a package whose `exports` has `"./tags/*": "./dist/tags/*/index.marko"`, write `import ModalA from "@acme/ui/tags/acme-modal"` next to `import ModalB from "@acme/ui/dist/tags/acme-modal/index.marko"` and `static const b: number = ModalB`. `npx mtc -d condensed` reports `error TS2307 Cannot find module '@acme/ui/tags/acme-modal' or its corresponding type declarations` plus `TS2322 Type 'Template' is not assignable to type 'number'` on the `ModalB` line only, and rendering `` reports only `TS7006 Parameter 'reason' implicitly has an 'any' type` where `` reports TS2353 on `"totallyBogus"`; `npx vite build --app` on the same file builds clean. Both import forms should type-check identically. For the reverse case, drop `"./dist/*"` from the package's `exports`: the `ModalB` import still type-checks under `mtc` and the Vite build then fails with `[plugin marko-vite:pre] Error: Package subpath './dist/tags/acme-modal/index.marko' is not defined by "exports"`. diff --git a/agent-feedback/items/2026-08-28-imported-template-uses-dynamic-renderer.md b/agent-feedback/items/2026-08-28-imported-template-uses-dynamic-renderer.md new file mode 100644 index 00000000..162b8d69 --- /dev/null +++ b/agent-feedback/items/2026-08-28-imported-template-uses-dynamic-renderer.md @@ -0,0 +1,12 @@ +--- +type: bug +impact: high +effort: med +site: packages/language-tools/marko.internal.d.ts › DynamicRenderer +--- + +# Restore `TemplateRenderer` for a statically known template in `DynamicRenderer` without losing 5e05dffd's perf win + +`#writeTag` flips `isTemplate` only on the branch where a kebab tag name resolves to a `.marko` file, so every identifier tag name is emitted as `Marko._.renderDynamicTag` and picks up `DynamicRenderer` rather than `TemplateRenderer`; that covers `` after `import Report from "./report.marko"`, the documented `import Report from ""` shorthand and the explicit `<${Report}/>` form alike. Commit 5e05dffd ("fix: improve performance for dynamic input intersections") merged `DynamicRenderer`'s template and string branches into one `>(input: Marko.Directives & Input)` signature, dropping the reuse of the template's own `_()` renderer and, with it, three checks the discovered spelling of the same tag still gets. Excess-property checking disappears as soon as the call site supplies attributes that satisfy `Marko.Input`, because `Input` then infers as the call-site literal and freshness has nothing left to reject, so `` and `<@nope title="a"/>` are silent where `` reports TS2353; generic parameters are lost, because `Marko.Input>>` cannot recover `T`, so `` is TS18046 where the discovered tag infers `{ id: string }`; and expanding `Marko.Input<>` over a self-referential attr-tag type (`group?: Marko.AttrTag` inside `GroupAttrs`) trips TS2589 on top of the real diagnostic. Missing required attributes and mistyped attribute values are still caught, and a call site whose attributes all fail the constraint still errors, which is why the `tags-api-basic` fixture kept passing and the hole survived the commit. Prefer `Name extends { _: infer Renderer } ? Renderer` for the statically known template case, the way `TemplateRenderer` already does, and keep 5e05dffd's merged intersection shape for genuinely dynamic names. + +Check: in a project on `@marko/type-check` 3.2.0 with a discoverable `tags/` dir holding `flat-tag.marko` (`export interface Input { title: string; count?: number }`), `gen-tag.marko` (`export interface Input { items: T[]; content: Marko.Body<[{ item: T; index: number }]> }`) and `rec-tag.marko` (`export interface GroupAttrs { title: string; group?: Marko.AttrTag }` plus `export interface Input { title: string; group?: Marko.AttrTag }`), render each tag twice from one template, once by discovered name and once through `import Flat from "./tags/flat-tag.marko"` and friends: `` vs ``, `<@nope title="a"/>` vs the `` form, `${item.id}` vs the `` form, and `` vs ``. `npx mtc -p tsconfig.json -d condensed` today reports TS2353 on the discovered forms only, TS18046 `'item' is of type 'unknown'` on the imported generic only, and TS2589 `Type instantiation is excessively deep and possibly infinite` on the imported recursive one only; each pair should report the same thing, and patching just the `Marko.Template` case back to `TemplateRenderer` makes all four agree. diff --git a/agent-feedback/items/2026-08-28-namespaced-attr-split-as-modifier.md b/agent-feedback/items/2026-08-28-namespaced-attr-split-as-modifier.md new file mode 100644 index 00000000..9b750485 --- /dev/null +++ b/agent-feedback/items/2026-08-28-namespaced-attr-split-as-modifier.md @@ -0,0 +1,12 @@ +--- +type: bug +impact: med +effort: low +site: packages/language-tools/src/extractors/script/index.ts › ScriptExtractor#getNamedAttrModifier +--- + +# Split a named attribute at `:` only for bound attrs and the class API, so namespaced names survive into the extracted TS + +`#getNamedAttrModifier` splits every named attribute at its last `:` and `#writeAttrs` then writes only the part before the colon as the object key, although a modifier means something in just two places: the class-API `id:scoped` / `class:no-update`, and bound attributes such as `value:parseInt:=b`. marko-js/marko does the opposite for a non-bound attribute, re-joining `attr.name += ":" + attr.modifier` in `packages/runtime-tags/src/translator/visitors/program/pre-analyze.ts`, and the compiled HTML keeps `xmlns:v=...` and `xml:space=preserve` verbatim, so this split exists only in the extraction and every namespaced attribute collapses to its prefix there. `xml:space` becomes the key `"xml"`, which makes the `"xml:space"` key marko declares in `packages/runtime-tags/tags-html.d.ts` unreachable and turns a plain `` into a TS2353, while `xmlns:v` and `xmlns:o` on an HTML-email root both become `"xmlns"` and report TS1117 ("An object literal cannot have multiple properties with the same name") twice, a message that names the wrong problem and points at the wrong fix. Because the real attribute name never reaches TypeScript, declaration merging on `Marko.HTML.HTML` cannot silence any of it either, so a template carrying namespaced attributes has no user-side workaround short of `// @ts-nocheck`. Gate the split on bound attributes and `#api === RuntimeAPI.class`, matching the translator: `xml:space` then checks clean with no user change, and the xmlns pair becomes one honest TS2353 naming `"xmlns:v"` that a small `Marko.HTML.HTML` augmentation can answer (marko declares no `xmlns:*` key, so an email root still needs that augmentation or an upstream declaration). + +Check: in a project with `@marko/type-check` 3.2.0 and `@marko/language-tools` 2.7.0, write `src/probe/ns.marko` with `` on line 1 and `` on line 2, then run `npx mtc -d condensed`. Today it reports `ns.marko:1:44 - error TS1117`, `ns.marko:1:84 - error TS1117` and `ns.marko:2:6 - error TS2353 Object literal may only specify known properties, and '"xml"' does not exist in type 'Directives & SVG'`; line 2 should report nothing and line 1 at most one TS2353 naming `"xmlns:v"`. Adding a `.d.ts` with `declare global { namespace Marko { namespace HTML { interface HTML { "xmlns:v"?: string; "xmlns:o"?: string } } } }` leaves that output byte-identical, because the key TypeScript sees is `"xmlns"`. diff --git a/agent-feedback/items/2026-08-28-repeated-attr-tag-body-params.md b/agent-feedback/items/2026-08-28-repeated-attr-tag-body-params.md new file mode 100644 index 00000000..95985429 --- /dev/null +++ b/agent-feedback/items/2026-08-28-repeated-attr-tag-body-params.md @@ -0,0 +1,12 @@ +--- +type: bug +impact: high +effort: med +site: packages/language-tools/marko.internal.d.ts › attrTagFor +--- + +# Restore body-parameter types on an attribute tag repeated under one parent + +When the same attribute tag appears more than once under one parent, every body parameter on it loses its contextual type and reports TS7031/TS7006, while a single `<@item|{ n }|>` under the same parent types `n` correctly. `#writeStaticAttrTags` writes a single attribute tag inline into the parent's input object but routes a repeated one through `Marko._.attrTagFor(...)`, whose `AttrTags` constraint recovers the parent input with `Tag extends InputFor`; `InputFor` is itself a conditional type, so TypeScript cannot infer `Input` through it, the check takes its false branch, the constraint degrades to `Record>`, and no contextual signature reaches the body parameter. This is a regression rather than a limitation: the constraint read `Marko.Input extends infer Input` (eager, no inference) and typed these bodies correctly until a857d7fe "fix: attr tag types when any", and restoring the eager form makes the non-generic case clean again. A generic parent loses its body-parameter types the same way, whether `T` is inferred from an attribute or given explicitly as `>`, and there the eager form only improves TS7031 to TS18046, so recovering `T` through `attrTagFor` takes more than reverting that one line. The only workaround is annotating every body parameter at every call site, which defeats the point of the generic; a fix also has to re-record `packages/language-server/src/__tests__/fixtures/script/attr-tags-params`, whose snapshot pins the current `(parameter) data: any` hovers and the three implicit-any diagnostics. + +Check: in a project with `@marko/type-check` 3.2.0, with `tags/a-multi.marko` declaring `export interface Input { item?: Marko.AttrTag<{ title: string; content?: Marko.Body<[{ n: number }]> }> }`, type-check a template holding one `` with a single `<@item|{ n }| title="x">${n.toFixed(1)}` and a second `` with two of them: `npx mtc -p tsconfig.json -d condensed` reports `TS7031 Binding element 'n' implicitly has an 'any' type` on both lines of the repeated pair and nothing on the single one, and should report nothing at all. The generic form is the same shape: with `export interface Input { rows: T[]; column?: Marko.AttrTag<{ header: string; content?: Marko.Body<[{ row: T }]> }> }`, two `<@column|{ row }|>` under one parent report TS7031 on `row` while one does not. diff --git a/agent-feedback/items/2026-08-28-ts-ignore-above-a-tag.md b/agent-feedback/items/2026-08-28-ts-ignore-above-a-tag.md new file mode 100644 index 00000000..2a45d1f6 --- /dev/null +++ b/agent-feedback/items/2026-08-28-ts-ignore-above-a-tag.md @@ -0,0 +1,12 @@ +--- +type: dx +impact: low +effort: low +site: packages/language-tools/src/extractors/script/index.ts › ScriptExtractor#writeTagInputObject +--- + +# Give `// @ts-ignore` above a tag a line it can suppress in the extracted TS + +A `//` comment above a tag can never suppress a TypeScript diagnostic on that tag's attributes, on any of the three paths the extractor has for such a comment. A file-leading `// @ts-` comment is hoisted by `#writeCommentPragmas` to line 1 of the extracted TS, where the next line is `export interface Input {}`; a comment above a tag without params is copied by `#writeTagInputObject` (`if (!tag.params) this.#writeComments(tag)`) onto the `Marko._.renderNativeTag("input")()()(` call line, so the only thing it covers is the `{` that opens the input object while each attribute is written on a later line; and a comment above a tag with params is written by the `tag.params` branch after the whole attribute object, onto the params arrow. Moving the comment inside the tag directly above the offending attribute does not help, because that comment is dropped from the extraction entirely. So `// @ts-expect-error` above a tag reports `TS2578 Unused '@ts-expect-error' directive` at 1:1 and still reports the attribute error, and the only suppression that works is `// @ts-nocheck` on line 1, which turns off checking for the whole file; a template that hits an extractor-side type bug therefore has no narrow escape hatch. Write a tag's leading comments onto the line before its first attribute so a directive covers them, and name the current limitation in `website/docs/reference/typescript.md`, which mentions `// @ts-nocheck` and no other suppression. + +Check: in a project with `@marko/type-check` 3.2.0, write `src/probe/ign1.marko` as the four lines `// @ts-ignore`, ``; `src/probe/ign4.marko` the same with `// @ts-expect-error`; `src/probe/ign6.marko` as ``; and `src/probe/ign9.marko` as `
` / ` // @ts-ignore` / ` ` / ` ${item}` / ` ` / `
`. `npx mtc -d condensed` reports `TS2353 Object literal may only specify known properties, and '"bogus"' does not exist in type 'Directives & Input'` for ign1, ign6 and ign9, and for ign4 both `ign4.marko:1:1 - error TS2578` and the same TS2353; after the fix the directive suppresses the attribute diagnostic and no TS2578 is emitted. diff --git a/agent-feedback/items/2026-08-28-type-libs-resolved-from-cwd.md b/agent-feedback/items/2026-08-28-type-libs-resolved-from-cwd.md new file mode 100644 index 00000000..5c055a1b --- /dev/null +++ b/agent-feedback/items/2026-08-28-type-libs-resolved-from-cwd.md @@ -0,0 +1,12 @@ +--- +type: bug +impact: med +effort: low +site: packages/language-tools/src/processors/marko.ts › markoProcessor.create +--- + +# Resolve the marko/@marko/run type libs and `.marko-run/routes.d.ts` from the tsconfig's directory, not the process cwd + +`markoProcessor.create` is handed `configFile` but derives its root from `host.getCurrentDirectory()`, and passes that root to `Project.getTypeLibs`, which uses it both to resolve the `marko` and `@marko/run` type libs (`getConfig(rootDir)`, with `/_.d.ts` as the resolution anchor) and to look for `/.marko-run/routes.d.ts`; `solutionHost.getParsedCommandLine` in `packages/type-check/src/run.ts` then unions every processor's `getRootNames()` into each project's `fileNames`. Under `mtc` that root is the process cwd rather than the project being checked, so `mtc -p sub/tsconfig.json` run from a `@marko/run` app root force-adds the app's generated `routes.d.ts` into a program that never asked for it, and its `declare module "../src/routes/..."` augmentations drag in every route and every tag they import, producing 26 `src/**` diagnostics (13 TS6059 + 13 TS6307) about files the config does not include. Run the same binary from a directory that has no `node_modules`, such as `mtc -p packages/app/tsconfig.json` from a monorepo root, and `getConfig` instead throws an unhandled `Error: Cannot find module 'marko/package.json'` with a raw stack and no diagnostic. Beyond the noise, the same config file silently checks a different program depending on where it was invoked from, which quietly invalidates negative type tests. Derive the root from `path.dirname(configFile)`, using the `configFile` that `create` already accepts and that the CLI always resolves to an absolute path, keeping `host.getCurrentDirectory()` as the fallback for the language-server caller that passes no `configFile`. + +Check: in a `@marko/run` app on marko 6.3.46 / `@marko/run` 0.11.12 / `@marko/type-check` 3.2.0 that has built once (so `.marko-run/routes.d.ts` exists), add `sub/tsconfig.json` as `{ "extends": "../tsconfig.json", "include": ["./**/*"] }` plus one clean `.marko` file, and delete every `*.tsbuildinfo` before each run. Today `npx mtc -d condensed -p sub/tsconfig.json` from the app root reports 26 `src/**` diagnostics (13 TS6059 `is not under 'rootDir'`, 13 TS6307 `is not listed within the file list of project`) that `(cd sub && npx mtc -d condensed -p tsconfig.json)` does not, and moving `.marko-run/` away makes the app-root run match; from the app's parent directory, `./app/node_modules/.bin/mtc -p app/tsconfig.json` throws `Cannot find module 'marko/package.json'`. After the fix both runs match the one made from the tsconfig's own directory.