🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@prisma-next/ts-render

Package Overview
Dependencies
Maintainers
4
Versions
522
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@prisma-next/ts-render - npm Package Compare versions

Comparing version
0.16.0-dev.4
to
0.16.0-dev.5
+20
-15
dist/index.d.mts

@@ -79,15 +79,19 @@ //#region src/json-to-ts-source.d.ts

*
* - **One line per module specifier.** Named imports are aggregated and
* emitted sorted; a single default symbol is combined onto the same line
* when attributes agree (`import def, { a, b } from "m";`). Aliased symbols
* render `symbol as alias`. When every symbol for a module is `typeOnly`,
* the statement collapses to `import type { … }`; a module mixing value
* and type symbols prefixes the type-only ones (`import { type T, v }`).
* Exception: a fully type-only statement that has both a default and one or
* more named bindings splits to two lines (`import type D from "m";` then
* `import type { N } from "m";`) because TypeScript rejects
* `import type D, { N } from "m"` (TS1363).
* - **At most one default symbol per module.** Two conflicting default
* symbols on the same specifier throw — the user's renderer can't
* guess which one they meant.
* - **Usually one line per module specifier.** Named imports are aggregated
* and emitted sorted; a single default symbol is combined onto the same
* line when attributes agree (`import def, { a, b } from "m";`). Aliased
* symbols render `symbol as alias`. When every symbol for a module is
* `typeOnly`, the statement collapses to `import type { … }`; a module
* mixing value and type symbols prefixes the type-only ones
* (`import { type T, v }`). Exceptions that split into multiple lines: a
* fully type-only statement with both a default and one or more named
* bindings (`import type D from "m";` then `import type { N } from "m";`,
* because TypeScript rejects `import type D, { N } from "m"` — TS1363),
* and multiple distinct default symbols (see below).
* - **Multiple distinct default symbols per module are allowed.** JS permits
* re-importing the same specifier under different default-binding names
* (`import a from 'm'; import b from 'm';`), so each distinct default
* symbol renders its own `import` line, sorted alphabetically; a repeated
* requirement for the same symbol still collapses into one binding,
* merging `typeOnly` by AND.
* - **Attribute unanimity per module.** All requirements for the same

@@ -109,4 +113,5 @@ * module specifier must carry the same (or no) `attributes` map.

*
* Returns a string containing one import line per module, joined by `\n`
* (no trailing newline). An empty requirement list returns `""`.
* Returns a string containing one or more import lines per module (see the
* splitting exceptions above), joined by `\n` (no trailing newline). An
* empty requirement list returns `""`.
*/

@@ -113,0 +118,0 @@ declare function renderImports(requirements: readonly ImportRequirement[]): string;

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.mts","names":[],"sources":["../src/json-to-ts-source.ts","../src/ts-expression.ts","../src/render-imports.ts"],"mappings":";;;;;;;;;;;;;;;;;KAiBY,wDAAwD,cAAc;KACtE;YAAyB,cAAc;;;;;;;;;;iBAUnC,eAAe;;;;;;;;;;;;;;;;;;;;UCXd;WACN;WACA;WACA;WACA,aAAa,SAAS;WACtB;WACA;;;;;;;;;;uBAWW;WACX;WACA,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCK1B,cAAc,uBAAuB"}
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/json-to-ts-source.ts","../src/ts-expression.ts","../src/render-imports.ts"],"mappings":";;;;;;;;;;;;;;;;;KAiBY,wDAAwD,cAAc;KACtE;YAAyB,cAAc;;;;;;;;;;iBAUnC,eAAe;;;;;;;;;;;;;;;;;;;;UCXd;WACN;WACA;WACA;WACA,aAAa,SAAS;WACtB;WACA;;;;;;;;;;uBAWW;WACX;WACA,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCU1B,cAAc,uBAAuB"}

@@ -13,3 +13,3 @@ //#region src/json-to-ts-source.ts

if (value === null) return "null";
if (typeof value === "string") return JSON.stringify(value);
if (typeof value === "string") return stringifyLiteral(value);
if (typeof value === "number" || typeof value === "boolean") return String(value);

