# RenderContext



## Source [#source]

`skmtc/deno/core/context/RenderContext.ts`

## Class [#class]

```ts
class RenderContext {
  files: Map<string, FileBase>
  previews: Record<string, Preview>
  basePath: string | undefined
  logger: Logger
  captureCurrentResult: (result: ResultType, stackTrail: StackTrail) => void
  attribution: AttributionState | undefined

  constructor(args: {
    files: Map<string, FileBase>
    previews: Record<string, Preview>
    basePath: string | undefined
    logger: Logger
    captureCurrentResult: ...
    attribution?: AttributionState     // gen-maps emission config
    captureChannel?: CaptureChannel    // shared with GenerateContext
  })

  render(stackTrail: StackTrail): RenderPhaseResult
  collate(stackTrail: StackTrail): FilesRenderResult
  getFile(filePath: string): FileBase
  pick(args: PickArgs): DefinitionBase | undefined
}
```

## Output is unformatted by design [#output-is-unformatted-by-design]

Render does not invoke any formatter. The `renderFile` helper takes
the `content` produced by `file.toString()`, resolves the artifact
path, and returns the metadata-wrapped result without modification:

```ts
const renderFile = ({ content, destinationPath, basePath }: RenderFileArgs): FileObject => {
  const path = toResolvedArtifactPath({ basePath, destinationPath })
  return {
    content,                            // ← unmodified
    path,
    destinationPath,
    lines: content.split('\n').length,
    characters: content.length
  }
}
```

Generated output is **unformatted** by design. Consumers run their
own formatter (Prettier, Biome, `deno fmt`) as a separate step —
typically in a pre-commit hook or build script.

## Constructor [#constructor]

Constructed internally by `toArtifacts`. Not typically instantiated
by generators.

## Properties [#properties]

### `files: Map<string, FileBase>` [#files-mapstring-filebase]

The file map produced by GenerateContext. Keys are unresolved
destination paths (before `basePath` application). Values are the
concrete `FileBase` subclasses — the language code files (`TsFile`),
`JsonFile`, `MarkdownFile`.

### `previews: Record<string, Preview>` [#previews-recordstring-preview]

Per-Projection preview metadata, forwarded from the Generate phase.
Render passes it through unchanged.

### `basePath: string | undefined` [#basepath-string--undefined]

The base path under which generated files land. Pulled from
`client.json#settings.basePath`. Applied by `toResolvedArtifactPath`
to compute final on-disk paths.

### `logger: Logger` [#logger-logger]

Structured logger.

### `captureCurrentResult: (result, stackTrail) => void` [#capturecurrentresult-result-stacktrail--void]

Records per-file render outcomes into the manifest. Render-phase
results are typically all `'success'` because the heavy lifting
happened in Generate.

## Methods [#methods]

### `render(stackTrail: StackTrail): RenderPhaseResult` [#renderstacktrail-stacktrail-renderphaseresult]

The Render-phase entry point. Calls `collate`, packages the result
with `previews`. Returns:

```ts
type RenderResult = {
  artifacts: Record<string, string>      // resolved path → content
  files: Record<string, {                // resolved path → metadata
    destinationPath: string
    lines: number
    characters: number
  }>
  previews: Record<string, Preview>
  results: ResultsItem
}

type RenderPhaseResult = Omit<RenderResult, 'results'> & {
  sidecars?: Record<string, Sidecar>          // attribution runs only
  generationMap?: GenerationMapEntry[]        // attribution runs only
}
```

(The omitted `results` field comes from the manifest layer above.
`sidecars` / `generationMap` appear only when the run configured
`attribution.postPass`.)

### `collate(stackTrail: StackTrail): FilesRenderResult` [#collatestacktrail-stacktrail-filesrenderresult]

The core serialization loop. Iterates `files`, calls
`file.toString()` on each, computes line/character metadata,
resolves paths against `basePath`.

```ts
type FilesRenderResult = {
  artifacts: Record<string, string>
  files: Record<string, FileMetadata>
}
```

Each file is stringified independently. There's no cross-file
operation in Render — by the time Render runs, every file's content
is fully determined by `file.toString()`.

### `getFile(filePath: string): FileBase` [#getfilefilepath-string-filebase]

Look up a file by its (normalized) destination path. Throws if the
file isn't in the map.

