Skip to content

Commit

Permalink
packages:js:add-features-to-codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
Voldemat committed Nov 13, 2023
1 parent dd1863c commit 43b979c
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 63 deletions.
1 change: 0 additions & 1 deletion packages/js/src/cli/actions/genDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export class GenDiffAction implements IAction {
}
const spec = specResult.data
spec.designSystems = []
spec.features = []
const code = this.container.fsUtils.readGeneratedFiles(outputDir)
const codeSpec = this.container.specGenerator.fromCode(code)
if (this.container.specUtils.isEqual(spec, codeSpec)) {
Expand Down
38 changes: 24 additions & 14 deletions packages/js/src/generators/code/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TechSpec } from '../../spec/types'

export abstract class BaseCodeGenerator<T extends TechSpec> {
abstract genCode (items: T[]): ts.Node[]
buildVariable (
protected buildVariable (
name: string, value: ts.Expression, isExported: boolean = false
): ts.VariableStatement {
const modifiers: ts.ModifierLike[] = []
Expand All @@ -26,7 +26,7 @@ export abstract class BaseCodeGenerator<T extends TechSpec> {
)
}

buildAsConst (value: ts.Expression): ts.Expression {
protected buildAsConst (value: ts.Expression): ts.AsExpression {
return ts.factory.createAsExpression(
value,
ts.factory.createTypeReferenceNode(
Expand All @@ -36,55 +36,65 @@ export abstract class BaseCodeGenerator<T extends TechSpec> {
)
}

buildObject (
protected buildStringOrNumber (
value: string | number
): ts.StringLiteral | ts.NumericLiteral {
if (typeof value === 'string') return this.buildString(value)
return this.buildNumber(value)
}

protected buildObject (
properties: ts.PropertyAssignment[],
multiline: boolean = true
): ts.ObjectLiteralExpression {
return ts.factory.createObjectLiteralExpression(properties, multiline)
}

buildProperty (name: string, value: ts.Expression): ts.PropertyAssignment {
protected buildProperty (
name: string,
value: ts.Expression
): ts.PropertyAssignment {
return ts.factory.createPropertyAssignment(
ts.factory.createIdentifier(name),
value
)
}

buildStringProperties (
protected buildStringProperties (
items: Array<[string, ts.Expression]>
): ts.PropertyAssignment[] {
return items.map(([key, value]) => this.buildProperty(key, value))
}

buildBoolean (value: boolean): ts.BooleanLiteral {
protected buildBoolean (value: boolean): ts.BooleanLiteral {
return value ? ts.factory.createTrue() : ts.factory.createFalse()
}

buildStringOrNull (
protected buildStringOrNull (
value: string | null
): ts.StringLiteral | ts.NullLiteral {
return value !== null
? this.buildString(value)
: ts.factory.createNull()
}

buildString (value: string): ts.StringLiteral {
protected buildString (value: string): ts.StringLiteral {
return ts.factory.createStringLiteral(value)
}

buildNumber (value: number): ts.NumericLiteral {
protected buildNumber (value: number): ts.NumericLiteral {
return ts.factory.createNumericLiteral(value)
}

buildNumberOrNull (
protected buildNumberOrNull (
value: number | null
): ts.NumericLiteral | ts.NullLiteral {
return value !== null
? this.buildNumber(value)
: ts.factory.createNull()
}

buildImportStatement (
protected buildImportStatement (
imports: string[], from: string
): ts.ImportDeclaration {
return ts.factory.createImportDeclaration(
Expand All @@ -107,15 +117,15 @@ export abstract class BaseCodeGenerator<T extends TechSpec> {
)
}

buildArray (items: ts.Expression[]): ts.ArrayLiteralExpression {
protected buildArray (items: ts.Expression[]): ts.ArrayLiteralExpression {
return ts.factory.createArrayLiteralExpression(items)
}

buildStringArray (items: string[]): ts.ArrayLiteralExpression {
protected buildStringArray (items: string[]): ts.ArrayLiteralExpression {
return this.buildArray(items.map(this.buildString))
}

buildStringArrayOrNull (
protected buildStringArrayOrNull (
items: string[] | null
): ts.ArrayLiteralExpression | ts.NullLiteral {
return items === null
Expand Down
51 changes: 51 additions & 0 deletions packages/js/src/generators/code/features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type ts from 'typescript'
import { type Feature } from '../../spec/types'
import { BaseCodeGenerator } from './base'
import type { FeatureFieldSpec, FeatureSpec } from '../../spec/types/feature'

export class FeaturesCodeGenerator extends BaseCodeGenerator<Feature> {
genCode (items: Feature[]): ts.Node[] {
return items
.map(item => this.genFeatureVariable(item))
.filter((item): item is ts.VariableStatement => item !== null)
}

private genFeatureVariable (
item: Feature
): ts.VariableStatement | null {
return this.buildVariable(
this.featureNameToCodeName(item.metadata.name),
this.buildFeatureValue(item.spec),
true
)
}

private featureNameToCodeName (name: string): string {
return name + 'Feature'
}

private buildFeatureValue (spec: FeatureSpec): ts.AsExpression {
return this.buildAsConst(
this.buildObject(
this.buildStringProperties(
Object.entries(spec)
.map(([pName, pValue]): [
string, ts.ObjectLiteralExpression
] => ([pName, this.buildFeatureFieldValue(pValue)]))
),
true
)
)
}

private buildFeatureFieldValue (
spec: FeatureFieldSpec
): ts.ObjectLiteralExpression {
return this.buildObject(
this.buildStringProperties([
['type', this.buildString(spec.type)],
['value', this.buildStringOrNumber(spec.value)]
]), true
)
}
}
11 changes: 8 additions & 3 deletions packages/js/src/generators/code/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import type { TechSpec, TechSpecContainer } from '../../spec/types'
import { FormsCodeGenerator } from '../code/forms'
import { DesignSystemCodeGenerator } from './designSystem'
import { TypesCodeGenerator } from './types'
import { FeaturesCodeGenerator } from './features'

export class CodeFactory {
private readonly printer: Printer
private readonly sourceFile: SourceFile
private readonly forms: FormsCodeGenerator
private readonly designSystems: DesignSystemCodeGenerator
private readonly types: TypesCodeGenerator
private readonly features: FeaturesCodeGenerator
constructor () {
this.printer = ts.createPrinter({ })
this.sourceFile = ts.createSourceFile(
Expand All @@ -22,6 +24,7 @@ export class CodeFactory {
this.types = new TypesCodeGenerator()
this.forms = new FormsCodeGenerator(this.types)
this.designSystems = new DesignSystemCodeGenerator()
this.features = new FeaturesCodeGenerator()
}

generate (
Expand All @@ -32,21 +35,23 @@ export class CodeFactory {
DesignSystem: this.render(
this.designSystems.genCode(spec.designSystems)
),
feature: undefined,
feature: this.render(
this.features.genCode(spec.features)
),
type: this.render(this.types.genCode(spec.types))
}
}

protected render (nodes: ts.Node[]): string | undefined {
if (nodes.length === 0) return undefined
const string = this.printer.printList(
const sourceCode = this.printer.printList(
ts.ListFormat.MultiLine,
ts.factory.createNodeArray(nodes),
this.sourceFile
)
return JSON.parse(
JSON.stringify(
string.replace(/\\u/g, '%u')
sourceCode.replace(/\\u/g, '%u')
).replace(/%u/g, '\\u')
)
}
Expand Down
31 changes: 30 additions & 1 deletion packages/js/src/generators/spec/generators/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export abstract class BaseSpecGenerator<T extends TechSpec> {
abstract getSpec (nodes: ts.Node[]): T[]
protected abstract codeNameToSpecName (codeName: string): string

protected buildFieldErrorMessage (field: string): string {
return `"${field}" property is not defined or has invalid type`
}

protected extractRegex (node: ts.RegularExpressionLiteral): RegExp {
return new RegExp(node.text.slice(1, node.text.length - 1))
}
Expand Down Expand Up @@ -42,7 +46,7 @@ export abstract class BaseSpecGenerator<T extends TechSpec> {
if (
declaration.initializer?.kind !==
ts.SyntaxKind.AsExpression &&
(declaration.initializer as ts.AsExpression).expression.kind !==
(declaration.initializer as ts.AsExpression).expression?.kind !==
ts.SyntaxKind.ObjectLiteralExpression
) return null
const value = (
Expand Down Expand Up @@ -92,4 +96,29 @@ export abstract class BaseSpecGenerator<T extends TechSpec> {
case ts.SyntaxKind.NullKeyword: return null
}
}

protected extractKeyFromProperties<T extends ts.Expression> (
properties: Array<[string, ts.Expression]>,
key: string,
kind: T['kind']
): T | null {
for (const [pName, pValue] of properties) {
if (pName === key && pValue.kind === kind) return pValue as T
}
return null
}

protected filterObjectProperties (
properties: ts.NodeArray<ts.ObjectLiteralElementLike>
): Array<[string, ts.Expression]> {
return properties
.filter((el): el is ts.PropertyAssignment => (
el.kind === ts.SyntaxKind.PropertyAssignment
))
.map((el): [string, ts.Expression] | null => {
if (el.name.kind !== ts.SyntaxKind.Identifier) return null
return [el.name.text, el.initializer]
})
.filter((el): el is [string, ts.Expression] => el !== null)
}
}
Loading

0 comments on commit 43b979c

Please sign in to comment.