@@ -34,5 +34,8 @@ if (Array.isArray(value)) {

function renderKey(key) {
if (key === "__proto__") return JSON.stringify(key);
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
if (key === "__proto__") return stringifyLiteral(key);
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : stringifyLiteral(key);
}
function stringifyLiteral(value) {
return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
}
//#endregion

@@ -47,15 +50,19 @@ //#region src/render-imports.ts

*
* - **One line per module specifier.** Named imports are aggregated and
* emitted sorted; a single default symbol is combined onto the same line
* when attributes agree (`import def, { a, b } from "m";`). Aliased symbols
* render `symbol as alias`. When every symbol for a module is `typeOnly`,
* the statement collapses to `import type { … }`; a module mixing value
* and type symbols prefixes the type-only ones (`import { type T, v }`).
* Exception: a fully type-only statement that has both a default and one or
* more named bindings splits to two lines (`import type D from "m";` then
* `import type { N } from "m";`) because TypeScript rejects
* `import type D, { N } from "m"` (TS1363).
* - **At most one default symbol per module.** Two conflicting default
* symbols on the same specifier throw — the user's renderer can't
* guess which one they meant.
* - **Usually one line per module specifier.** Named imports are aggregated
* and emitted sorted; a single default symbol is combined onto the same
* line when attributes agree (`import def, { a, b } from "m";`). Aliased
* symbols render `symbol as alias`. When every symbol for a module is
* `typeOnly`, the statement collapses to `import type { … }`; a module
* mixing value and type symbols prefixes the type-only ones
* (`import { type T, v }`). Exceptions that split into multiple lines: a
* fully type-only statement with both a default and one or more named
* bindings (`import type D from "m";` then `import type { N } from "m";`,
* because TypeScript rejects `import type D, { N } from "m"` — TS1363),
* and multiple distinct default symbols (see below).
* - **Multiple distinct default symbols per module are allowed.** JS permits
* re-importing the same specifier under different default-binding names
* (`import a from 'm'; import b from 'm';`), so each distinct default
* symbol renders its own `import` line, sorted alphabetically; a repeated
* requirement for the same symbol still collapses into one binding,
* merging `typeOnly` by AND.
* - **Attribute unanimity per module.** All requirements for the same

@@ -77,4 +84,5 @@ * module specifier must carry the same (or no) `attributes` map.

*
* Returns a string containing one import line per module, joined by `\n`
* (no trailing newline). An empty requirement list returns `""`.
* Returns a string containing one or more import lines per module (see the
* splitting exceptions above), joined by `\n` (no trailing newline). An
* empty requirement list returns `""`.
*/

@@ -91,4 +99,3 @@ function renderImports(requirements) {

named: /* @__PURE__ */ new Map(),
defaultSymbol: null,
defaultTypeOnly: true,
defaults: /* @__PURE__ */ new Map(),
attributes: null,

@@ -107,5 +114,4 @@ attributesSet: false

if (kind === "default") {
if (group.defaultSymbol !== null && group.defaultSymbol !== req.symbol) throw new Error(`Conflicting default imports for module "${req.moduleSpecifier}": "${group.defaultSymbol}" and "${req.symbol}". Only one default symbol is allowed per module.`);
group.defaultSymbol = req.symbol;
group.defaultTypeOnly = group.defaultTypeOnly && typeOnly;
const existingTypeOnly = group.defaults.get(req.symbol);
group.defaults.set(req.symbol, existingTypeOnly === void 0 ? typeOnly : existingTypeOnly && typeOnly);
} else {

@@ -151,23 +157,34 @@ const alias = req.alias && req.alias !== req.symbol ? req.alias : null;

function renderModuleImport(moduleSpecifier, group) {
const typeOnlyStatement = isStatementTypeOnly(group);
const attrs = buildAttributesClause(group.attributes);
const hasDefault = group.defaultSymbol !== null;
const defaultEntries = [...group.defaults.entries()].sort(([a], [b]) => a.localeCompare(b));
const hasNamed = group.named.size > 0;
if (typeOnlyStatement && hasDefault && hasNamed) return `${`import type ${group.defaultSymbol} from '${moduleSpecifier}'${attrs};`}\n${`import type { ${renderNamedBindingsList(group, true)} } from '${moduleSpecifier}'${attrs};`}`;
return `${typeOnlyStatement ? "import type" : "import"} ${buildImportClause(group, typeOnlyStatement)} from '${moduleSpecifier}'${attrs};`;
if (defaultEntries.length > 1) {
const defaultLines = defaultEntries.map(([symbol, typeOnly]) => `import ${typeOnly ? "type " : ""}${symbol} from '${moduleSpecifier}'${attrs};`);
if (!hasNamed) return defaultLines.join("\n");
return [...defaultLines, renderNamedOnlyStatement(moduleSpecifier, group, attrs)].join("\n");
}
const defaultEntry = defaultEntries[0];
const hasDefault = defaultEntry !== void 0;
const [defaultSymbol, defaultTypeOnly] = defaultEntry ?? [null, true];
const typeOnlyStatement = isStatementTypeOnly(hasDefault, defaultTypeOnly, group);
if (typeOnlyStatement && hasDefault && hasNamed) return `${`import type ${defaultSymbol} from '${moduleSpecifier}'${attrs};`}\n${`import type { ${renderNamedBindingsList(group, true)} } from '${moduleSpecifier}'${attrs};`}`;
return `${typeOnlyStatement ? "import type" : "import"} ${buildImportClause(defaultSymbol, group, typeOnlyStatement)} from '${moduleSpecifier}'${attrs};`;
}
function isStatementTypeOnly(group) {
const hasDefault = group.defaultSymbol !== null;
function renderNamedOnlyStatement(moduleSpecifier, group, attrs) {
const typeOnlyStatement = [...group.named.values()].every((binding) => binding.typeOnly);
return `${typeOnlyStatement ? "import type" : "import"} { ${renderNamedBindingsList(group, typeOnlyStatement)} } from '${moduleSpecifier}'${attrs};`;
}
function isStatementTypeOnly(hasDefault, defaultTypeOnly, group) {
const hasNamed = group.named.size > 0;
if (!hasDefault && !hasNamed) return false;
if (hasDefault && !group.defaultTypeOnly) return false;
if (hasDefault && !defaultTypeOnly) return false;
for (const binding of group.named.values()) if (!binding.typeOnly) return false;
return true;
}
function buildImportClause(group, statementTypeOnly) {
function buildImportClause(defaultSymbol, group, statementTypeOnly) {
const hasNamed = group.named.size > 0;
const hasDefault = group.defaultSymbol !== null;
const hasDefault = defaultSymbol !== null;
const namedClause = hasNamed ? renderNamedBindingsList(group, statementTypeOnly) : "";
if (hasDefault && hasNamed) return `${group.defaultSymbol}, { ${namedClause} }`;
if (hasDefault) return group.defaultSymbol;
if (hasDefault && hasNamed) return `${defaultSymbol}, { ${namedClause} }`;
if (hasDefault) return defaultSymbol;
return `{ ${namedClause} }`;

@@ -174,0 +191,0 @@ }

@@ -1,1 +0,1 @@

{"version":3,"file":"index.mjs","names":[],"sources":["../src/json-to-ts-source.ts","../src/render-imports.ts","../src/ts-expression.ts"],"sourcesContent":["/**\n * Pure JSON-to-TypeScript-source printer.\n *\n * This module is the second stage of the codec → TS pipeline:\n *\n * jsValue → codec.encodeJson → JsonValue → jsonToTsSource → TS source text\n *\n * Stage 1 (`codec.encodeJson`) is a codec responsibility — date serialization,\n * opaque domain types (vector, bigint, uuid), JSON canonicalization. Stage 2\n * (this module) is a pure JSON-to-TS printer that must never grow type-specific\n * branches.\n *\n * To render a non-JSON JS value (Date, Vector, BigInt, Buffer, …), encode it\n * through the relevant codec's `encodeJson` first. Adding special cases to\n * this file is not the answer — that's what codecs are for.\n */\n\nexport type JsonValue = string | number | boolean | null | readonly JsonValue[] | JsonObject;\nexport type JsonObject = { readonly [key: string]: JsonValue | undefined };\n\n/**\n * Render a JSON-compatible value as a TypeScript source-text literal.\n *\n * Accepts `unknown` for ergonomics with structural types (e.g. `ColumnSpec`,\n * `ForeignKeySpec`) whose fields are all JSON-compatible but whose interfaces\n * lack the index signature TypeScript requires for `JsonObject` assignability.\n * Non-JSON values (Date, Symbol, Function, etc.) throw at runtime.\n */\nexport function jsonToTsSource(value: unknown): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n const items = value.map((v: unknown) => jsonToTsSource(v));\n const singleLine = `[${items.join(', ')}]`;\n if (singleLine.length <= 80) return singleLine;\n return `[\\n${items.map((i) => ` ${i}`).join(',\\n')},\\n]`;\n }\n if (typeof value === 'object') {\n const entries = Object.entries(value).filter(([, v]) => v !== undefined);\n if (entries.length === 0) return '{}';\n const items = entries.map(([k, v]) => `${renderKey(k)}: ${jsonToTsSource(v)}`);\n const singleLine = `{ ${items.join(', ')} }`;\n if (singleLine.length <= 80) return singleLine;\n return `{\\n${items.map((i) => ` ${i}`).join(',\\n')},\\n}`;\n }\n throw new Error(`jsonToTsSource: unsupported value type \"${typeof value}\"`);\n}\n\nfunction renderKey(key: string): string {\n if (key === '__proto__') return JSON.stringify(key);\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);\n}\n","import type { ImportRequirement } from './ts-expression';\n\n/**\n * Render an aggregated `import` block from a flat list of\n * `ImportRequirement`s. Each target's migration renderer collects\n * requirements polymorphically from its call nodes and pipes them here.\n *\n * The emitter invariants:\n *\n * - **One line per module specifier.** Named imports are aggregated and\n * emitted sorted; a single default symbol is combined onto the same line\n * when attributes agree (`import def, { a, b } from \"m\";`). Aliased symbols\n * render `symbol as alias`. When every symbol for a module is `typeOnly`,\n * the statement collapses to `import type { … }`; a module mixing value\n * and type symbols prefixes the type-only ones (`import { type T, v }`).\n * Exception: a fully type-only statement that has both a default and one or\n * more named bindings splits to two lines (`import type D from \"m\";` then\n * `import type { N } from \"m\";`) because TypeScript rejects\n * `import type D, { N } from \"m\"` (TS1363).\n * - **At most one default symbol per module.** Two conflicting default\n * symbols on the same specifier throw — the user's renderer can't\n * guess which one they meant.\n * - **Attribute unanimity per module.** All requirements for the same\n * module specifier must carry the same (or no) `attributes` map.\n * Divergent attribute maps throw — they can't collapse to one line\n * and there's no user-resolvable recovery at this layer.\n * - **Distinct (symbol, alias) pairs are distinct bindings.** TypeScript\n * permits importing the same export under multiple local names, so\n * `{ A }` + `{ A as B }` renders as `import { A, A as B } from \"m\"` and\n * `{ A as B }` + `{ A as C }` renders as `import { A as B, A as C } from \"m\"`.\n * Truly identical `(symbol, alias)` pairs still collapse to one binding,\n * merging `typeOnly` by AND.\n * - **Deterministic ordering.** Modules are emitted sorted by specifier;\n * within a module, named bindings are emitted sorted by `(symbol, alias)`\n * using JavaScript code-unit comparison, with the un-aliased form (no\n * alias) treated as alias `\"\"` so it sorts before any aliased form of the\n * same symbol.\n *\n * Returns a string containing one import line per module, joined by `\\n`\n * (no trailing newline). An empty requirement list returns `\"\"`.\n */\nexport function renderImports(requirements: readonly ImportRequirement[]): string {\n const byModule = aggregateByModule(requirements);\n const entries = [...byModule.entries()].sort(([a], [b]) => a.localeCompare(b));\n return entries\n .map(([moduleSpecifier, group]) => renderModuleImport(moduleSpecifier, group))\n .join('\\n');\n}\n\ninterface NamedBinding {\n symbol: string;\n alias: string | null;\n typeOnly: boolean;\n}\n\ninterface ModuleImportGroup {\n readonly named: Map<string, NamedBinding>;\n defaultSymbol: string | null;\n defaultTypeOnly: boolean;\n attributes: Readonly<Record<string, string>> | null;\n attributesSet: boolean;\n}\n\nfunction aggregateByModule(\n requirements: readonly ImportRequirement[],\n): Map<string, ModuleImportGroup> {\n const byModule = new Map<string, ModuleImportGroup>();\n for (const req of requirements) {\n let group = byModule.get(req.moduleSpecifier);\n if (!group) {\n group = {\n named: new Map(),\n defaultSymbol: null,\n defaultTypeOnly: true,\n attributes: null,\n attributesSet: false,\n };\n byModule.set(req.moduleSpecifier, group);\n }\n mergeRequirementIntoGroup(req, group);\n }\n return byModule;\n}\n\nfunction mergeRequirementIntoGroup(req: ImportRequirement, group: ModuleImportGroup): void {\n const kind = req.kind ?? 'named';\n const typeOnly = req.typeOnly === true;\n if (kind === 'default') {\n if (group.defaultSymbol !== null && group.defaultSymbol !== req.symbol) {\n throw new Error(\n `Conflicting default imports for module \"${req.moduleSpecifier}\": ` +\n `\"${group.defaultSymbol}\" and \"${req.symbol}\". Only one default symbol is allowed per module.`,\n );\n }\n group.defaultSymbol = req.symbol;\n group.defaultTypeOnly = group.defaultTypeOnly && typeOnly;\n } else {\n const alias = req.alias && req.alias !== req.symbol ? req.alias : null;\n const key = namedBindingKey(req.symbol, alias);\n const existing = group.named.get(key);\n if (existing) {\n existing.typeOnly = existing.typeOnly && typeOnly;\n } else {\n group.named.set(key, { symbol: req.symbol, alias, typeOnly });\n }\n }\n mergeAttributes(req, group);\n}\n\nfunction mergeAttributes(req: ImportRequirement, group: ModuleImportGroup): void {\n const incoming = req.attributes ?? null;\n if (!group.attributesSet) {\n group.attributes = incoming;\n group.attributesSet = true;\n return;\n }\n if (!attributesEqual(group.attributes, incoming)) {\n throw new Error(\n `Conflicting import attributes for module \"${req.moduleSpecifier}\": ` +\n `${stringifyAttributes(group.attributes)} vs ${stringifyAttributes(incoming)}.`,\n );\n }\n}\n\nfunction attributesEqual(\n a: Readonly<Record<string, string>> | null,\n b: Readonly<Record<string, string>> | null,\n): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n const aKeys = Object.keys(a).sort();\n const bKeys = Object.keys(b).sort();\n if (aKeys.length !== bKeys.length) return false;\n for (let i = 0; i < aKeys.length; i++) {\n const key = aKeys[i];\n if (key !== bKeys[i]) return false;\n if (a[key as string] !== b[key as string]) return false;\n }\n return true;\n}\n\nfunction stringifyAttributes(attrs: Readonly<Record<string, string>> | null): string {\n if (attrs === null) return '(none)';\n const entries = Object.entries(attrs)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}: ${JSON.stringify(v)}`);\n return `{ ${entries.join(', ')} }`;\n}\n\nfunction renderModuleImport(moduleSpecifier: string, group: ModuleImportGroup): string {\n const typeOnlyStatement = isStatementTypeOnly(group);\n const attrs = buildAttributesClause(group.attributes);\n const hasDefault = group.defaultSymbol !== null;\n const hasNamed = group.named.size > 0;\n if (typeOnlyStatement && hasDefault && hasNamed) {\n const defaultLine = `import type ${group.defaultSymbol} from '${moduleSpecifier}'${attrs};`;\n const namedClause = renderNamedBindingsList(group, true);\n const namedLine = `import type { ${namedClause} } from '${moduleSpecifier}'${attrs};`;\n return `${defaultLine}\\n${namedLine}`;\n }\n const keyword = typeOnlyStatement ? 'import type' : 'import';\n const clause = buildImportClause(group, typeOnlyStatement);\n return `${keyword} ${clause} from '${moduleSpecifier}'${attrs};`;\n}\n\nfunction isStatementTypeOnly(group: ModuleImportGroup): boolean {\n const hasDefault = group.defaultSymbol !== null;\n const hasNamed = group.named.size > 0;\n if (!hasDefault && !hasNamed) return false;\n if (hasDefault && !group.defaultTypeOnly) return false;\n for (const binding of group.named.values()) {\n if (!binding.typeOnly) return false;\n }\n return true;\n}\n\nfunction buildImportClause(group: ModuleImportGroup, statementTypeOnly: boolean): string {\n const hasNamed = group.named.size > 0;\n const hasDefault = group.defaultSymbol !== null;\n const namedClause = hasNamed ? renderNamedBindingsList(group, statementTypeOnly) : '';\n if (hasDefault && hasNamed) {\n return `${group.defaultSymbol}, { ${namedClause} }`;\n }\n if (hasDefault) {\n return group.defaultSymbol as string;\n }\n return `{ ${namedClause} }`;\n}\n\nfunction renderNamedBindingsList(group: ModuleImportGroup, statementTypeOnly: boolean): string {\n return [...group.named.values()]\n .sort(compareNamedBindings)\n .map((binding) => renderNamedBinding(binding, statementTypeOnly))\n .join(', ');\n}\n\nfunction compareNamedBindings(a: NamedBinding, b: NamedBinding): number {\n if (a.symbol !== b.symbol) return a.symbol < b.symbol ? -1 : 1;\n const aAlias = a.alias ?? '';\n const bAlias = b.alias ?? '';\n if (aAlias === bAlias) return 0;\n return aAlias < bAlias ? -1 : 1;\n}\n\nfunction namedBindingKey(symbol: string, alias: string | null): string {\n return `${symbol}\\x00${alias ?? ''}`;\n}\n\nfunction renderNamedBinding(binding: NamedBinding, statementTypeOnly: boolean): string {\n const prefix = !statementTypeOnly && binding.typeOnly ? 'type ' : '';\n const aliasClause = binding.alias !== null ? ` as ${binding.alias}` : '';\n return `${prefix}${binding.symbol}${aliasClause}`;\n}\n\nfunction buildAttributesClause(attrs: Readonly<Record<string, string>> | null): string {\n if (attrs === null) return '';\n const entries = Object.entries(attrs)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}: ${JSON.stringify(v)}`);\n if (entries.length === 0) return '';\n return ` with { ${entries.join(', ')} }`;\n}\n","/**\n * Declarative contribution to the `import` block of a rendered TypeScript\n * source file. Each node in an IR declares which symbols it needs from which\n * modules; the top-level renderer deduplicates across nodes and emits one\n * `import { a, b, c } from \"…\"` line per module.\n *\n * `kind` defaults to `\"named\"` (e.g. `import { a } from \"m\"`). Setting it to\n * `\"default\"` emits `import a from \"m\"`. `attributes`, if provided, emits an\n * import attributes clause (`with { type: \"json\" }`) verbatim — required for\n * JSON module imports in the rendered scaffolds.\n *\n * `alias`, when present and different from `symbol`, renders `symbol as alias`.\n * `typeOnly` marks the symbol as a type import: when every symbol contributed\n * for a module is `typeOnly`, the whole statement collapses to\n * `import type { … } from \"m\"`; when a module mixes value and type symbols, the\n * type-only ones carry a per-specifier `type` prefix (`import { type T, val }`).\n */\nexport interface ImportRequirement {\n readonly moduleSpecifier: string;\n readonly symbol: string;\n readonly kind?: 'named' | 'default';\n readonly attributes?: Readonly<Record<string, string>>;\n readonly alias?: string;\n readonly typeOnly?: boolean;\n}\n\n/**\n * Abstract base class for any IR node that can be emitted as a TypeScript\n * expression and declare its own import requirements.\n *\n * A top-level renderer walks an array of these polymorphically, concatenates\n * `renderTypeScript()` results, and aggregates `importRequirements()` into a\n * deduplicated import block.\n */\nexport abstract class TsExpression {\n abstract renderTypeScript(): string;\n abstract importRequirements(): readonly ImportRequirement[];\n}\n"],"mappings":"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"}
{"version":3,"file":"index.mjs","names":[],"sources":["../src/json-to-ts-source.ts","../src/render-imports.ts","../src/ts-expression.ts"],"sourcesContent":["/**\n * Pure JSON-to-TypeScript-source printer.\n *\n * This module is the second stage of the codec → TS pipeline:\n *\n * jsValue → codec.encodeJson → JsonValue → jsonToTsSource → TS source text\n *\n * Stage 1 (`codec.encodeJson`) is a codec responsibility — date serialization,\n * opaque domain types (vector, bigint, uuid), JSON canonicalization. Stage 2\n * (this module) is a pure JSON-to-TS printer that must never grow type-specific\n * branches.\n *\n * To render a non-JSON JS value (Date, Vector, BigInt, Buffer, …), encode it\n * through the relevant codec's `encodeJson` first. Adding special cases to\n * this file is not the answer — that's what codecs are for.\n */\n\nexport type JsonValue = string | number | boolean | null | readonly JsonValue[] | JsonObject;\nexport type JsonObject = { readonly [key: string]: JsonValue | undefined };\n\n/**\n * Render a JSON-compatible value as a TypeScript source-text literal.\n *\n * Accepts `unknown` for ergonomics with structural types (e.g. `ColumnSpec`,\n * `ForeignKeySpec`) whose fields are all JSON-compatible but whose interfaces\n * lack the index signature TypeScript requires for `JsonObject` assignability.\n * Non-JSON values (Date, Symbol, Function, etc.) throw at runtime.\n */\nexport function jsonToTsSource(value: unknown): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n if (typeof value === 'string') return stringifyLiteral(value);\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n const items = value.map((v: unknown) => jsonToTsSource(v));\n const singleLine = `[${items.join(', ')}]`;\n if (singleLine.length <= 80) return singleLine;\n return `[\\n${items.map((i) => ` ${i}`).join(',\\n')},\\n]`;\n }\n if (typeof value === 'object') {\n const entries = Object.entries(value).filter(([, v]) => v !== undefined);\n if (entries.length === 0) return '{}';\n const items = entries.map(([k, v]) => `${renderKey(k)}: ${jsonToTsSource(v)}`);\n const singleLine = `{ ${items.join(', ')} }`;\n if (singleLine.length <= 80) return singleLine;\n return `{\\n${items.map((i) => ` ${i}`).join(',\\n')},\\n}`;\n }\n throw new Error(`jsonToTsSource: unsupported value type \"${typeof value}\"`);\n}\n\nfunction renderKey(key: string): string {\n if (key === '__proto__') return stringifyLiteral(key);\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : stringifyLiteral(key);\n}\n\n// JSON.stringify leaves U+2028/U+2029 unescaped; embedded raw in JS/TS source\n// those characters can terminate the string literal, so escape them explicitly.\nfunction stringifyLiteral(value: string): string {\n return JSON.stringify(value)\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029');\n}\n","import type { ImportRequirement } from './ts-expression';\n\n/**\n * Render an aggregated `import` block from a flat list of\n * `ImportRequirement`s. Each target's migration renderer collects\n * requirements polymorphically from its call nodes and pipes them here.\n *\n * The emitter invariants:\n *\n * - **Usually one line per module specifier.** Named imports are aggregated\n * and emitted sorted; a single default symbol is combined onto the same\n * line when attributes agree (`import def, { a, b } from \"m\";`). Aliased\n * symbols render `symbol as alias`. When every symbol for a module is\n * `typeOnly`, the statement collapses to `import type { … }`; a module\n * mixing value and type symbols prefixes the type-only ones\n * (`import { type T, v }`). Exceptions that split into multiple lines: a\n * fully type-only statement with both a default and one or more named\n * bindings (`import type D from \"m\";` then `import type { N } from \"m\";`,\n * because TypeScript rejects `import type D, { N } from \"m\"` — TS1363),\n * and multiple distinct default symbols (see below).\n * - **Multiple distinct default symbols per module are allowed.** JS permits\n * re-importing the same specifier under different default-binding names\n * (`import a from 'm'; import b from 'm';`), so each distinct default\n * symbol renders its own `import` line, sorted alphabetically; a repeated\n * requirement for the same symbol still collapses into one binding,\n * merging `typeOnly` by AND.\n * - **Attribute unanimity per module.** All requirements for the same\n * module specifier must carry the same (or no) `attributes` map.\n * Divergent attribute maps throw — they can't collapse to one line\n * and there's no user-resolvable recovery at this layer.\n * - **Distinct (symbol, alias) pairs are distinct bindings.** TypeScript\n * permits importing the same export under multiple local names, so\n * `{ A }` + `{ A as B }` renders as `import { A, A as B } from \"m\"` and\n * `{ A as B }` + `{ A as C }` renders as `import { A as B, A as C } from \"m\"`.\n * Truly identical `(symbol, alias)` pairs still collapse to one binding,\n * merging `typeOnly` by AND.\n * - **Deterministic ordering.** Modules are emitted sorted by specifier;\n * within a module, named bindings are emitted sorted by `(symbol, alias)`\n * using JavaScript code-unit comparison, with the un-aliased form (no\n * alias) treated as alias `\"\"` so it sorts before any aliased form of the\n * same symbol.\n *\n * Returns a string containing one or more import lines per module (see the\n * splitting exceptions above), joined by `\\n` (no trailing newline). An\n * empty requirement list returns `\"\"`.\n */\nexport function renderImports(requirements: readonly ImportRequirement[]): string {\n const byModule = aggregateByModule(requirements);\n const entries = [...byModule.entries()].sort(([a], [b]) => a.localeCompare(b));\n return entries\n .map(([moduleSpecifier, group]) => renderModuleImport(moduleSpecifier, group))\n .join('\\n');\n}\n\ninterface NamedBinding {\n symbol: string;\n alias: string | null;\n typeOnly: boolean;\n}\n\ninterface ModuleImportGroup {\n readonly named: Map<string, NamedBinding>;\n readonly defaults: Map<string, boolean>;\n attributes: Readonly<Record<string, string>> | null;\n attributesSet: boolean;\n}\n\nfunction aggregateByModule(\n requirements: readonly ImportRequirement[],\n): Map<string, ModuleImportGroup> {\n const byModule = new Map<string, ModuleImportGroup>();\n for (const req of requirements) {\n let group = byModule.get(req.moduleSpecifier);\n if (!group) {\n group = {\n named: new Map(),\n defaults: new Map(),\n attributes: null,\n attributesSet: false,\n };\n byModule.set(req.moduleSpecifier, group);\n }\n mergeRequirementIntoGroup(req, group);\n }\n return byModule;\n}\n\nfunction mergeRequirementIntoGroup(req: ImportRequirement, group: ModuleImportGroup): void {\n const kind = req.kind ?? 'named';\n const typeOnly = req.typeOnly === true;\n if (kind === 'default') {\n const existingTypeOnly = group.defaults.get(req.symbol);\n group.defaults.set(\n req.symbol,\n existingTypeOnly === undefined ? typeOnly : existingTypeOnly && typeOnly,\n );\n } else {\n const alias = req.alias && req.alias !== req.symbol ? req.alias : null;\n const key = namedBindingKey(req.symbol, alias);\n const existing = group.named.get(key);\n if (existing) {\n existing.typeOnly = existing.typeOnly && typeOnly;\n } else {\n group.named.set(key, { symbol: req.symbol, alias, typeOnly });\n }\n }\n mergeAttributes(req, group);\n}\n\nfunction mergeAttributes(req: ImportRequirement, group: ModuleImportGroup): void {\n const incoming = req.attributes ?? null;\n if (!group.attributesSet) {\n group.attributes = incoming;\n group.attributesSet = true;\n return;\n }\n if (!attributesEqual(group.attributes, incoming)) {\n throw new Error(\n `Conflicting import attributes for module \"${req.moduleSpecifier}\": ` +\n `${stringifyAttributes(group.attributes)} vs ${stringifyAttributes(incoming)}.`,\n );\n }\n}\n\nfunction attributesEqual(\n a: Readonly<Record<string, string>> | null,\n b: Readonly<Record<string, string>> | null,\n): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n const aKeys = Object.keys(a).sort();\n const bKeys = Object.keys(b).sort();\n if (aKeys.length !== bKeys.length) return false;\n for (let i = 0; i < aKeys.length; i++) {\n const key = aKeys[i];\n if (key !== bKeys[i]) return false;\n if (a[key as string] !== b[key as string]) return false;\n }\n return true;\n}\n\nfunction stringifyAttributes(attrs: Readonly<Record<string, string>> | null): string {\n if (attrs === null) return '(none)';\n const entries = Object.entries(attrs)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}: ${JSON.stringify(v)}`);\n return `{ ${entries.join(', ')} }`;\n}\n\nfunction renderModuleImport(moduleSpecifier: string, group: ModuleImportGroup): string {\n const attrs = buildAttributesClause(group.attributes);\n const defaultEntries = [...group.defaults.entries()].sort(([a], [b]) => a.localeCompare(b));\n const hasNamed = group.named.size > 0;\n\n // More than one distinct default symbol can't share a single import\n // clause (`import a, b from 'm'` isn't valid syntax), so each gets its\n // own line; named bindings (if any) follow on their own line.\n if (defaultEntries.length > 1) {\n const defaultLines = defaultEntries.map(\n ([symbol, typeOnly]) =>\n `import ${typeOnly ? 'type ' : ''}${symbol} from '${moduleSpecifier}'${attrs};`,\n );\n if (!hasNamed) return defaultLines.join('\\n');\n return [...defaultLines, renderNamedOnlyStatement(moduleSpecifier, group, attrs)].join('\\n');\n }\n\n const defaultEntry = defaultEntries[0];\n const hasDefault = defaultEntry !== undefined;\n const [defaultSymbol, defaultTypeOnly] = defaultEntry ?? [null, true];\n const typeOnlyStatement = isStatementTypeOnly(hasDefault, defaultTypeOnly, group);\n if (typeOnlyStatement && hasDefault && hasNamed) {\n const defaultLine = `import type ${defaultSymbol} from '${moduleSpecifier}'${attrs};`;\n const namedClause = renderNamedBindingsList(group, true);\n const namedLine = `import type { ${namedClause} } from '${moduleSpecifier}'${attrs};`;\n return `${defaultLine}\\n${namedLine}`;\n }\n const keyword = typeOnlyStatement ? 'import type' : 'import';\n const clause = buildImportClause(defaultSymbol, group, typeOnlyStatement);\n return `${keyword} ${clause} from '${moduleSpecifier}'${attrs};`;\n}\n\nfunction renderNamedOnlyStatement(\n moduleSpecifier: string,\n group: ModuleImportGroup,\n attrs: string,\n): string {\n const typeOnlyStatement = [...group.named.values()].every((binding) => binding.typeOnly);\n const keyword = typeOnlyStatement ? 'import type' : 'import';\n const namedClause = renderNamedBindingsList(group, typeOnlyStatement);\n return `${keyword} { ${namedClause} } from '${moduleSpecifier}'${attrs};`;\n}\n\nfunction isStatementTypeOnly(\n hasDefault: boolean,\n defaultTypeOnly: boolean,\n group: ModuleImportGroup,\n): boolean {\n const hasNamed = group.named.size > 0;\n if (!hasDefault && !hasNamed) return false;\n if (hasDefault && !defaultTypeOnly) return false;\n for (const binding of group.named.values()) {\n if (!binding.typeOnly) return false;\n }\n return true;\n}\n\nfunction buildImportClause(\n defaultSymbol: string | null,\n group: ModuleImportGroup,\n statementTypeOnly: boolean,\n): string {\n const hasNamed = group.named.size > 0;\n const hasDefault = defaultSymbol !== null;\n const namedClause = hasNamed ? renderNamedBindingsList(group, statementTypeOnly) : '';\n if (hasDefault && hasNamed) {\n return `${defaultSymbol}, { ${namedClause} }`;\n }\n if (hasDefault) {\n return defaultSymbol;\n }\n return `{ ${namedClause} }`;\n}\n\nfunction renderNamedBindingsList(group: ModuleImportGroup, statementTypeOnly: boolean): string {\n return [...group.named.values()]\n .sort(compareNamedBindings)\n .map((binding) => renderNamedBinding(binding, statementTypeOnly))\n .join(', ');\n}\n\nfunction compareNamedBindings(a: NamedBinding, b: NamedBinding): number {\n if (a.symbol !== b.symbol) return a.symbol < b.symbol ? -1 : 1;\n const aAlias = a.alias ?? '';\n const bAlias = b.alias ?? '';\n if (aAlias === bAlias) return 0;\n return aAlias < bAlias ? -1 : 1;\n}\n\nfunction namedBindingKey(symbol: string, alias: string | null): string {\n return `${symbol}\\x00${alias ?? ''}`;\n}\n\nfunction renderNamedBinding(binding: NamedBinding, statementTypeOnly: boolean): string {\n const prefix = !statementTypeOnly && binding.typeOnly ? 'type ' : '';\n const aliasClause = binding.alias !== null ? ` as ${binding.alias}` : '';\n return `${prefix}${binding.symbol}${aliasClause}`;\n}\n\nfunction buildAttributesClause(attrs: Readonly<Record<string, string>> | null): string {\n if (attrs === null) return '';\n const entries = Object.entries(attrs)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}: ${JSON.stringify(v)}`);\n if (entries.length === 0) return '';\n return ` with { ${entries.join(', ')} }`;\n}\n","/**\n * Declarative contribution to the `import` block of a rendered TypeScript\n * source file. Each node in an IR declares which symbols it needs from which\n * modules; the top-level renderer deduplicates across nodes and emits one\n * `import { a, b, c } from \"…\"` line per module.\n *\n * `kind` defaults to `\"named\"` (e.g. `import { a } from \"m\"`). Setting it to\n * `\"default\"` emits `import a from \"m\"`. `attributes`, if provided, emits an\n * import attributes clause (`with { type: \"json\" }`) verbatim — required for\n * JSON module imports in the rendered scaffolds.\n *\n * `alias`, when present and different from `symbol`, renders `symbol as alias`.\n * `typeOnly` marks the symbol as a type import: when every symbol contributed\n * for a module is `typeOnly`, the whole statement collapses to\n * `import type { … } from \"m\"`; when a module mixes value and type symbols, the\n * type-only ones carry a per-specifier `type` prefix (`import { type T, val }`).\n */\nexport interface ImportRequirement {\n readonly moduleSpecifier: string;\n readonly symbol: string;\n readonly kind?: 'named' | 'default';\n readonly attributes?: Readonly<Record<string, string>>;\n readonly alias?: string;\n readonly typeOnly?: boolean;\n}\n\n/**\n * Abstract base class for any IR node that can be emitted as a TypeScript\n * expression and declare its own import requirements.\n *\n * A top-level renderer walks an array of these polymorphically, concatenates\n * `renderTypeScript()` results, and aggregates `importRequirements()` into a\n * deduplicated import block.\n */\nexport abstract class TsExpression {\n abstract renderTypeScript(): string;\n abstract importRequirements(): readonly ImportRequirement[];\n}\n"],"mappings":"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"}
{
"name": "@prisma-next/ts-render",
"version": "0.16.0-dev.4",
"version": "0.16.0-dev.5",
"license": "Apache-2.0",

@@ -10,4 +10,4 @@ "type": "module",

"devDependencies": {
"@prisma-next/tsconfig": "0.16.0-dev.4",
"@prisma-next/tsdown": "0.16.0-dev.4",
"@prisma-next/tsconfig": "0.16.0-dev.5",
"@prisma-next/tsdown": "0.16.0-dev.5",
"tsdown": "0.22.8",

@@ -14,0 +14,0 @@ "typescript": "5.9.3",

@@ -32,3 +32,3 @@ /**

if (value === null) return 'null';
if (typeof value === 'string') return JSON.stringify(value);
if (typeof value === 'string') return stringifyLiteral(value);
if (typeof value === 'number' || typeof value === 'boolean') return String(value);

@@ -54,4 +54,12 @@ if (Array.isArray(value)) {

function renderKey(key: string): string {
if (key === '__proto__') return JSON.stringify(key);
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
if (key === '__proto__') return stringifyLiteral(key);
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : stringifyLiteral(key);
}
// JSON.stringify leaves U+2028/U+2029 unescaped; embedded raw in JS/TS source
// those characters can terminate the string literal, so escape them explicitly.
function stringifyLiteral(value: string): string {
return JSON.stringify(value)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
}

@@ -10,15 +10,19 @@ import type { ImportRequirement } from './ts-expression';

*
* - **One line per module specifier.** Named imports are aggregated and
* emitted sorted; a single default symbol is combined onto the same line
* when attributes agree (`import def, { a, b } from "m";`). Aliased symbols
* render `symbol as alias`. When every symbol for a module is `typeOnly`,
* the statement collapses to `import type { … }`; a module mixing value
* and type symbols prefixes the type-only ones (`import { type T, v }`).
* Exception: a fully type-only statement that has both a default and one or
* more named bindings splits to two lines (`import type D from "m";` then
* `import type { N } from "m";`) because TypeScript rejects
* `import type D, { N } from "m"` (TS1363).
* - **At most one default symbol per module.** Two conflicting default
* symbols on the same specifier throw — the user's renderer can't
* guess which one they meant.
* - **Usually one line per module specifier.** Named imports are aggregated
* and emitted sorted; a single default symbol is combined onto the same
* line when attributes agree (`import def, { a, b } from "m";`). Aliased
* symbols render `symbol as alias`. When every symbol for a module is
* `typeOnly`, the statement collapses to `import type { … }`; a module
* mixing value and type symbols prefixes the type-only ones
* (`import { type T, v }`). Exceptions that split into multiple lines: a
* fully type-only statement with both a default and one or more named
* bindings (`import type D from "m";` then `import type { N } from "m";`,
* because TypeScript rejects `import type D, { N } from "m"` — TS1363),
* and multiple distinct default symbols (see below).
* - **Multiple distinct default symbols per module are allowed.** JS permits
* re-importing the same specifier under different default-binding names
* (`import a from 'm'; import b from 'm';`), so each distinct default
* symbol renders its own `import` line, sorted alphabetically; a repeated
* requirement for the same symbol still collapses into one binding,
* merging `typeOnly` by AND.
* - **Attribute unanimity per module.** All requirements for the same

@@ -40,4 +44,5 @@ * module specifier must carry the same (or no) `attributes` map.

*
* Returns a string containing one import line per module, joined by `\n`
* (no trailing newline). An empty requirement list returns `""`.
* Returns a string containing one or more import lines per module (see the
* splitting exceptions above), joined by `\n` (no trailing newline). An
* empty requirement list returns `""`.
*/

@@ -60,4 +65,3 @@ export function renderImports(requirements: readonly ImportRequirement[]): string {

readonly named: Map<string, NamedBinding>;
defaultSymbol: string | null;
defaultTypeOnly: boolean;
readonly defaults: Map<string, boolean>;
attributes: Readonly<Record<string, string>> | null;

@@ -76,4 +80,3 @@ attributesSet: boolean;

named: new Map(),
defaultSymbol: null,
defaultTypeOnly: true,
defaults: new Map(),
attributes: null,

@@ -93,10 +96,7 @@ attributesSet: false,

if (kind === 'default') {
if (group.defaultSymbol !== null && group.defaultSymbol !== req.symbol) {
throw new Error(
`Conflicting default imports for module "${req.moduleSpecifier}": ` +
`"${group.defaultSymbol}" and "${req.symbol}". Only one default symbol is allowed per module.`,
);
}
group.defaultSymbol = req.symbol;
group.defaultTypeOnly = group.defaultTypeOnly && typeOnly;
const existingTypeOnly = group.defaults.get(req.symbol);
group.defaults.set(
req.symbol,
existingTypeOnly === undefined ? typeOnly : existingTypeOnly && typeOnly,
);
} else {

@@ -156,8 +156,24 @@ const alias = req.alias && req.alias !== req.symbol ? req.alias : null;

function renderModuleImport(moduleSpecifier: string, group: ModuleImportGroup): string {
const typeOnlyStatement = isStatementTypeOnly(group);
const attrs = buildAttributesClause(group.attributes);
const hasDefault = group.defaultSymbol !== null;
const defaultEntries = [...group.defaults.entries()].sort(([a], [b]) => a.localeCompare(b));
const hasNamed = group.named.size > 0;
// More than one distinct default symbol can't share a single import
// clause (`import a, b from 'm'` isn't valid syntax), so each gets its
// own line; named bindings (if any) follow on their own line.
if (defaultEntries.length > 1) {
const defaultLines = defaultEntries.map(
([symbol, typeOnly]) =>
`import ${typeOnly ? 'type ' : ''}${symbol} from '${moduleSpecifier}'${attrs};`,
);
if (!hasNamed) return defaultLines.join('\n');
return [...defaultLines, renderNamedOnlyStatement(moduleSpecifier, group, attrs)].join('\n');
}
const defaultEntry = defaultEntries[0];
const hasDefault = defaultEntry !== undefined;
const [defaultSymbol, defaultTypeOnly] = defaultEntry ?? [null, true];
const typeOnlyStatement = isStatementTypeOnly(hasDefault, defaultTypeOnly, group);
if (typeOnlyStatement && hasDefault && hasNamed) {
const defaultLine = `import type ${group.defaultSymbol} from '${moduleSpecifier}'${attrs};`;
const defaultLine = `import type ${defaultSymbol} from '${moduleSpecifier}'${attrs};`;
const namedClause = renderNamedBindingsList(group, true);

@@ -168,11 +184,25 @@ const namedLine = `import type { ${namedClause} } from '${moduleSpecifier}'${attrs};`;

const keyword = typeOnlyStatement ? 'import type' : 'import';
const clause = buildImportClause(group, typeOnlyStatement);
const clause = buildImportClause(defaultSymbol, group, typeOnlyStatement);
return `${keyword} ${clause} from '${moduleSpecifier}'${attrs};`;
}
function isStatementTypeOnly(group: ModuleImportGroup): boolean {
const hasDefault = group.defaultSymbol !== null;
function renderNamedOnlyStatement(
moduleSpecifier: string,
group: ModuleImportGroup,
attrs: string,
): string {
const typeOnlyStatement = [...group.named.values()].every((binding) => binding.typeOnly);
const keyword = typeOnlyStatement ? 'import type' : 'import';
const namedClause = renderNamedBindingsList(group, typeOnlyStatement);
return `${keyword} { ${namedClause} } from '${moduleSpecifier}'${attrs};`;
}
function isStatementTypeOnly(
hasDefault: boolean,
defaultTypeOnly: boolean,
group: ModuleImportGroup,
): boolean {
const hasNamed = group.named.size > 0;
if (!hasDefault && !hasNamed) return false;
if (hasDefault && !group.defaultTypeOnly) return false;
if (hasDefault && !defaultTypeOnly) return false;
for (const binding of group.named.values()) {

@@ -184,11 +214,15 @@ if (!binding.typeOnly) return false;

function buildImportClause(group: ModuleImportGroup, statementTypeOnly: boolean): string {
function buildImportClause(
defaultSymbol: string | null,
group: ModuleImportGroup,
statementTypeOnly: boolean,
): string {
const hasNamed = group.named.size > 0;
const hasDefault = group.defaultSymbol !== null;
const hasDefault = defaultSymbol !== null;
const namedClause = hasNamed ? renderNamedBindingsList(group, statementTypeOnly) : '';
if (hasDefault && hasNamed) {
return `${group.defaultSymbol}, { ${namedClause} }`;
return `${defaultSymbol}, { ${namedClause} }`;
}
if (hasDefault) {
return group.defaultSymbol as string;
return defaultSymbol;
}

@@ -195,0 +229,0 @@ return `{ ${namedClause} }`;