```ts
const file = renderContext.getFile('./src/types/User.ts')
console.log(file.toString())  // the rendered content
```

### `pick({ name, exportPath }: PickArgs): DefinitionBase | undefined` [#pick-name-exportpath--pickargs-definitionbase--undefined]

Look up a specific Definition by name within a file. Convenience
for inspecting generated content from outside the standard flow.

```ts
const userDef = renderContext.pick({
  name: 'User',
  exportPath: './src/models/User.ts'
})
```

## Examples [#examples]

### What Render actually does (conceptually) [#what-render-actually-does-conceptually]

```ts
// Pseudocode of the core Render loop
for (const [path, file] of files) {
  const content = file.toString()  // ← imports + reExports + definitions joined
  const resolvedPath = join(basePath, path)
  artifacts[resolvedPath] = content
}
```

That's the entire transformation. No analysis, no per-file
optimization, no formatting.

### `file.toString()` shape [#filetostring-shape]

A `File.toString()` produces the assembled file content:

```ts
[reExports, imports, definitions]
  .filter(section => section.length > 0)
  .map(section => section.join('\n'))
  .join('\n\n')
```

Three sections separated by blank lines, in order: re-exports, then
imports, then definitions. Each section is empty-skipped if it has
no entries.

## Common questions [#common-questions]

### Why doesn't Render run Prettier? [#why-doesnt-render-run-prettier]

Two reasons:

1. **Format is the consumer's concern.** SKMTC produces syntactically
   valid TypeScript; aesthetic choices (semis, trailing commas,
   quote style) belong to the consumer's existing tooling.
2. **Coupling to a specific formatter would create friction.**
   Different consumer projects use different formatters (Prettier,
   Biome, dprint, custom). Picking one in SKMTC would force every
   consumer to either accept SKMTC's choice or post-process anyway.

The pragmatic stance is "produce valid, let the consumer format."

### Can I add formatting back into the pipeline? [#can-i-add-formatting-back-into-the-pipeline]

You could — call Prettier in the host after `writeGeneratedFiles`.
The CLI doesn't do this, but a consumer running the engine
programmatically could. The cleanest pattern is a pre-commit hook
on the generated output directory, which runs Prettier independent
of SKMTC.

### Why is Render a separate phase if it's so simple? [#why-is-render-a-separate-phase-if-its-so-simple]

For the same reason Parse and Generate are separate: the phase
boundary creates an invariant. Once Generate finishes, the file
map is the source of truth — no further mutation. Render reads;
Render doesn't transform.

This makes the manifest's structure consistent (every file in the
output was in the map at the end of Generate). It also makes
debugging easier — you can inspect the file map after Generate and
predict what Render will produce.

### Can I inject content during Render? [#can-i-inject-content-during-render]

No — by design. If you want to inject content, do it during
Generate via `register({ ... })` calls. Render reads the file map
and serializes; it doesn't accept new contributions.

### How does `JsonFile` differ from the code files? [#how-does-jsonfile-differ-from-the-code-files]

`JsonFile` is used for JSON output. Its `toString()` returns
`JSON.stringify(content, null, 2)` rather than the
`reExports + imports + definitions` join. Generators register JSON
content via `registerJson` rather than `register`.

Code paths (`.ts`, `.tsx`) hold the language's concrete code file
(`TsFile` from `@skmtc/lang-typescript`), created by the lang
package's `register` function on first write.

## Related types [#related-types]

```ts
type RenderFileArgs = {
  content: string
  destinationPath: string
  basePath?: string
}

type FileObject = {
  content: string
  path: string
  destinationPath: string
  lines: number
  characters: number
}

type PickArgs = {
  name: string
  exportPath: string
}
```

## See also [#see-also]

* [The three phases concept](/docs/concepts/the-three-phases) — Render in context
* [API: GenerateContext](/docs/reference/api/generate-context) — what produces the file map Render serializes
* [API: ParseContext](/docs/reference/api/parse-context) — what runs before Generate
* [API: SnippetBase](/docs/reference/api/dsl-snippet-base) — the `toString()` contract Render relies on
* [API: Definition](/docs/reference/api/dsl-definition) — what `File.definitions` holds
* [Design philosophy](/docs/explanation/design-philosophy) — the "format is consumer's concern" principle
