
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@cdklabs/typewriter
Advanced tools
Write code with an AST builder instead of string concatenation.
import { code, FreeFunction, Module, TypeScriptRenderer } from '@cdklabs/typewriter';
// Create a new module
const scope = new Module('my-package');
// Add a function to the module ...
const fn = new FreeFunction(scope, {
name: 'myFunction',
});
// ... add statements to the function body
fn.addBody(code.stmt.ret(code.expr.lit(1)));
// Emit the code
const renderer = new TypeScriptRenderer();
console.log(renderer.render(scope));
/**
* Prints:
*
* function myFunction(): void {
* return 1;
* }
*/
expr and stmtThe expr and stmt modules provide a set of functions to create expressions and statements programmatically.
These builders help you construct complex code structures with ease.
expr builderThe expr module contains functions for creating various types of expressions.
Here are some examples:
import { code } from '@cdklabs/typewriter';
// or: import { expr } from '@cdklabs/typewriter';
// Create a literal expression
const literalExpr = code.expr.lit(42);
// Create an identifier
const identExpr = code.expr.ident('myVariable');
// Create an object literal
const objectExpr = code.expr.object({ key1: code.expr.lit('value1'), key2: code.expr.lit(2) });
// Create a function call
const callExpr = code.expr.builtInFn('console.log', code.expr.lit('Hello, world!'));
// Create a binary operation
const binOpExpr = code.expr.binOp(code.expr.lit(5), '+', code.expr.lit(3));
stmt builderThe stmt module provides functions for creating various types of statements.
Here are some examples:
import { code } from '@cdklabs/typewriter';
// or: import { stmt, expr } from '@cdklabs/typewriter';
// Create a return statement
const returnStmt = code.stmt.ret(code.expr.lit(42));
// Create an if statement
const ifStmt = code.stmt.if_(code.expr.binOp(code.expr.ident('x'), '>', code.expr.lit(0)))
.then(code.stmt.expr(code.expr.builtInFn('console.log', code.expr.lit('Positive'))))
.else(code.stmt.expr(code.expr.builtInFn('console.log', code.expr.lit('Non-positive'))));
// Create a variable declaration
const varDeclStmt = code.stmt.constVar(code.expr.ident('myVar'), code.expr.lit('Hello'));
// Create a for loop
const forLoopStmt = code.stmt.forConst(
code.expr.ident('i'),
code.expr.builtInFn('Array', code.expr.lit(5)),
code.stmt.expr(code.expr.builtInFn('console.log', code.expr.ident('i')))
);
// Create a block of statements
const blockStmt = code.stmt.block(
varDeclStmt,
forLoopStmt,
returnStmt
);
$E)The Expression Proxy ($E) is a mechanism that allows you to build expression trees by intercepting JavaScript operations and converting them into an AST (Abstract Syntax Tree). It provides a more natural way to write code generation expressions.
import { $E, code } from '@cdklabs/typewriter';
// Create a proxy-wrapped expression
const expression = $E(someExpression);
// Method calls and property access will be converted to expression nodes
expression.someMethod(); // Becomes a method call expression
expression.someProperty; // Becomes a property access expression
The Expression Proxy works through JavaScript's Proxy mechanism and handles three main types of operations:
new creates new instance expressionsimport { $E, code, Module, Type, TypeScriptRenderer } from '@cdklabs/typewriter';
const scope = new Module('example');
// Create a function that uses expression proxies
const fn = scope.addFunction({
name: 'example',
returnType: Type.STRING,
parameters: [{ name: 'value', type: 'number' }],
returnType: 'string',
});
// Using $E to create method chains
fn.addBody(
code.stmt.ret(
$E(code.expr.ident('value')).toString()
)
);
// Renders as:
// function example(value: number): string {
// return value.toString();
// }
// The proxy automatically converts operations into expression nodes
const proxy = $E(someExpression);
proxy.method(); // Creates a method call expression
proxy.property; // Creates a property access expression
new proxy(); // Creates a new instance expression
// Method chains are automatically converted to nested expressions
$E(baseExpression)
.method1()
.method2()
.property;
// Good: Using $E for dynamic method calls
$E(baseExpression).dynamicMethod();
// Less ideal: Manual expression building
code.expr.call(code.expr.prop(baseExpression, 'dynamicMethod'));
// Mixing static and dynamic expressions
code.stmt.ret(
$E(code.expr.lit('hello'))
.toUpperCase()
.concat($E(code.expr.lit(' world')))
);
// The proxy maintains type information from the original expression
const typedExpression = $E(code.expr.lit(42));
// TypeScript will provide proper type hints for number methods
+, -, *, etc.)Use Expression Proxy when:
Use direct expression building when:
This feature is particularly useful when generating code that involves method chaining or complex property access patterns, as it provides a more natural and readable way to construct expression trees compared to manually building them using the expression builder API.
The TypeScriptRenderer is a crucial component of the @cdklabs/typewriter library, responsible for generating TypeScript code from the abstract syntax tree (AST) you've built using the library's constructs.
The TypeScriptRenderer takes the code objects you've created (such as modules, classes, interfaces, and functions) and transforms them into valid TypeScript code. It handles the intricacies of proper indentation, syntax, and structure, ensuring that the output is correct and readable.
Here's an example of how to use the TypeScriptRenderer:
import { Module, TypeScriptRenderer, FreeFunction, code } from '@cdklabs/typewriter';
// Create a scope
const scope = new Module('myModule');
// Add a function to the scope
const myFunction = new FreeFunction(scope, {
name: 'greet',
parameters: [{ name: 'name', type: 'string' }],
returnType: 'string',
});
// Add function body
myFunction.addBody(
code.stmt.ret(code.expr.strConcat(code.expr.lit('Hello, '), code.expr.ident('name'), code.expr.lit('!')))
);
// Create a renderer instance
const renderer = new TypeScriptRenderer();
// Render the scope
const generatedCode = renderer.render(scope);
console.log(generatedCode);
// Output:
// function greet(name: string): string {
// return "Hello, " + name + "!";
// }
The TypeScriptRenderer also provides functionality for managing ESLint rules in the generated code. This is particularly useful when you need to disable certain linting rules for generated code. By default, the prettier/prettier and @stylistic/max-len ESLint rules are disabled.
You can customize the ESLint rule management using the disabledEsLintRules option when creating a TypeScriptRenderer instance:
import { TypeScriptRenderer, EsLintRules } from '@cdklabs/typewriter';
const renderer = new TypeScriptRenderer({
disabledEsLintRules: [
EsLintRules.COMMA_DANGLE,
EsLintRules.MAX_LEN,
],
});
// Or disable all rules by passing an empty array
const noRulesRenderer = new TypeScriptRenderer({
disabledEsLintRules: [],
});
This feature allows you to fine-tune the linting behavior for your generated code, ensuring it meets your project's coding standards while accommodating the nature of generated code.
Start with a Module: Always begin by creating a Module instance, which will serve as the root of your code structure.
Use builders for expressions and statements: Utilize the expr and stmt builders to create expressions and statements. This approach provides better type safety and readability compared to string manipulation.
Leverage type information: When defining functions, properties, or variables, always specify their types using the Type class or its derivatives. This ensures type consistency in the generated code.
Structure your code hierarchically: Use classes like Class, Interface, and Struct to create complex type structures that mirror the desired output.
Render at the end: Build your entire code structure before rendering. Use the TypeScriptRenderer to generate the final TypeScript code.
Utilize documentation features: Add documentation to your generated code using the withComments method available on many classes. This helps in generating well-documented TypeScript code.
Modularize your code generation: For large projects, consider splitting your code generation logic into multiple functions or classes, each responsible for generating a specific part of your TypeScript code.
Test your generated code: After generating the TypeScript code, it's a good practice to compile and test it to ensure it behaves as expected.
The library provides robust support for creating classes and interfaces:
import { Module, ClassType, InterfaceType, Type, MemberVisibility, TypeScriptRenderer, code } from '@cdklabs/typewriter';
const scope = new Module('my-module');
// Create an interface
const myInterface = new InterfaceType(scope, {
name: 'MyInterface',
export: true,
});
myInterface.addProperty({
name: 'name',
type: Type.STRING,
});
myInterface.addProperty({
name: 'age',
type: Type.NUMBER,
optional: true,
});
// Create a class that implements the interface
const myClass = new ClassType(scope, {
name: 'MyClass',
export: true,
implements: [myInterface.type],
});
// Add properties
myClass.addProperty({
name: 'name',
type: Type.STRING,
visibility: MemberVisibility.Public,
});
myClass.addProperty({
name: 'age',
type: Type.NUMBER,
visibility: MemberVisibility.Private,
optional: true,
});
// Add a constructor
myClass.addInitializer({
parameters: [
{ name: 'name', type: Type.STRING },
{ name: 'age', type: Type.NUMBER, optional: true },
],
body: code.stmt.block(
code.stmt.assign(code.expr.get(code.expr.this_(), 'name'), code.expr.ident('name')),
code.stmt.assign(code.expr.get(code.expr.this_(), 'age'), code.expr.ident('age')),
),
});
// Add a method
myClass.addMethod({
name: 'greet',
returnType: Type.STRING,
body: code.stmt.block(
code.stmt.ret(
code.expr.strConcat(
code.expr.lit('Hello, my name is '),
code.expr.get(code.expr.this_(), 'name'),
),
),
),
});
const renderer = new TypeScriptRenderer();
console.log(renderer.render(scope));
You can add comments to various code elements:
import { Module, FreeFunction, Type, code, TypeScriptRenderer } from '@cdklabs/typewriter';
const scope = new Module('my-module');
const fn = new FreeFunction(scope, {
name: 'calculate',
returnType: Type.NUMBER,
});
// Add JSDoc comments
fn.withComments(
'Calculate a complex value',
'',
'@returns The calculated result',
);
fn.addBody(
code.commentOn(
code.stmt.ret(code.expr.lit(42)),
'Return the answer to everything',
),
);
const renderer = new TypeScriptRenderer();
console.log(renderer.render(scope));
// Output:
// /**
// * Calculate a complex value
// *
// * @returns The calculated result
// */
// function calculate(): number {
// // Return the answer to everything
// return 42;
// }
FAQs
Write typed code for jsii
We found that @cdklabs/typewriter demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.