ts-procedures 6.0.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent_config/bin/setup.mjs +0 -0
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +1 -0
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +2 -0
- package/agent_config/claude-code/skills/ts-procedures-kotlin/SKILL.md +106 -0
- package/agent_config/copilot/copilot-instructions.md +2 -0
- package/agent_config/cursor/cursorrules +2 -0
- package/build/codegen/bin/cli.d.ts +25 -0
- package/build/codegen/bin/cli.js +88 -0
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/bin/cli.test.js +180 -1
- package/build/codegen/bin/cli.test.js.map +1 -1
- package/build/codegen/index.d.ts +19 -0
- package/build/codegen/index.js +5 -0
- package/build/codegen/index.js.map +1 -1
- package/build/codegen/pipeline.d.ts +7 -0
- package/build/codegen/pipeline.js +57 -0
- package/build/codegen/pipeline.js.map +1 -1
- package/build/codegen/pipeline.test.js +162 -0
- package/build/codegen/pipeline.test.js.map +1 -1
- package/build/codegen/targets/kotlin/ajsc-adapter.d.ts +26 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.js +38 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.js.map +1 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.test.js +37 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.test.js.map +1 -0
- package/build/codegen/targets/kotlin/e2e-compile.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/e2e-compile.test.js +75 -0
- package/build/codegen/targets/kotlin/e2e-compile.test.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +15 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.js +80 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +207 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.d.ts +14 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.js +40 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +91 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -0
- package/build/codegen/targets/kotlin/format-kotlin.d.ts +15 -0
- package/build/codegen/targets/kotlin/format-kotlin.js +40 -0
- package/build/codegen/targets/kotlin/format-kotlin.js.map +1 -0
- package/build/codegen/targets/kotlin/format-kotlin.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/format-kotlin.test.js +50 -0
- package/build/codegen/targets/kotlin/format-kotlin.test.js.map +1 -0
- package/build/codegen/targets/kotlin/integration.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/integration.test.js +51 -0
- package/build/codegen/targets/kotlin/integration.test.js.map +1 -0
- package/build/codegen/targets/kotlin/probe-unsupported-unions.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/probe-unsupported-unions.test.js +50 -0
- package/build/codegen/targets/kotlin/probe-unsupported-unions.test.js.map +1 -0
- package/build/codegen/test-helpers/golden.d.ts +15 -0
- package/build/codegen/test-helpers/golden.js +30 -0
- package/build/codegen/test-helpers/golden.js.map +1 -0
- package/build/codegen/test-helpers/golden.test.d.ts +1 -0
- package/build/codegen/test-helpers/golden.test.js +76 -0
- package/build/codegen/test-helpers/golden.test.js.map +1 -0
- package/docs/codegen-kotlin.md +175 -0
- package/docs/http-integrations.md +32 -0
- package/docs/superpowers/plans/2026-04-24-kotlin-codegen-target.md +1265 -0
- package/docs/superpowers/plans/2026-04-25-ajsc-v7-kotlin-polish.md +1993 -0
- package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +401 -0
- package/docs/superpowers/specs/2026-04-25-ajsc-v7-kotlin-polish-design.md +314 -0
- package/package.json +2 -2
- package/src/codegen/bin/cli.test.ts +200 -1
- package/src/codegen/bin/cli.ts +103 -0
- package/src/codegen/index.ts +27 -0
- package/src/codegen/pipeline.test.ts +175 -0
- package/src/codegen/pipeline.ts +79 -0
- package/src/codegen/targets/kotlin/__fixtures__/users-envelope.json +144 -0
- package/src/codegen/targets/kotlin/__fixtures__/users-golden.kt +121 -0
- package/src/codegen/targets/kotlin/__snapshots__/probe-unsupported-unions.test.ts.snap +27 -0
- package/src/codegen/targets/kotlin/ajsc-adapter.test.ts +47 -0
- package/src/codegen/targets/kotlin/ajsc-adapter.ts +66 -0
- package/src/codegen/targets/kotlin/e2e-compile.test.ts +86 -0
- package/src/codegen/targets/kotlin/emit-route-kotlin.test.ts +239 -0
- package/src/codegen/targets/kotlin/emit-route-kotlin.ts +109 -0
- package/src/codegen/targets/kotlin/emit-scope-kotlin.test.ts +112 -0
- package/src/codegen/targets/kotlin/emit-scope-kotlin.ts +65 -0
- package/src/codegen/targets/kotlin/format-kotlin.test.ts +70 -0
- package/src/codegen/targets/kotlin/format-kotlin.ts +45 -0
- package/src/codegen/targets/kotlin/integration.test.ts +77 -0
- package/src/codegen/targets/kotlin/probe-unsupported-unions.test.ts +64 -0
- package/src/codegen/test-helpers/golden.test.ts +80 -0
- package/src/codegen/test-helpers/golden.ts +34 -0
- package/src/implementations/http/README.md +2 -0
- package/src/implementations/http/hono-stream/README.md +15 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { execFileSync, execSync } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, writeFileSync, readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { runPipeline } from '../../pipeline.js';
|
|
8
|
+
import { resolveProductionKotlinEmitter } from './ajsc-adapter.js';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
function kotlincAvailable() {
|
|
12
|
+
try {
|
|
13
|
+
execSync('kotlinc -version', { stdio: 'ignore' });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const RUN = process.env.TS_PROCEDURES_KOTLIN_E2E === '1';
|
|
21
|
+
/**
|
|
22
|
+
* E2E: real ajsc → real .kt output → real kotlinc compile.
|
|
23
|
+
*
|
|
24
|
+
* Gated on (a) `kotlinc` on PATH and (b) opt-in via env var so default
|
|
25
|
+
* `npm test` runs stay green for contributors without the toolchain.
|
|
26
|
+
*
|
|
27
|
+
* **Classpath setup (one-time, local):**
|
|
28
|
+
*
|
|
29
|
+
* Download kotlinx-serialization jars (matching versions, e.g. 1.6.3) from
|
|
30
|
+
* Maven Central or a local Gradle/Maven cache. The classpath value is a
|
|
31
|
+
* `:`-separated list (`;` on Windows). Concrete example:
|
|
32
|
+
*
|
|
33
|
+
* export TS_PROCEDURES_KOTLIN_E2E_CLASSPATH="\
|
|
34
|
+
* $HOME/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-serialization-json-jvm/1.6.3/<hash>/kotlinx-serialization-json-jvm-1.6.3.jar:\
|
|
35
|
+
* $HOME/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-serialization-core-jvm/1.6.3/<hash>/kotlinx-serialization-core-jvm-1.6.3.jar"
|
|
36
|
+
*
|
|
37
|
+
* If the env var is unset the compile uses kotlinc's default classpath which
|
|
38
|
+
* lacks the kotlinx jars and fails — that's the expected mode when checking
|
|
39
|
+
* gating works.
|
|
40
|
+
*/
|
|
41
|
+
describe('kotlin codegen — kotlinc compile (gated)', () => {
|
|
42
|
+
it.skipIf(!kotlincAvailable() || !RUN)('compiles generated output without errors', async () => {
|
|
43
|
+
const emitter = await resolveProductionKotlinEmitter();
|
|
44
|
+
const envelope = JSON.parse(readFileSync(join(__dirname, '__fixtures__/users-envelope.json'), 'utf8'));
|
|
45
|
+
const files = await runPipeline({
|
|
46
|
+
envelope,
|
|
47
|
+
outDir: 'out',
|
|
48
|
+
dryRun: true,
|
|
49
|
+
target: 'kotlin',
|
|
50
|
+
kotlinPackage: 'com.example.api',
|
|
51
|
+
kotlinEmitter: emitter,
|
|
52
|
+
});
|
|
53
|
+
const dir = mkdtempSync(join(tmpdir(), 'tsp-kotlin-e2e-'));
|
|
54
|
+
for (const f of files) {
|
|
55
|
+
writeFileSync(join(dir, f.path.split('/').pop()), f.code);
|
|
56
|
+
}
|
|
57
|
+
// Expand the source glob in Node and pass kotlinc args as an array so
|
|
58
|
+
// the shell never sees user-controlled input. The classpath env var is
|
|
59
|
+
// only forwarded as a single -classpath argument; even a value with
|
|
60
|
+
// shell metachars cannot escape into command parsing.
|
|
61
|
+
const ktFiles = readdirSync(dir)
|
|
62
|
+
.filter((name) => name.endsWith('.kt'))
|
|
63
|
+
.map((name) => join(dir, name));
|
|
64
|
+
const outJar = join(dir, 'out.jar');
|
|
65
|
+
const cp = process.env.TS_PROCEDURES_KOTLIN_E2E_CLASSPATH;
|
|
66
|
+
const args = [];
|
|
67
|
+
if (cp != null && cp.length > 0) {
|
|
68
|
+
args.push('-classpath', cp);
|
|
69
|
+
}
|
|
70
|
+
args.push(...ktFiles, '-d', outJar);
|
|
71
|
+
execFileSync('kotlinc', args, { stdio: 'inherit' });
|
|
72
|
+
expect(existsSync(outJar)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=e2e-compile.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e2e-compile.test.js","sourceRoot":"","sources":["../../../../src/codegen/targets/kotlin/e2e-compile.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AnyHttpRouteDoc } from '../../../implementations/types.js';
|
|
2
|
+
import type { KotlinEmitter, KotlinEmitOptions } from './ajsc-adapter.js';
|
|
3
|
+
export interface EmitRouteResult {
|
|
4
|
+
/** Inner body of the `object RouteName { ... }` block — already indented one level. */
|
|
5
|
+
code: string;
|
|
6
|
+
/** Imports collected from every ajsc emit + any helpers this route used. */
|
|
7
|
+
imports: string[];
|
|
8
|
+
/** Outer route name used as the `object RouteName` identifier. */
|
|
9
|
+
routeName: string;
|
|
10
|
+
/** True when the route was a stream (out-of-scope). Caller logs once. */
|
|
11
|
+
skipped?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Subset of KotlinEmitOptions threaded by the pipeline; per-call rootTypeName is set inside. */
|
|
14
|
+
export type EmitRouteOpts = Omit<KotlinEmitOptions, 'rootTypeName' | 'inlineTypes'>;
|
|
15
|
+
export declare function emitKotlinRoute(route: AnyHttpRouteDoc, emitter: KotlinEmitter, errorSchemas: Map<string, unknown>, routeOpts?: EmitRouteOpts): EmitRouteResult;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { indent, pickDefined } from './format-kotlin.js';
|
|
2
|
+
const COLON_PARAM_RE = /:([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
3
|
+
function toBracePath(template) {
|
|
4
|
+
return template.replace(COLON_PARAM_RE, '{$1}');
|
|
5
|
+
}
|
|
6
|
+
function pathParamNames(template) {
|
|
7
|
+
const names = [];
|
|
8
|
+
for (const match of template.matchAll(COLON_PARAM_RE))
|
|
9
|
+
names.push(match[1]);
|
|
10
|
+
return names;
|
|
11
|
+
}
|
|
12
|
+
function buildPathFn(bracePath, params) {
|
|
13
|
+
if (params.length === 0)
|
|
14
|
+
return `const val path = "${bracePath}"`;
|
|
15
|
+
let body = bracePath;
|
|
16
|
+
for (const name of params)
|
|
17
|
+
body = body.replace(`{${name}}`, `\${p.${name}}`);
|
|
18
|
+
return `fun path(p: PathParams): String = "${body}"`;
|
|
19
|
+
}
|
|
20
|
+
function emitOptsFor(rootTypeName, routeOpts) {
|
|
21
|
+
const PASSTHROUGH_KEYS = ['serializer', 'unsupportedUnions', 'arrayItemNaming', 'depluralize', 'uncountableWords'];
|
|
22
|
+
return {
|
|
23
|
+
rootTypeName,
|
|
24
|
+
inlineTypes: true,
|
|
25
|
+
...pickDefined(routeOpts, PASSTHROUGH_KEYS),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function emitKotlinRoute(route, emitter, errorSchemas, routeOpts = {}) {
|
|
29
|
+
const kind = route.kind;
|
|
30
|
+
if (kind === 'stream') {
|
|
31
|
+
return { code: '', imports: [], routeName: route.name, skipped: true };
|
|
32
|
+
}
|
|
33
|
+
const isApi = kind === 'api' || 'fullPath' in route;
|
|
34
|
+
const rawPath = isApi ? route.fullPath : route.path;
|
|
35
|
+
const method = String(route.method).toUpperCase();
|
|
36
|
+
const bracePath = toBracePath(rawPath);
|
|
37
|
+
const params = pathParamNames(rawPath);
|
|
38
|
+
const lines = [
|
|
39
|
+
`const val method = "${method}"`,
|
|
40
|
+
`const val pathTemplate = "${bracePath}"`,
|
|
41
|
+
buildPathFn(bracePath, params),
|
|
42
|
+
];
|
|
43
|
+
const imports = [];
|
|
44
|
+
const schema = route.schema ?? {};
|
|
45
|
+
const input = (schema.input ?? {});
|
|
46
|
+
// Per-slot emission. Order is fixed for deterministic output.
|
|
47
|
+
const slots = [
|
|
48
|
+
{ rootName: 'PathParams', source: input.pathParams },
|
|
49
|
+
{ rootName: 'Query', source: input.query },
|
|
50
|
+
{ rootName: 'Body', source: input.body },
|
|
51
|
+
{ rootName: 'Response', source: schema.returnType },
|
|
52
|
+
];
|
|
53
|
+
for (const slot of slots) {
|
|
54
|
+
if (slot.source == null)
|
|
55
|
+
continue;
|
|
56
|
+
const result = emitter.emit(slot.source, emitOptsFor(slot.rootName, routeOpts));
|
|
57
|
+
lines.push('');
|
|
58
|
+
lines.push(result.code);
|
|
59
|
+
imports.push(...result.imports);
|
|
60
|
+
}
|
|
61
|
+
// Errors namespace — route.errors is `string[]` of taxonomy keys; look up each schema
|
|
62
|
+
// from the envelope-level errors map. Keys without schemas are skipped silently
|
|
63
|
+
// (matching the existing TS scope emitter's `errorKeys` filter).
|
|
64
|
+
const routeErrorKeys = (route.errors ?? [])
|
|
65
|
+
.filter((key) => errorSchemas.has(key));
|
|
66
|
+
if (routeErrorKeys.length > 0) {
|
|
67
|
+
const inner = [];
|
|
68
|
+
for (const key of routeErrorKeys) {
|
|
69
|
+
const r = emitter.emit(errorSchemas.get(key), emitOptsFor(key, routeOpts));
|
|
70
|
+
inner.push(r.code);
|
|
71
|
+
imports.push(...r.imports);
|
|
72
|
+
}
|
|
73
|
+
lines.push('');
|
|
74
|
+
lines.push('object Errors {');
|
|
75
|
+
lines.push(indent(inner.join('\n\n'), 1));
|
|
76
|
+
lines.push('}');
|
|
77
|
+
}
|
|
78
|
+
return { code: lines.join('\n'), imports, routeName: route.name, skipped: false };
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=emit-route-kotlin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-route-kotlin.js","sourceRoot":"","sources":["../../../../src/codegen/targets/kotlin/emit-route-kotlin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { emitKotlinRoute } from './emit-route-kotlin.js';
|
|
3
|
+
import { createStubKotlinEmitter } from './ajsc-adapter.js';
|
|
4
|
+
const ok = (code, rootTypeName) => ({
|
|
5
|
+
code,
|
|
6
|
+
rootTypeName,
|
|
7
|
+
extractedTypeNames: [],
|
|
8
|
+
imports: ['kotlinx.serialization.Serializable'],
|
|
9
|
+
});
|
|
10
|
+
const noErrors = new Map();
|
|
11
|
+
function makeSpyEmitter(results) {
|
|
12
|
+
const calls = [];
|
|
13
|
+
const emitter = {
|
|
14
|
+
emit(schema, opts) {
|
|
15
|
+
calls.push({ schema, opts });
|
|
16
|
+
const r = results[opts.rootTypeName];
|
|
17
|
+
if (r == null)
|
|
18
|
+
throw new Error(`No stubbed result for "${opts.rootTypeName}"`);
|
|
19
|
+
return r;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
return { emitter, calls };
|
|
23
|
+
}
|
|
24
|
+
describe('emitKotlinRoute', () => {
|
|
25
|
+
it('emits an api-kind route with path params and a response', () => {
|
|
26
|
+
const route = {
|
|
27
|
+
kind: 'api',
|
|
28
|
+
name: 'GetUser',
|
|
29
|
+
method: 'GET',
|
|
30
|
+
fullPath: '/users/:id',
|
|
31
|
+
schema: {
|
|
32
|
+
input: {
|
|
33
|
+
pathParams: { type: 'object' },
|
|
34
|
+
},
|
|
35
|
+
returnType: { type: 'object' },
|
|
36
|
+
},
|
|
37
|
+
errors: [],
|
|
38
|
+
};
|
|
39
|
+
const emitter = createStubKotlinEmitter({
|
|
40
|
+
PathParams: ok('@Serializable data class PathParams(val id: String)', 'PathParams'),
|
|
41
|
+
Response: ok('@Serializable data class Response(val id: String, val name: String)', 'Response'),
|
|
42
|
+
});
|
|
43
|
+
const result = emitKotlinRoute(route, emitter, noErrors);
|
|
44
|
+
expect(result.imports).toContain('kotlinx.serialization.Serializable');
|
|
45
|
+
expect(result.code).toContain('const val method = "GET"');
|
|
46
|
+
expect(result.code).toContain('const val pathTemplate = "/users/{id}"');
|
|
47
|
+
expect(result.code).toContain('fun path(p: PathParams): String = "/users/${p.id}"');
|
|
48
|
+
expect(result.code).toContain('@Serializable data class PathParams(val id: String)');
|
|
49
|
+
expect(result.code).toContain('@Serializable data class Response(val id: String, val name: String)');
|
|
50
|
+
});
|
|
51
|
+
it('emits a route with no path params using a path constant', () => {
|
|
52
|
+
const route = {
|
|
53
|
+
kind: 'api',
|
|
54
|
+
name: 'CreateUser',
|
|
55
|
+
method: 'POST',
|
|
56
|
+
fullPath: '/users',
|
|
57
|
+
schema: { input: { body: { type: 'object' } }, returnType: { type: 'object' } },
|
|
58
|
+
errors: [],
|
|
59
|
+
};
|
|
60
|
+
const emitter = createStubKotlinEmitter({
|
|
61
|
+
Body: ok('@Serializable data class Body(val name: String)', 'Body'),
|
|
62
|
+
Response: ok('@Serializable data class Response(val id: String)', 'Response'),
|
|
63
|
+
});
|
|
64
|
+
const result = emitKotlinRoute(route, emitter, noErrors);
|
|
65
|
+
expect(result.code).toContain('const val path = "/users"');
|
|
66
|
+
expect(result.code).not.toContain('fun path(');
|
|
67
|
+
});
|
|
68
|
+
it('emits an Errors namespace for routes whose error keys have schemas in the envelope', () => {
|
|
69
|
+
const route = {
|
|
70
|
+
kind: 'api',
|
|
71
|
+
name: 'GetUser',
|
|
72
|
+
method: 'GET',
|
|
73
|
+
fullPath: '/users/:id',
|
|
74
|
+
schema: { input: { pathParams: { type: 'object' } }, returnType: { type: 'object' } },
|
|
75
|
+
errors: ['NotFound'],
|
|
76
|
+
};
|
|
77
|
+
const emitter = createStubKotlinEmitter({
|
|
78
|
+
PathParams: ok('@Serializable data class PathParams(val id: String)', 'PathParams'),
|
|
79
|
+
Response: ok('@Serializable data class Response(val id: String)', 'Response'),
|
|
80
|
+
NotFound: ok('@Serializable data class NotFound(val name: String, val message: String)', 'NotFound'),
|
|
81
|
+
});
|
|
82
|
+
const errorSchemas = new Map([['NotFound', { type: 'object' }]]);
|
|
83
|
+
const result = emitKotlinRoute(route, emitter, errorSchemas);
|
|
84
|
+
expect(result.code).toContain('object Errors {');
|
|
85
|
+
expect(result.code).toContain('@Serializable data class NotFound(val name: String, val message: String)');
|
|
86
|
+
});
|
|
87
|
+
it('silently skips error keys with no schema in the envelope map', () => {
|
|
88
|
+
const route = {
|
|
89
|
+
kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/users',
|
|
90
|
+
schema: {}, errors: ['UnknownTaxonomyKey'],
|
|
91
|
+
};
|
|
92
|
+
const result = emitKotlinRoute(route, createStubKotlinEmitter({}), new Map());
|
|
93
|
+
expect(result.code).not.toContain('object Errors {');
|
|
94
|
+
});
|
|
95
|
+
it('returns skipped:true for stream routes', () => {
|
|
96
|
+
const route = { kind: 'stream', name: 'WatchUsers', method: 'GET', path: '/users/stream', schema: {}, errors: [] };
|
|
97
|
+
const result = emitKotlinRoute(route, createStubKotlinEmitter({}), noErrors);
|
|
98
|
+
expect(result.code).toBe('');
|
|
99
|
+
expect(result.skipped).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
it('passes inlineTypes:true plus serializer/unsupportedUnions to every slot emit', () => {
|
|
102
|
+
const route = {
|
|
103
|
+
kind: 'api',
|
|
104
|
+
name: 'GetUser',
|
|
105
|
+
method: 'GET',
|
|
106
|
+
fullPath: '/users/:id',
|
|
107
|
+
schema: {
|
|
108
|
+
input: { pathParams: { type: 'object' } },
|
|
109
|
+
returnType: { type: 'object' },
|
|
110
|
+
},
|
|
111
|
+
errors: ['NotFound'],
|
|
112
|
+
};
|
|
113
|
+
const { emitter, calls } = makeSpyEmitter({
|
|
114
|
+
PathParams: ok('@Serializable data class PathParams(val id: String)', 'PathParams'),
|
|
115
|
+
Response: ok('@Serializable data class Response(val id: String)', 'Response'),
|
|
116
|
+
NotFound: ok('@Serializable data class NotFound(val message: String)', 'NotFound'),
|
|
117
|
+
});
|
|
118
|
+
emitKotlinRoute(route, emitter, new Map([['NotFound', { type: 'object' }]]), {
|
|
119
|
+
serializer: 'none',
|
|
120
|
+
unsupportedUnions: 'fallback',
|
|
121
|
+
});
|
|
122
|
+
// 3 emits: PathParams, Response, NotFound
|
|
123
|
+
expect(calls.length).toBe(3);
|
|
124
|
+
for (const c of calls) {
|
|
125
|
+
expect(c.opts.inlineTypes).toBe(true);
|
|
126
|
+
expect(c.opts.serializer).toBe('none');
|
|
127
|
+
expect(c.opts.unsupportedUnions).toBe('fallback');
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
it('preserves slot order: pathParams → query → body → response → errors', () => {
|
|
131
|
+
const route = {
|
|
132
|
+
kind: 'api',
|
|
133
|
+
name: 'X',
|
|
134
|
+
method: 'POST',
|
|
135
|
+
fullPath: '/x/:id',
|
|
136
|
+
schema: {
|
|
137
|
+
input: {
|
|
138
|
+
pathParams: { type: 'object' },
|
|
139
|
+
query: { type: 'object' },
|
|
140
|
+
body: { type: 'object' },
|
|
141
|
+
},
|
|
142
|
+
returnType: { type: 'object' },
|
|
143
|
+
},
|
|
144
|
+
errors: ['Z'],
|
|
145
|
+
};
|
|
146
|
+
const { emitter, calls } = makeSpyEmitter({
|
|
147
|
+
PathParams: ok('a', 'PathParams'),
|
|
148
|
+
Query: ok('b', 'Query'),
|
|
149
|
+
Body: ok('c', 'Body'),
|
|
150
|
+
Response: ok('d', 'Response'),
|
|
151
|
+
Z: ok('e', 'Z'),
|
|
152
|
+
});
|
|
153
|
+
emitKotlinRoute(route, emitter, new Map([['Z', { type: 'object' }]]), {});
|
|
154
|
+
expect(calls.map((c) => c.opts.rootTypeName)).toEqual([
|
|
155
|
+
'PathParams', 'Query', 'Body', 'Response', 'Z',
|
|
156
|
+
]);
|
|
157
|
+
});
|
|
158
|
+
it('threads passthrough opts (arrayItemNaming/depluralize/uncountableWords) verbatim', () => {
|
|
159
|
+
const route = {
|
|
160
|
+
kind: 'api',
|
|
161
|
+
name: 'X',
|
|
162
|
+
method: 'GET',
|
|
163
|
+
fullPath: '/x',
|
|
164
|
+
schema: { returnType: { type: 'object' } },
|
|
165
|
+
errors: [],
|
|
166
|
+
};
|
|
167
|
+
const { emitter, calls } = makeSpyEmitter({
|
|
168
|
+
Response: ok('@Serializable data class Response(val ok: Boolean)', 'Response'),
|
|
169
|
+
});
|
|
170
|
+
emitKotlinRoute(route, emitter, new Map(), {
|
|
171
|
+
arrayItemNaming: false,
|
|
172
|
+
depluralize: true,
|
|
173
|
+
uncountableWords: ['data', 'metadata'],
|
|
174
|
+
});
|
|
175
|
+
expect(calls).toHaveLength(1);
|
|
176
|
+
expect(calls[0].opts.arrayItemNaming).toBe(false);
|
|
177
|
+
expect(calls[0].opts.depluralize).toBe(true);
|
|
178
|
+
expect(calls[0].opts.uncountableWords).toEqual(['data', 'metadata']);
|
|
179
|
+
});
|
|
180
|
+
it('does not include passthrough keys when caller omits them', () => {
|
|
181
|
+
const route = {
|
|
182
|
+
kind: 'api',
|
|
183
|
+
name: 'X',
|
|
184
|
+
method: 'GET',
|
|
185
|
+
fullPath: '/x',
|
|
186
|
+
schema: { returnType: { type: 'object' } },
|
|
187
|
+
errors: [],
|
|
188
|
+
};
|
|
189
|
+
const { emitter, calls } = makeSpyEmitter({
|
|
190
|
+
Response: ok('@Serializable data class Response(val ok: Boolean)', 'Response'),
|
|
191
|
+
});
|
|
192
|
+
emitKotlinRoute(route, emitter, new Map(), {});
|
|
193
|
+
expect(calls).toHaveLength(1);
|
|
194
|
+
// Conditional-spread invariant: undefined opts must NOT be forwarded as keys
|
|
195
|
+
// (otherwise ajsc would receive `{ arrayItemNaming: undefined }` etc, which
|
|
196
|
+
// could shadow ajsc-side defaults).
|
|
197
|
+
expect('arrayItemNaming' in calls[0].opts).toBe(false);
|
|
198
|
+
expect('depluralize' in calls[0].opts).toBe(false);
|
|
199
|
+
expect('uncountableWords' in calls[0].opts).toBe(false);
|
|
200
|
+
// serializer / unsupportedUnions same invariant
|
|
201
|
+
expect('serializer' in calls[0].opts).toBe(false);
|
|
202
|
+
expect('unsupportedUnions' in calls[0].opts).toBe(false);
|
|
203
|
+
// inlineTypes is always set
|
|
204
|
+
expect(calls[0].opts.inlineTypes).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
//# sourceMappingURL=emit-route-kotlin.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-route-kotlin.test.js","sourceRoot":"","sources":["../../../../src/codegen/targets/kotlin/emit-route-kotlin.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ScopeGroup } from '../../group-routes.js';
|
|
2
|
+
import type { KotlinEmitter } from './ajsc-adapter.js';
|
|
3
|
+
import { type EmitRouteOpts } from './emit-route-kotlin.js';
|
|
4
|
+
export interface EmitScopeOptions extends EmitRouteOpts {
|
|
5
|
+
kotlinPackage: string;
|
|
6
|
+
sourceHash: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EmittedKotlinFile {
|
|
9
|
+
filename: string;
|
|
10
|
+
code: string;
|
|
11
|
+
/** Names of stream routes within this scope that were skipped. */
|
|
12
|
+
skippedStreams: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function emitKotlinScope(group: ScopeGroup, opts: EmitScopeOptions, emitter: KotlinEmitter, errorSchemas: Map<string, unknown>): EmittedKotlinFile;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { emitKotlinRoute } from './emit-route-kotlin.js';
|
|
2
|
+
import { kotlinPackageDecl, kotlinSourceHashHeader, kotlinImports, indent, pickDefined } from './format-kotlin.js';
|
|
3
|
+
function pascalCase(scope) {
|
|
4
|
+
return scope
|
|
5
|
+
.split('-')
|
|
6
|
+
.filter((p) => p.length > 0)
|
|
7
|
+
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
8
|
+
.join('');
|
|
9
|
+
}
|
|
10
|
+
export function emitKotlinScope(group, opts, emitter, errorSchemas) {
|
|
11
|
+
const scopeName = pascalCase(group.scopeKey);
|
|
12
|
+
const allImports = [];
|
|
13
|
+
const routeBlocks = [];
|
|
14
|
+
const skippedStreams = [];
|
|
15
|
+
const PASSTHROUGH_KEYS = ['serializer', 'unsupportedUnions', 'arrayItemNaming', 'depluralize', 'uncountableWords'];
|
|
16
|
+
const routeOpts = pickDefined(opts, PASSTHROUGH_KEYS);
|
|
17
|
+
for (const route of group.routes) {
|
|
18
|
+
const r = emitKotlinRoute(route, emitter, errorSchemas, routeOpts);
|
|
19
|
+
if (r.skipped) {
|
|
20
|
+
skippedStreams.push(r.routeName);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
allImports.push(...r.imports);
|
|
24
|
+
const wrapped = `object ${r.routeName} {\n${indent(r.code, 1)}\n}`;
|
|
25
|
+
routeBlocks.push(wrapped);
|
|
26
|
+
}
|
|
27
|
+
const innerScope = routeBlocks.length === 0 ? '' : indent(routeBlocks.join('\n\n'), 1);
|
|
28
|
+
const scopeBlock = innerScope === ''
|
|
29
|
+
? `object ${scopeName} {\n}`
|
|
30
|
+
: `object ${scopeName} {\n${innerScope}\n}`;
|
|
31
|
+
const importsBlock = kotlinImports(allImports);
|
|
32
|
+
const parts = [
|
|
33
|
+
kotlinPackageDecl(opts.kotlinPackage),
|
|
34
|
+
kotlinSourceHashHeader(opts.sourceHash),
|
|
35
|
+
importsBlock,
|
|
36
|
+
scopeBlock,
|
|
37
|
+
].filter((p) => p.length > 0);
|
|
38
|
+
return { filename: `${scopeName}.kt`, code: parts.join('\n\n') + '\n', skippedStreams };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=emit-scope-kotlin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-scope-kotlin.js","sourceRoot":"","sources":["../../../../src/codegen/targets/kotlin/emit-scope-kotlin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAsB,MAAM,wBAAwB,CAAA;AAC5E,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAclH,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAClD,IAAI,CAAC,EAAE,CAAC,CAAA;AACb,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,KAAiB,EACjB,IAAsB,EACtB,OAAsB,EACtB,YAAkC;IAElC,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAC5C,MAAM,UAAU,GAAa,EAAE,CAAA;IAC/B,MAAM,WAAW,GAAa,EAAE,CAAA;IAChC,MAAM,cAAc,GAAa,EAAE,CAAA;IAEnC,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,aAAa,EAAE,kBAAkB,CAAU,CAAA;IAC3H,MAAM,SAAS,GAAkB,WAAW,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAA;IAEpE,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,CAAA;QAClE,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;YAChC,SAAQ;QACV,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAA;QAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,SAAS,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAA;QAClE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC3B,CAAC;IAED,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;IACtF,MAAM,UAAU,GAAG,UAAU,KAAK,EAAE;QAClC,CAAC,CAAC,UAAU,SAAS,OAAO;QAC5B,CAAC,CAAC,UAAU,SAAS,OAAO,UAAU,KAAK,CAAA;IAE7C,MAAM,YAAY,GAAG,aAAa,CAAC,UAAU,CAAC,CAAA;IAC9C,MAAM,KAAK,GAAG;QACZ,iBAAiB,CAAC,IAAI,CAAC,aAAa,CAAC;QACrC,sBAAsB,CAAC,IAAI,CAAC,UAAU,CAAC;QACvC,YAAY;QACZ,UAAU;KACX,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAE7B,OAAO,EAAE,QAAQ,EAAE,GAAG,SAAS,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,cAAc,EAAE,CAAA;AACzF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { emitKotlinScope } from './emit-scope-kotlin.js';
|
|
3
|
+
import { createStubKotlinEmitter } from './ajsc-adapter.js';
|
|
4
|
+
const ok = (code, rootTypeName) => ({
|
|
5
|
+
code,
|
|
6
|
+
rootTypeName,
|
|
7
|
+
extractedTypeNames: [],
|
|
8
|
+
imports: ['kotlinx.serialization.Serializable'],
|
|
9
|
+
});
|
|
10
|
+
describe('emitKotlinScope', () => {
|
|
11
|
+
it('produces a complete kotlin source file for a single-route scope', () => {
|
|
12
|
+
const route = {
|
|
13
|
+
kind: 'api',
|
|
14
|
+
name: 'GetUser',
|
|
15
|
+
method: 'GET',
|
|
16
|
+
fullPath: '/users/:id',
|
|
17
|
+
schema: { input: { pathParams: { type: 'object' } }, returnType: { type: 'object' } },
|
|
18
|
+
errors: [],
|
|
19
|
+
};
|
|
20
|
+
const group = { scopeKey: 'users', camelCase: 'users', routes: [route] };
|
|
21
|
+
const emitter = createStubKotlinEmitter({
|
|
22
|
+
PathParams: ok('@Serializable data class PathParams(val id: String)', 'PathParams'),
|
|
23
|
+
Response: ok('@Serializable data class Response(val id: String)', 'Response'),
|
|
24
|
+
});
|
|
25
|
+
const file = emitKotlinScope(group, { kotlinPackage: 'com.example.api', sourceHash: 'abc123' }, emitter, new Map());
|
|
26
|
+
expect(file.filename).toBe('Users.kt');
|
|
27
|
+
expect(file.code).toContain('package com.example.api');
|
|
28
|
+
expect(file.code).toContain('// Source hash: abc123');
|
|
29
|
+
expect(file.code).toContain('import kotlinx.serialization.Serializable');
|
|
30
|
+
expect(file.code).toContain('object Users {');
|
|
31
|
+
expect(file.code).toContain('object GetUser {');
|
|
32
|
+
expect(file.code).toContain('const val method = "GET"');
|
|
33
|
+
});
|
|
34
|
+
it('joins multiple routes inside one scope object', () => {
|
|
35
|
+
const route1 = { kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/users/:id', schema: {}, errors: [] };
|
|
36
|
+
const route2 = { kind: 'api', name: 'CreateUser', method: 'POST', fullPath: '/users', schema: {}, errors: [] };
|
|
37
|
+
const group = { scopeKey: 'users', camelCase: 'users', routes: [route1, route2] };
|
|
38
|
+
const emitter = createStubKotlinEmitter({});
|
|
39
|
+
const file = emitKotlinScope(group, { kotlinPackage: 'com.example.api', sourceHash: 'h' }, emitter, new Map());
|
|
40
|
+
expect(file.code).toContain('object GetUser {');
|
|
41
|
+
expect(file.code).toContain('object CreateUser {');
|
|
42
|
+
// exactly one outer scope object
|
|
43
|
+
expect((file.code.match(/^object Users \{/gm) ?? []).length).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
it('uses PascalCase scope name for the filename and outer object', () => {
|
|
46
|
+
const group = { scopeKey: 'admin-users', camelCase: 'adminUsers', routes: [] };
|
|
47
|
+
const file = emitKotlinScope(group, { kotlinPackage: 'p', sourceHash: 'h' }, createStubKotlinEmitter({}), new Map());
|
|
48
|
+
expect(file.filename).toBe('AdminUsers.kt');
|
|
49
|
+
expect(file.code).toContain('object AdminUsers {');
|
|
50
|
+
});
|
|
51
|
+
it('threads all 5 passthrough opts to every emitter call', () => {
|
|
52
|
+
const route = { kind: 'api', name: 'X', method: 'GET', fullPath: '/x', schema: { returnType: { type: 'object' } }, errors: [] };
|
|
53
|
+
const group = { scopeKey: 'x', camelCase: 'x', routes: [route] };
|
|
54
|
+
const calls = [];
|
|
55
|
+
const emitter = {
|
|
56
|
+
emit(_s, opts) {
|
|
57
|
+
calls.push(opts);
|
|
58
|
+
return { code: 'data class Response', rootTypeName: 'Response', extractedTypeNames: [], imports: [] };
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
emitKotlinScope(group, {
|
|
62
|
+
kotlinPackage: 'p',
|
|
63
|
+
sourceHash: 'h',
|
|
64
|
+
serializer: 'none',
|
|
65
|
+
unsupportedUnions: 'fallback',
|
|
66
|
+
arrayItemNaming: false,
|
|
67
|
+
depluralize: true,
|
|
68
|
+
uncountableWords: ['data', 'metadata'],
|
|
69
|
+
}, emitter, new Map());
|
|
70
|
+
expect(calls.length).toBe(1);
|
|
71
|
+
expect(calls[0].inlineTypes).toBe(true);
|
|
72
|
+
expect(calls[0].serializer).toBe('none');
|
|
73
|
+
expect(calls[0].unsupportedUnions).toBe('fallback');
|
|
74
|
+
expect(calls[0].arrayItemNaming).toBe(false);
|
|
75
|
+
expect(calls[0].depluralize).toBe(true);
|
|
76
|
+
expect(calls[0].uncountableWords).toEqual(['data', 'metadata']);
|
|
77
|
+
});
|
|
78
|
+
it('collects skipped stream-route names', () => {
|
|
79
|
+
const stream = { kind: 'stream', name: 'WatchUsers', method: 'GET', path: '/u/stream', schema: {}, errors: [] };
|
|
80
|
+
const api = { kind: 'api', name: 'GetUser', method: 'GET', fullPath: '/u', schema: { returnType: { type: 'object' } }, errors: [] };
|
|
81
|
+
const group = { scopeKey: 'u', camelCase: 'u', routes: [stream, api] };
|
|
82
|
+
const emitter = createStubKotlinEmitter({
|
|
83
|
+
Response: ok('data class Response(val id: String)', 'Response'),
|
|
84
|
+
});
|
|
85
|
+
const result = emitKotlinScope(group, { kotlinPackage: 'p', sourceHash: 'h' }, emitter, new Map());
|
|
86
|
+
expect(result.skippedStreams).toEqual(['WatchUsers']);
|
|
87
|
+
expect(result.code).toContain('object GetUser');
|
|
88
|
+
expect(result.code).not.toContain('object WatchUsers');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=emit-scope-kotlin.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-scope-kotlin.test.js","sourceRoot":"","sources":["../../../../src/codegen/targets/kotlin/emit-scope-kotlin.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare function kotlinPackageDecl(pkg: string): string;
|
|
2
|
+
export declare function kotlinSourceHashHeader(hash: string): string;
|
|
3
|
+
export declare function kotlinImports(imports: string[]): string;
|
|
4
|
+
export declare function indent(text: string, level: number): string;
|
|
5
|
+
/**
|
|
6
|
+
* Returns a new object containing only the keys of `src` whose values are
|
|
7
|
+
* not `undefined`. Useful for building option objects where unset keys must
|
|
8
|
+
* be ABSENT (not `undefined`-valued), so they don't shadow downstream
|
|
9
|
+
* defaults — e.g., when forwarding into `KotlinEmitOptions`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* pickDefined({ a: 1, b: undefined, c: false }, ['a', 'b', 'c'])
|
|
13
|
+
* // → { a: 1, c: false }
|
|
14
|
+
*/
|
|
15
|
+
export declare function pickDefined<T extends object, K extends keyof T>(src: T, keys: readonly K[]): Partial<Pick<T, K>>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function kotlinPackageDecl(pkg) {
|
|
2
|
+
return `package ${pkg}`;
|
|
3
|
+
}
|
|
4
|
+
export function kotlinSourceHashHeader(hash) {
|
|
5
|
+
return `// Source hash: ${hash}`;
|
|
6
|
+
}
|
|
7
|
+
export function kotlinImports(imports) {
|
|
8
|
+
if (imports.length === 0)
|
|
9
|
+
return '';
|
|
10
|
+
const unique = Array.from(new Set(imports)).sort();
|
|
11
|
+
return unique.map((i) => `import ${i}`).join('\n');
|
|
12
|
+
}
|
|
13
|
+
export function indent(text, level) {
|
|
14
|
+
const prefix = ' '.repeat(level);
|
|
15
|
+
return text
|
|
16
|
+
.split('\n')
|
|
17
|
+
.map((line) => (line.length === 0 ? line : `${prefix}${line}`))
|
|
18
|
+
.join('\n');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns a new object containing only the keys of `src` whose values are
|
|
22
|
+
* not `undefined`. Useful for building option objects where unset keys must
|
|
23
|
+
* be ABSENT (not `undefined`-valued), so they don't shadow downstream
|
|
24
|
+
* defaults — e.g., when forwarding into `KotlinEmitOptions`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* pickDefined({ a: 1, b: undefined, c: false }, ['a', 'b', 'c'])
|
|
28
|
+
* // → { a: 1, c: false }
|
|
29
|
+
*/
|
|
30
|
+
export function pickDefined(src, keys) {
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
const value = src[key];
|
|
34
|
+
if (value !== undefined) {
|
|
35
|
+
out[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=format-kotlin.js.map
|