ReviewOS

stacks/stx

typecheck: a relative import in a script block never resolves, though a tsconfig path alias to the same file does

#1928
Closed glennmichael123 opened this 14 days ago · 1 comment
14 days ago

A relative import in a script block does not resolve, while a tsconfig path alias to the same file does. The specifier is correct and the target exists.

Repro

stxrepro/target.ts
stxrepro/deep/rel.stx

target.ts:

export const THING = 'value'

deep/rel.stx:

<script server>
import { THING } from '../target'
const label = THING
</script>
<template><p>{{ label }}</p></template>
$ stx typecheck "stxrepro/deep/rel.stx"
rel.stx:2:23  error TS2307  Cannot find module '../target' or its corresponding type declarations.  [<script server>]

The alias form works

Same file, same target, reached through paths:

<script server>
import { FEATURES } from '~/app/Support/Marketing/catalog'
</script>
  Checked 1 script block(s) and 1 template expression(s) in 1 file(s) — 0 error(s), 0 warning(s)

So readProjectPathAliases is doing its job and module resolution is otherwise healthy. It is specifically the relative specifier that fails — consistent with the virtual program resolving it against the temp directory the generated tsconfig lives in, rather than against the real directory of the .stx file.

Where it shows up

Two marketing components import a shared catalog the ordinary way:

import { FEATURES, USE_CASES } from '../../../app/Support/Marketing/catalog'

The path is correct, the file is 13 KB of real exports, and the page renders it fine. It is only the checker that cannot follow it — the last 2 errors in a corpus otherwise down from 432 to 45, and the only ones with no app-side fix that is not a workaround.

Why it matters beyond two files

A relative import is the first thing anyone writes, and it is what every editor's auto-import produces. An app that hits this either rewrites correct imports to aliases to appease the checker, or mutes the checker. Both are worse than the import.

Suggested fix

Resolve module specifiers against the original file's directory. If the virtual file keeps living in a temp directory, paths alone will not cover the relative case — mapping the virtual file back to its real path for resolution, or adding a rootDirs/baseUrl entry pointing at the source directory, would.

Worth a test alongside the paths one that already exists, since the two travel together and only one is covered.

14 days ago

Fixed in d3527dd.

Relative specifiers in script blocks are now rewritten to absolute paths against the .stx file's own directory before the virtual buffer is written, so ../target resolves to where you wrote it rather than to .stx/typecheck/../target.

Your read of the cause was right, and the alias case was the tell: paths entries go through the absolute baseUrl the checker already emits, which is why only the relative form fell through.

Two notes on the fix:

  • Not rootDirs. It is the built-in answer for a virtual layout, but it merges its entries into one namespace — with two checked pages in different directories, ./util from one could silently resolve to the other's util. Rewriting per buffer cannot cross-talk, since each buffer has exactly one origin.
  • Textual, not scanImports. Bun's parser-based scan erases import type, which is the form that matters most here. The rewrite adds no lines, so diagnostics still land on the authored line.

The template buffer needed the same treatment, and that half is the one that hides. Its own TS2307 is dropped as a duplicate of the per-block file's, so the unresolved import was invisible there — while making the imported types any, which quietly turned every template expression reading one back into unchecked text. test/typecheck/relative-import.test.ts pins that alongside the alias case, and asserts the module resolves to the real declarations rather than to any.

Sign in to comment on this issue.