Skip to content

fix(schema): decorator qualified_name is the import-table resolution or absent - #152

Merged
rahlk merged 1 commit into
mainfrom
fix/issue-151-decorator-qualified-name
Sep 6, 2026
Merged

fix(schema): decorator qualified_name is the import-table resolution or absent#152
rahlk merged 1 commit into
mainfrom
fix/issue-151-decorator-qualified-name

Conversation

@rahlk

@rahlk rahlk commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Closes #151. Unit 0 of the entrypoint spec (#150), ahead of the decorator matcher.

TSDecorator.qualified_name was documented as "checker-resolved FQN when available". It was ts-morph's
Decorator.getFullName() — the written expression text — and the builder never consulted the checker.
Measured with import { Get as HttpGet } from "./decorators", where resolution is trivial: the field
emitted HttpGet, the alias as typed.

What changes

qualified_name is now the import-table resolution of the written spelling, or absent when the
decorator's head is not an imported binding. Python's rule — "Jedi, else the import table, else None" —
minus the Jedi tier TypeScript does not have. name is the spelling as written.

decorator name before → after qualified_name before → after
@Get from @nestjs/common GetGet Get@nestjs/common.Get
@HttpGet via import { Get as HttpGet } HttpGetHttpGet HttpGet<module>.Get
@http.route via import * as http routehttp.route http.route<module>.route
@Dflt.deco via import Dflt from decoDflt.deco Dflt.deco<module>.default.deco
@Local declared in the same file LocalLocal Localabsent

Why name changes too. The full spelling used to survive only in qualified_name. Once that is
resolved-or-absent, an unresolved dotted decorator would lose its spelling from the output entirely.
Python's name is the written spelling for the same reason, and the entrypoint pass's unresolved counter
needs it.

Effect on #143. Its :TSDecorator node merges on qualified_name || name. Before this, a project's
local @Get and NestJS's @Get collapsed into one node — the opposite of the "two spellings, one
decorator" behaviour the PR described. Now imported decorators merge on a package-qualified key and
same-file ones on their written spelling.

The resolver lives in its own module (importResolver.ts) because the entrypoint pass needs the same
table for base classes and the unresolved counter.

Verification

  • bun test — 242 pass, 0 fail (3 new): the import table's mapping, all five rows above end-to-end,
    and the absent case asserted as key not present, not undefined
  • Mutation-checked — reverting to getFullName() fails 2 of the 3 new tests
  • Container tests run — 4 pass against live Neo4j
  • bun run typecheck clean

Not a breaking change, but a visible one

The field was documented as resolved and never was, so this is a fix. But anything that matched on the
old bare qualified_name sees different values, and dotted decorators' name gains its prefix. Both
are called out here rather than left for a consumer to discover.

…or absent

`TSDecorator.qualified_name` was documented as "checker-resolved FQN when available".
It was ts-morph's `Decorator.getFullName()` -- the written expression text -- and the
builder never consulted the checker. Measured with `import { Get as HttpGet }`, where
resolution is trivial: the field emitted `HttpGet`, the alias as typed.

Now `qualified_name` is the import-table resolution of the written spelling, or ABSENT
when the decorator's head is not an imported binding -- python's rule ("Jedi, else the
import table, else None") minus the Jedi tier TypeScript does not have. Aliases map
back to the exported name; module specifiers are kept verbatim, which is the spelling
a rule names.

`name` becomes the spelling as WRITTEN (`http.route`, not `route`). The full spelling
used to survive only in `qualified_name`; once that is resolved-or-absent, an unresolved
dotted decorator would otherwise lose it from the output entirely. Python's `name` is
the written spelling for the same reason.

Effect on #143's Neo4j merge (`qualified_name || name`): imported decorators now merge
on a package-qualified key, same-file ones on their written spelling. That is what
the PR described and the opposite of what it did -- a local `@Get` and NestJS's `@Get`
collapsed into one node.

The resolver is its own module because the entrypoint pass (#72 units 2-3) needs the
same import table for base classes and the unresolved counter.
@rahlk
rahlk merged commit 8bc792b into main Sep 6, 2026
1 check passed
@rahlk
rahlk deleted the fix/issue-151-decorator-qualified-name branch September 6, 2026 00:53
@rahlk rahlk mentioned this pull request Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(schema): decorator qualified_name is the import-table resolution or absent; name is the written spelling

1 participant