Skip to content

Commit

Permalink
Allow to name the constants of a string union by configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Vampire committed Sep 23, 2024
1 parent 425bd90 commit 166e764
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 5 deletions.
14 changes: 14 additions & 0 deletions schema/karakum-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,20 @@
],
"title": "plugins"
},
"unionNameResolvers": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "string"
}
],
"title": "unionNameResolvers"
},
"varianceModifiers": {
"anyOf": [
{
Expand Down
3 changes: 3 additions & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export interface SchemaConfiguration {
* */
packageNameMapper?: Record<string, string>

unionNameResolvers?: string | string[]

/**
* @TJS-type object
* @additionalProperties { "type": "array", "items": { "type": "string" } }
Expand Down Expand Up @@ -118,6 +120,7 @@ export interface Configuration extends PartialConfiguration {

moduleNameMapper: Record<string, string>
packageNameMapper: Record<string, string>
unionNameResolvers: string[]

importInjector: Record<string, string[]>
importMapper: Record<string, string | Record<string, string>>
Expand Down
6 changes: 6 additions & 0 deletions src/configuration/defaultizeConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const defaultNameResolverPatterns = [
"karakum/nameResolvers/*.js"
]

const defaultUnionNameResolverPatterns = [
"karakum/unionNameResolvers/*.js"
]

const defaultInheritanceModifierPatterns = [
"karakum/inheritanceModifiers/*.js"
]
Expand Down Expand Up @@ -109,6 +113,8 @@ export async function defaultizeConfiguration(configuration: PartialConfiguratio

inheritanceModifiers: normalizeOption(configuration.inheritanceModifiers, defaultInheritanceModifierPatterns),

unionNameResolvers: normalizeOption(configuration.unionNameResolvers, defaultUnionNameResolverPatterns),

varianceModifiers: normalizeOption(configuration.varianceModifiers, defaultVarianceModifierPatterns),

moduleNameMapper: configuration.moduleNameMapper ?? {},
Expand Down
17 changes: 13 additions & 4 deletions src/converter/plugins/StringUnionTypePlugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ts, {LiteralTypeNode, StringLiteral, UnionTypeNode} from "typescript";
import {escapeIdentifier, notEscapedIdentifier} from "../../utils/strings.js";
import {CheckCoverageService, checkCoverageServiceKey} from "./CheckCoveragePlugin.js";
import {UnionNameResolverService, unionNameResolverServiceKey} from "./UnionNameResolverPlugin.js";
import {ConverterContext} from "../context.js";
import {createAnonymousDeclarationPlugin} from "./AnonymousDeclarationPlugin.js";
import {flatUnionTypes, isNullableType} from "./NullableUnionTypePlugin.js";
Expand Down Expand Up @@ -48,6 +49,8 @@ export function convertStringUnionType(
const checkCoverageService = context.lookupService<CheckCoverageService>(checkCoverageServiceKey)
checkCoverageService?.cover(node)

const unionNameResolverService = context.lookupService<UnionNameResolverService>(unionNameResolverServiceKey)
if (unionNameResolverService === undefined) throw new Error("UnionNameResolverService required")
const typeScriptService = context.lookupService<TypeScriptService>(typeScriptServiceKey)
const namespaceInfoService = context.lookupService<NamespaceInfoService>(namespaceInfoServiceKey)
const injectionService = context.lookupService<InjectionService>(injectionServiceKey)
Expand All @@ -69,10 +72,16 @@ export function convertStringUnionType(
checkCoverageService?.cover(literal)

const value = literal.text
const valueAsIdentifier = notEscapedIdentifier(value)
const key = (value === "") || (valueAsIdentifier === "")
? "_"
: valueAsIdentifier

let key = unionNameResolverService.resolveUnionName(literal, context)

if (!key) {
const valueAsIdentifier = notEscapedIdentifier(value)
key = (value === "") || (valueAsIdentifier === "")
? "_"
: valueAsIdentifier
}

return [key, value] as const
})

Expand Down
56 changes: 56 additions & 0 deletions src/converter/plugins/UnionNameResolverPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import ts, {Node} from "typescript";
import {ConverterPlugin} from "../plugin.js";
import {ConverterContext} from "../context.js";
import {Render} from "../render.js";
import {UnionNameResolver} from "../unionNameResolver.js";
import {GeneratedFile} from "../generated.js";

export const unionNameResolverServiceKey = Symbol()

export class UnionNameResolverService {
private readonly unionNameResolvers: UnionNameResolver[]
private readonly resolvedNodes = new Map<Node, string>()

constructor(unionNameResolvers: UnionNameResolver[]) {
this.unionNameResolvers = unionNameResolvers
}

resolveUnionName(node: Node, context: ConverterContext): string | null {
const resolvedName = this.resolvedNodes.get(node)
if (resolvedName) return resolvedName

for (const unionNameResolver of this.unionNameResolvers) {
const result = unionNameResolver(node, context)

if (result !== null) {
this.resolvedNodes.set(node, result)
return result
}
}

return null
}
}

export class UnionNameResolverPlugin implements ConverterPlugin {
private readonly unionNameResolverService: UnionNameResolverService;

constructor(unionNameResolvers: UnionNameResolver[]) {
this.unionNameResolverService = new UnionNameResolverService(unionNameResolvers)
}

generate(): GeneratedFile[] {
return [];
}

render(node: Node, context: ConverterContext, next: Render): string | null {
return null;
}

traverse(node: ts.Node, context: ConverterContext): void {
}

setup(context: ConverterContext): void {
context.registerService(unionNameResolverServiceKey, this.unionNameResolverService)
}
}
5 changes: 5 additions & 0 deletions src/converter/unionNameResolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {ConverterContext} from "./context.js";
import {Node} from "typescript"

export type UnionNameResolver<TNode extends Node = Node> =
(node: TNode, context: ConverterContext) => string | null
4 changes: 4 additions & 0 deletions src/defaultPlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ts, {Node, Program} from "typescript";
import {Configuration} from "./configuration/configuration.js";
import {ConverterPlugin} from "./converter/plugin.js";
import {NameResolver} from "./converter/nameResolver.js";
import {UnionNameResolver} from "./converter/unionNameResolver.js";
import {ConfigurationPlugin} from "./converter/plugins/ConfigurationPlugin.js";
import {CheckKindsPlugin} from "./converter/plugins/CheckKindsPlugin.js";
import {CheckCoveragePlugin} from "./converter/plugins/CheckCoveragePlugin.js";
Expand Down Expand Up @@ -44,6 +45,7 @@ import {convertTypeOperator} from "./converter/plugins/convertTypeOperator.js";
import {convertImportType} from "./converter/plugins/convertImportType.js";
import {convertPropertyAccessExpression} from "./converter/plugins/convertPropertyAccessExpression.js";
import {NameResolverPlugin} from "./converter/plugins/NameResolverPlugin.js";
import {UnionNameResolverPlugin} from "./converter/plugins/UnionNameResolverPlugin.js";
import {InheritanceModifierPlugin} from "./converter/plugins/InheritanceModifierPlugin.js";
import {InheritanceModifier} from "./converter/inheritanceModifier.js";
import {mappedTypePlugin} from "./converter/plugins/MappedTypePlugin.js";
Expand Down Expand Up @@ -72,6 +74,7 @@ export const createPlugins = (
injections: Injection[],
nameResolvers: NameResolver[],
inheritanceModifiers: InheritanceModifier[],
unionNameResolvers: UnionNameResolver[],
varianceModifiers: VarianceModifier[],
program: Program,
namespaceInfo: NamespaceInfo,
Expand All @@ -82,6 +85,7 @@ export const createPlugins = (
new InjectionPlugin(injections),
new NameResolverPlugin(nameResolvers),
new InheritanceModifierPlugin(inheritanceModifiers),
new UnionNameResolverPlugin(unionNameResolvers),
new VarianceModifierPlugin(varianceModifiers),
new NamespaceInfoPlugin(namespaceInfo),
new ImportInfoPlugin(program, importInfo),
Expand Down
9 changes: 9 additions & 0 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {minimatch} from "minimatch";
import {createRender} from "./converter/render.js";
import {NameResolver} from "./converter/nameResolver.js";
import {AnnotationPlugin} from "./converter/plugins/AnnotationPlugin.js";
import {UnionNameResolver} from "./converter/unionNameResolver.js";
import {InheritanceModifier} from "./converter/inheritanceModifier.js";
import {Annotation} from "./converter/annotation.js";
import {collectNamespaceInfo} from "./structure/namespace/collectNamespaceInfo.js";
Expand Down Expand Up @@ -98,6 +99,7 @@ export async function generate(partialConfiguration: PartialConfiguration) {
annotations,
nameResolvers,
inheritanceModifiers,
unionNameResolvers,
varianceModifiers,
compilerOptions,
cwd,
Expand Down Expand Up @@ -147,6 +149,12 @@ export async function generate(partialConfiguration: PartialConfiguration) {
cwd,
)

const customUnionNameResolvers = await loadExtensions<UnionNameResolver>(
"Union Name Resolver",
unionNameResolvers,
cwd,
)

const customVarianceModifiers = await loadExtensions<VarianceModifier>(
"Variance Modifier",
varianceModifiers,
Expand Down Expand Up @@ -222,6 +230,7 @@ export async function generate(partialConfiguration: PartialConfiguration) {
customInjections,
customNameResolvers,
customInheritanceModifiers,
customUnionNameResolvers,
customVarianceModifiers,
program,
namespaceInfo,
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export type {NameResolver} from "./converter/nameResolver.js"
// inheritance modifier
export type {InheritanceModifier, InheritanceModifierContext} from "./converter/inheritanceModifier.js"

// union name resolver
export type {UnionNameResolver} from "./converter/unionNameResolver.js"

// variance modifier
export type {VarianceModifier} from "./converter/varianceModifier.js"

Expand Down Expand Up @@ -58,6 +61,10 @@ export {
inheritanceModifierServiceKey,
type InheritanceModifierService
} from "./converter/plugins/InheritanceModifierPlugin.js"
export {
unionNameResolverServiceKey,
type UnionNameResolverService
} from "./converter/plugins/UnionNameResolverPlugin.js"
export {
varianceModifierServiceKey,
type VarianceModifierService
Expand Down
36 changes: 35 additions & 1 deletion test/functional/base/generated/union/stringEnum.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,41 @@ val __4: Operator
@seskar.js.JsValue("<=")
val __5: Operator
@seskar.js.JsValue(">=")
val __6: Operator
val GTE: Operator
}
}

sealed external interface Operator2 {
companion object {
@seskar.js.JsValue("")
val EMPTY: Operator2
@seskar.js.JsValue("=")
val EQUAL: Operator2
@seskar.js.JsValue("<")
val LT: Operator2
@seskar.js.JsValue(">")
val GT: Operator2
@seskar.js.JsValue("<=")
val LTE: Operator2
@seskar.js.JsValue(">=")
val GTE: Operator2
}
}

sealed external interface Operator3 {
companion object {
@seskar.js.JsValue("")
val OPERATOR: Operator3
@seskar.js.JsValue("=")
val OPERATOR_2: Operator3
@seskar.js.JsValue("<")
val OPERATOR_3: Operator3
@seskar.js.JsValue(">")
val OPERATOR_4: Operator3
@seskar.js.JsValue("<=")
val OPERATOR_5: Operator3
@seskar.js.JsValue(">=")
val OPERATOR_6: Operator3
}
}
sealed external interface SwitcherResult {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import ts from "typescript";

export default (node) => {
if (!ts.isStringLiteral(node)) return null
if (!ts.isLiteralTypeNode(node.parent)) return null
if (!ts.isUnionTypeNode(node.parent.parent)) return null
if (!ts.isTypeAliasDeclaration(node.parent.parent.parent)) return null
if (!ts.isIdentifier(node.parent.parent.parent.name)) return null

const name = node.parent.parent.parent.name.text
if (name === "Operator2") {
switch (node.text) {
case "":
return "EMPTY"
case "=":
return "EQUAL"
case "<":
return "LT"
case ">":
return "GT"
case "<=":
return "LTE"
}
} else if (name === "Operator3") {
return "OPERATOR"
}

if (name.startsWith("Operator") && (node.text === ">=")) {
return "GTE"
}

return null
}
4 changes: 4 additions & 0 deletions test/functional/base/lib/union/stringEnum.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ export declare type FormEncType = "application/x-www-form-urlencoded" | "multipa
export declare function switcher(): "on" | "off"

export declare type Operator = "" | "=" | "<" | ">" | "<=" | ">=";

export declare type Operator2 = "" | "=" | "<" | ">" | "<=" | ">=";

export declare type Operator3 = "" | "=" | "<" | ">" | "<=" | ">=";

0 comments on commit 166e764

Please sign in to comment.