({ debug: false });
function toYargsHandler(
- Command: React.FC,
- props: P,
- cliOptions?: { validateDefault?: boolean }
+ Command: React.FC,
+ props: P,
+ cliOptions?: { validateDefault?: boolean },
) {
- // Return a closure which yargs will execute if this command is run.
- return async (args: yargs.Arguments) => {
- let anonymousId = 'unknown'
- try {
- anonymousId = await getAnonymousId()
- } catch (error) {}
+ // Return a closure which yargs will execute if this command is run.
+ return async (args: yargs.Arguments) => {
+ let anonymousId = 'unknown';
+ try {
+ anonymousId = await getAnonymousId();
+ } catch (error) {}
- try {
- // The '*' command is a catch-all. We want to fail the CLI if an unknown command is
- // supplied ('yarn rudder-typer footothebar'), instead of just running the default command.
- const isValidCommand =
- !cliOptions ||
- !cliOptions.validateDefault ||
- args._.length === 0 ||
- ['update', 'u'].includes(args._[0] as string)
+ try {
+ // The '*' command is a catch-all. We want to fail the CLI if an unknown command is
+ // supplied ('yarn rudder-typer footothebar'), instead of just running the default command.
+ const isValidCommand =
+ !cliOptions ||
+ !cliOptions.validateDefault ||
+ args._.length === 0 ||
+ ['update', 'u'].includes(args._[0] as string);
- // Attempt to read a config, if one is available.
- const cfg = await getConfig(args.config)
+ // Attempt to read a config, if one is available.
+ const cfg = await getConfig(args.config);
- const analyticsProps = await rudderTyperLibraryProperties(args, cfg)
+ const analyticsProps = await rudderTyperLibraryProperties(args, cfg);
- // Figure out which component to render.
- let Component = Command
- // Certain flags (--version, --help) will overide whatever command was provided.
- if (!!args.version || !!args.v || Command.displayName === Version.displayName) {
- // We override the --version flag from yargs with our own output. If it was supplied, print
- // the `version` component instead.
- Component = Version as typeof Command
- } else if (
- !isValidCommand ||
- !!args.help ||
- !!args.h ||
- args._.includes('help') ||
- Command.displayName === Help.displayName
- ) {
- // Same goes for the --help flag.
- Component = Help as typeof Command
- }
+ // Figure out which component to render.
+ let Component = Command;
+ // Certain flags (--version, --help) will overide whatever command was provided.
+ if (!!args.version || !!args.v || Command.displayName === Version.displayName) {
+ // We override the --version flag from yargs with our own output. If it was supplied, print
+ // the `version` component instead.
+ Component = Version as typeof Command;
+ } else if (
+ !isValidCommand ||
+ !!args.help ||
+ !!args.h ||
+ args._.includes('help') ||
+ Command.displayName === Help.displayName
+ ) {
+ // Same goes for the --help flag.
+ Component = Help as typeof Command;
+ }
- // 🌟Render the command.
- try {
- const { waitUntilExit } = render(
-
-
-
-
- ,
- { debug: !!args.debug }
- )
- await waitUntilExit()
- } catch (err) {
- // Errors are handled/reported in ErrorBoundary.
- process.exitCode = 1
- }
+ // 🌟Render the command.
+ try {
+ const { waitUntilExit } = render(
+
+
+
+
+ ,
+ { debug: !!args.debug },
+ );
+ await waitUntilExit();
+ } catch (err) {
+ // Errors are handled/reported in ErrorBoundary.
+ process.exitCode = 1;
+ }
- // If this isn't a valid command, make sure we exit with a non-zero exit code.
- if (!isValidCommand) {
- process.exitCode = 1
- }
- } catch (error) {
- // If an error was thrown in the command logic above (but outside of the ErrorBoundary in Component)
- // then render an ErrorBoundary.
- try {
- const { waitUntilExit } = render(
-
-
- ,
- {
- debug: !!args.debug,
- }
- )
- await waitUntilExit()
- } catch {
- // Errors are handled/reported in ErrorBoundary.
- process.exitCode = 1
- }
- }
- }
+ // If this isn't a valid command, make sure we exit with a non-zero exit code.
+ if (!isValidCommand) {
+ process.exitCode = 1;
+ }
+ } catch (error) {
+ // If an error was thrown in the command logic above (but outside of the ErrorBoundary in Component)
+ // then render an ErrorBoundary.
+ try {
+ const { waitUntilExit } = render(
+
+
+ ,
+ {
+ debug: !!args.debug,
+ },
+ );
+ await waitUntilExit();
+ } catch {
+ // Errors are handled/reported in ErrorBoundary.
+ process.exitCode = 1;
+ }
+ }
+ };
}
/** Helper to fetch the name of the current yargs CLI command. */
function getCommand(args: yargs.Arguments) {
- return args._.length === 0 ? 'update' : args._.join(' ')
+ return args._.length === 0 ? 'update' : args._.join(' ');
}
/**
* Helper to generate the shared library properties shared by all analytics calls.
*/
async function rudderTyperLibraryProperties(
- args: yargs.Arguments,
- cfg: Config | undefined = undefined
+ args: yargs.Arguments,
+ cfg: Config | undefined = undefined,
) {
- // In CI environments, or if there is no internet, we may not be able to execute the
- // the token script.
- let tokenMethod = undefined
- try {
- tokenMethod = await getTokenMethod(cfg, args.config)
- } catch {}
+ // In CI environments, or if there is no internet, we may not be able to execute the
+ // the token script.
+ let tokenMethod = undefined;
+ try {
+ tokenMethod = await getTokenMethod(cfg, args.config);
+ } catch {}
- // Attempt to read the name of the Tracking Plan from a local `plan.json`.
- // If this fails, that's fine -- we'll still have the id from the config.
- let trackingPlanName = ''
- try {
- if (cfg && cfg.trackingPlans.length > 0) {
- const tp = await loadTrackingPlan(args.config, cfg.trackingPlans[0])
- if (tp) {
- trackingPlanName = tp.display_name
- }
- }
- } catch {}
+ // Attempt to read the name of the Tracking Plan from a local `plan.json`.
+ // If this fails, that's fine -- we'll still have the id from the config.
+ let trackingPlanName = '';
+ try {
+ if (cfg && cfg.trackingPlans.length > 0) {
+ const tp = await loadTrackingPlan(args.config, cfg.trackingPlans[0]);
+ if (tp) {
+ trackingPlanName = getTrackingPlanName(tp);
+ }
+ }
+ } catch {}
- return {
- version,
- client: cfg && {
- language: cfg.client.language,
- sdk: cfg.client.sdk,
- },
- command: getCommand(args),
- is_ci: Boolean(process.env.CI),
- token_method: tokenMethod,
- tracking_plan:
- cfg && cfg.trackingPlans && cfg.trackingPlans.length > 0
- ? {
- name: trackingPlanName,
- id: cfg.trackingPlans[0].id,
- workspace_slug: cfg.trackingPlans[0].workspaceSlug,
- }
- : undefined,
- }
+ return {
+ version,
+ client: cfg && {
+ language: cfg.client.language,
+ sdk: cfg.client.sdk,
+ },
+ command: getCommand(args),
+ is_ci: Boolean(process.env.CI),
+ token_method: tokenMethod,
+ tracking_plan:
+ cfg && cfg.trackingPlans && cfg.trackingPlans.length > 0
+ ? {
+ name: trackingPlanName,
+ id: cfg.trackingPlans[0].id,
+ workspace_slug: cfg.trackingPlans[0].workspaceSlug,
+ }
+ : undefined,
+ };
}
/**
@@ -274,5 +275,5 @@ async function rudderTyperLibraryProperties(
* the same user together.
*/
async function getAnonymousId() {
- return await machineId(false)
+ return await machineId(false);
}
diff --git a/src/generators/android/android.ts b/src/generators/android/android.ts
index 4fc2ec15..0e53fbbd 100644
--- a/src/generators/android/android.ts
+++ b/src/generators/android/android.ts
@@ -1,153 +1,153 @@
-import { camelCase, upperFirst } from 'lodash'
-import { Type, Schema, getPropertiesSchema } from '../ast'
-import { Generator, GeneratorClient } from '../gen'
+import { camelCase, upperFirst } from 'lodash';
+import { Type, Schema, getPropertiesSchema } from '../ast';
+import { Generator, GeneratorClient } from '../gen';
// These contexts are what will be passed to Handlebars to perform rendering.
// Everything in these contexts should be properly sanitized.
type AndroidObjectContext = {
- // The formatted name for this object, ex: "ProductClicked"
- name: string
-}
+ // The formatted name for this object, ex: "ProductClicked"
+ name: string;
+};
type AndroidPropertyContext = {
- // The formatted name for this property, ex: "numAvocados".
- name: string
- // The type of this property. ex: "String".
- type: string
- // Whether the property is nullable (@NonNull vs @Nullable modifier).
- isVariableNullable: boolean
- // Whether runtime error should be thrown for null payload value
- shouldThrowRuntimeError: boolean | undefined
- // Whether this is a List property
- isListType: boolean
- // Whether this is property can be serialized with toProperties()
- implementsSerializableProperties: boolean
-}
+ // The formatted name for this property, ex: "numAvocados".
+ name: string;
+ // The type of this property. ex: "String".
+ type: string;
+ // Whether the property is nullable (@NonNull vs @Nullable modifier).
+ isVariableNullable: boolean;
+ // Whether runtime error should be thrown for null payload value
+ shouldThrowRuntimeError: boolean | undefined;
+ // Whether this is a List property
+ isListType: boolean;
+ // Whether this is property can be serialized with toProperties()
+ implementsSerializableProperties: boolean;
+};
type AndroidTrackCallContext = {
- // The formatted function name, ex: "orderCompleted".
- functionName: string
- propsType: string
- propsParam: boolean
-}
+ // The formatted function name, ex: "orderCompleted".
+ functionName: string;
+ propsType: string;
+ propsParam: boolean;
+};
export const android: Generator<
- Record,
- AndroidTrackCallContext,
- AndroidObjectContext,
- AndroidPropertyContext
+ Record,
+ AndroidTrackCallContext,
+ AndroidObjectContext,
+ AndroidPropertyContext
> = {
- generatePropertiesObject: true,
- namer: {
- // See: https://github.com/AnanthaRajuCprojects/Reserved-Key-Words-list-of-various-programming-languages/blob/master/Java%20Keywords%20List.md
- // prettier-ignore
- reservedWords: [
+ generatePropertiesObject: true,
+ namer: {
+ // See: https://github.com/AnanthaRajuCprojects/Reserved-Key-Words-list-of-various-programming-languages/blob/master/Java%20Keywords%20List.md
+ // prettier-ignore
+ reservedWords: [
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
"continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float",
"for", "goto", "if", "implement", "imports", "instanceof", "int", "interface", "long", "native",
"new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super",
"switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while"
],
- quoteChar: '"',
- allowedIdentifierStartingChars: 'A-Za-z_',
- allowedIdentifierChars: 'A-Za-z0-9_',
- },
- generatePrimitive: async (client, schema, parentPath) => {
- let type = 'Object'
+ quoteChar: '"',
+ allowedIdentifierStartingChars: 'A-Za-z_',
+ allowedIdentifierChars: 'A-Za-z0-9_',
+ },
+ generatePrimitive: async (client, schema, parentPath) => {
+ let type = 'Object';
- if (schema.type === Type.STRING) {
- type = 'String'
- } else if (schema.type === Type.BOOLEAN) {
- type = 'Boolean'
- } else if (schema.type === Type.INTEGER) {
- type = 'Long'
- } else if (schema.type === Type.NUMBER) {
- type = 'Double'
- }
+ if (schema.type === Type.STRING) {
+ type = 'String';
+ } else if (schema.type === Type.BOOLEAN) {
+ type = 'Boolean';
+ } else if (schema.type === Type.INTEGER) {
+ type = 'Long';
+ } else if (schema.type === Type.NUMBER) {
+ type = 'Double';
+ }
- return {
- ...defaultPropertyContext(client, schema, type, parentPath),
- }
- },
- setup: async () => ({}),
- generateArray: async (client, schema, items, parentPath) => {
- return {
- ...defaultPropertyContext(client, schema, `List<${items.type}>`, parentPath),
- isListType: true,
- }
- },
- generateObject: async (client, schema, properties, parentPath) => {
- const property = defaultPropertyContext(client, schema, 'Object', parentPath)
- let object: AndroidObjectContext | undefined
+ return {
+ ...defaultPropertyContext(client, schema, type, parentPath),
+ };
+ },
+ setup: async () => ({}),
+ generateArray: async (client, schema, items, parentPath) => {
+ return {
+ ...defaultPropertyContext(client, schema, `List<${items.type}>`, parentPath),
+ isListType: true,
+ };
+ },
+ generateObject: async (client, schema, properties, parentPath) => {
+ const property = defaultPropertyContext(client, schema, 'Object', parentPath);
+ let object: AndroidObjectContext | undefined;
- if (properties.length > 0) {
- const className = client.namer.register(schema.name, 'class', {
- transform: (name: string) => {
- return upperFirst(camelCase(name))
- },
- })
+ if (properties.length > 0) {
+ const className = client.namer.register(schema.name, 'class', {
+ transform: (name: string) => {
+ return upperFirst(camelCase(name));
+ },
+ });
- property.type = className
- property.implementsSerializableProperties = true
- object = {
- name: className,
- }
- }
+ property.type = className;
+ property.implementsSerializableProperties = true;
+ object = {
+ name: className,
+ };
+ }
- return { property, object }
- },
- generateUnion: async (client, schema, _, parentPath) => {
- // TODO: support unions
- return defaultPropertyContext(client, schema, 'Object', parentPath)
- },
- generateTrackCall: async (_client, schema, functionName, propertiesObject) => {
- const { properties } = getPropertiesSchema(schema)
- return {
- class: schema.name.replace(/\s/g, ''),
- functionName: functionName,
- propsType: propertiesObject.type,
- propsParam: !!properties.length,
- }
- },
- generateRoot: async (client, context) => {
- await Promise.all([
- client.generateFile(
- 'RudderTyperAnalytics.java',
- 'generators/android/templates/RudderTyperAnalytics.java.hbs',
- context
- ),
- client.generateFile(
- 'RudderTyperUtils.java',
- 'generators/android/templates/RudderTyperUtils.java.hbs',
- context
- ),
- client.generateFile(
- 'SerializableProperties.java',
- 'generators/android/templates/SerializableProperties.java.hbs',
- context
- ),
- ...context.objects.map(o =>
- client.generateFile(`${o.name}.java`, 'generators/android/templates/class.java.hbs', o)
- ),
- ])
- },
-}
+ return { property, object };
+ },
+ generateUnion: async (client, schema, _, parentPath) => {
+ // TODO: support unions
+ return defaultPropertyContext(client, schema, 'Object', parentPath);
+ },
+ generateTrackCall: async (_client, schema, functionName, propertiesObject) => {
+ const { properties } = getPropertiesSchema(schema);
+ return {
+ class: schema.name.replace(/\s/g, ''),
+ functionName: functionName,
+ propsType: propertiesObject.type,
+ propsParam: !!properties.length,
+ };
+ },
+ generateRoot: async (client, context) => {
+ await Promise.all([
+ client.generateFile(
+ 'RudderTyperAnalytics.java',
+ 'generators/android/templates/RudderTyperAnalytics.java.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'RudderTyperUtils.java',
+ 'generators/android/templates/RudderTyperUtils.java.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'SerializableProperties.java',
+ 'generators/android/templates/SerializableProperties.java.hbs',
+ context,
+ ),
+ ...context.objects.map(o =>
+ client.generateFile(`${o.name}.java`, 'generators/android/templates/class.java.hbs', o),
+ ),
+ ]);
+ },
+};
function defaultPropertyContext(
- client: GeneratorClient,
- schema: Schema,
- type: string,
- namespace: string
+ client: GeneratorClient,
+ schema: Schema,
+ type: string,
+ namespace: string,
): AndroidPropertyContext {
- return {
- name: client.namer.register(schema.name, namespace, {
- transform: camelCase,
- }),
- type,
- isVariableNullable: !schema.isRequired || !!schema.isNullable,
- shouldThrowRuntimeError: schema.isRequired && !schema.isNullable,
- isListType: false,
- implementsSerializableProperties: false,
- }
+ return {
+ name: client.namer.register(schema.name, namespace, {
+ transform: camelCase,
+ }),
+ type,
+ isVariableNullable: !schema.isRequired || !!schema.isNullable,
+ shouldThrowRuntimeError: schema.isRequired && !schema.isNullable,
+ isListType: false,
+ implementsSerializableProperties: false,
+ };
}
diff --git a/src/generators/android/index.ts b/src/generators/android/index.ts
index aa533284..2c167e82 100644
--- a/src/generators/android/index.ts
+++ b/src/generators/android/index.ts
@@ -1 +1 @@
-export { android } from './android'
+export { android } from './android';
diff --git a/src/generators/ast.ts b/src/generators/ast.ts
index 6debdb3b..fc3e5486 100644
--- a/src/generators/ast.ts
+++ b/src/generators/ast.ts
@@ -1,277 +1,277 @@
-import { JSONSchema7 } from 'json-schema'
+import { JSONSchema7 } from 'json-schema';
// Schema represents a JSON Schema, however the representation
// differs in ways that make it easier for codegen.
// It does not seek to represent all of JSON Schema, only the subset that
// is meaningful for codegen. Full JSON Schema validation should be done
// at run-time and should be supported by all RudderTyper clients.
-export type Schema = PrimitiveTypeSchema | ArrayTypeSchema | ObjectTypeSchema | UnionTypeSchema
+export type Schema = PrimitiveTypeSchema | ArrayTypeSchema | ObjectTypeSchema | UnionTypeSchema;
-export type PrimitiveTypeSchema = SchemaMetadata & PrimitiveTypeFields
-export type ArrayTypeSchema = SchemaMetadata & ArrayTypeFields
-export type ObjectTypeSchema = SchemaMetadata & ObjectTypeFields
-export type UnionTypeSchema = SchemaMetadata & UnionTypeFields
+export type PrimitiveTypeSchema = SchemaMetadata & PrimitiveTypeFields;
+export type ArrayTypeSchema = SchemaMetadata & ArrayTypeFields;
+export type ObjectTypeSchema = SchemaMetadata & ObjectTypeFields;
+export type UnionTypeSchema = SchemaMetadata & UnionTypeFields;
export type SchemaMetadata = {
- name: string
- description?: string
- isRequired?: boolean
- isNullable?: boolean
-}
+ name: string;
+ description?: string;
+ isRequired?: boolean;
+ isNullable?: boolean;
+};
export type TypeSpecificFields =
- | PrimitiveTypeFields
- | ArrayTypeFields
- | ObjectTypeFields
- | UnionTypeFields
+ | PrimitiveTypeFields
+ | ArrayTypeFields
+ | ObjectTypeFields
+ | UnionTypeFields;
// TODO: consider whether non-primitive types should have an enum.
// For unions, potentially the enum should just be within the primitive type
// and filtered down to the relevant enum values.
export type Enumable = {
- // enum optionally represents a specific set of allowed values.
- // Note: const fields (from JSON Schema) are treated as 1-element enums.
- enum?: EnumValue[]
-}
+ // enum optionally represents a specific set of allowed values.
+ // Note: const fields (from JSON Schema) are treated as 1-element enums.
+ enum?: EnumValue[];
+};
// Note: we don't support objects or arrays as enums, for simplification purposes.
-export type EnumValue = string | number | boolean | null
+export type EnumValue = string | number | boolean | null;
export type PrimitiveTypeFields = Enumable & {
- type: Type.STRING | Type.INTEGER | Type.NUMBER | Type.BOOLEAN | Type.ANY
-}
+ type: Type.STRING | Type.INTEGER | Type.NUMBER | Type.BOOLEAN | Type.ANY;
+};
export type ArrayTypeFields = {
- type: Type.ARRAY
- // items specifies the type of any items in this array.
- items: TypeSpecificFields
-}
+ type: Type.ARRAY;
+ // items specifies the type of any items in this array.
+ items: TypeSpecificFields;
+};
export type ObjectTypeFields = {
- type: Type.OBJECT
- // properties specifies all of the expected properties in this object.
- // Note: if an empty properties list is passed, all properties should be allowed.
- properties: Schema[]
-}
+ type: Type.OBJECT;
+ // properties specifies all of the expected properties in this object.
+ // Note: if an empty properties list is passed, all properties should be allowed.
+ properties: Schema[];
+};
export type UnionTypeFields = Enumable & {
- type: Type.UNION
- types: TypeSpecificFields[]
-}
+ type: Type.UNION;
+ types: TypeSpecificFields[];
+};
// Type is a standardization of the various JSON Schema types. It removes the concept
// of a "null" type, and introduces Unions and an explicit Any type. The Any type is
// part of the JSON Schema spec, but it isn't an explicit type.
export enum Type {
- ANY,
- STRING,
- BOOLEAN,
- INTEGER,
- NUMBER,
- OBJECT,
- ARRAY,
- UNION,
+ ANY,
+ STRING,
+ BOOLEAN,
+ INTEGER,
+ NUMBER,
+ OBJECT,
+ ARRAY,
+ UNION,
}
function toType(t: string): Type {
- switch (t) {
- case 'string':
- return Type.STRING
- case 'integer':
- return Type.INTEGER
- case 'number':
- return Type.NUMBER
- case 'boolean':
- return Type.BOOLEAN
- case 'object':
- return Type.OBJECT
- case 'array':
- return Type.ARRAY
- case 'null':
- return Type.ANY
- default:
- throw new Error(`Unsupported type: ${t}`)
- }
+ switch (t) {
+ case 'string':
+ return Type.STRING;
+ case 'integer':
+ return Type.INTEGER;
+ case 'number':
+ return Type.NUMBER;
+ case 'boolean':
+ return Type.BOOLEAN;
+ case 'object':
+ return Type.OBJECT;
+ case 'array':
+ return Type.ARRAY;
+ case 'null':
+ return Type.ANY;
+ default:
+ throw new Error(`Unsupported type: ${t}`);
+ }
}
// getPropertiesSchema extracts the Schema for `.properties` from an
// event schema.
export function getPropertiesSchema(event: Schema): ObjectTypeSchema {
- let properties: ObjectTypeSchema | undefined = undefined
-
- // Events should always be a type="object" at the root, anything
- // else would not match on a RudderStack analytics event.
- if (event.type === Type.OBJECT) {
- const propertiesSchema = event.properties.find(
- (schema: Schema): boolean => schema.name === 'properties'
- )
- // The schema representing `.properties` in the RudderStack analytics
- // event should also always be an object.
- if (propertiesSchema && propertiesSchema.type === Type.OBJECT) {
- properties = propertiesSchema
- }
- }
-
- return {
- // If `.properties` doesn't exist in the user-supplied JSON Schema,
- // default to an empty object schema as a sane default.
- type: Type.OBJECT,
- properties: [],
- ...(properties || {}),
- isRequired: properties ? !!properties.isRequired : false,
- isNullable: false,
- // Use the event's name and description when generating an interface
- // to represent these properties.
- name: event.name,
- description: event.description,
- }
+ let properties: ObjectTypeSchema | undefined = undefined;
+
+ // Events should always be a type="object" at the root, anything
+ // else would not match on a RudderStack analytics event.
+ if (event.type === Type.OBJECT) {
+ const propertiesSchema = event.properties.find(
+ (schema: Schema): boolean => schema.name === 'properties',
+ );
+ // The schema representing `.properties` in the RudderStack analytics
+ // event should also always be an object.
+ if (propertiesSchema && propertiesSchema.type === Type.OBJECT) {
+ properties = propertiesSchema;
+ }
+ }
+
+ return {
+ // If `.properties` doesn't exist in the user-supplied JSON Schema,
+ // default to an empty object schema as a sane default.
+ type: Type.OBJECT,
+ properties: [],
+ ...(properties || {}),
+ isRequired: properties ? !!properties.isRequired : false,
+ isNullable: false,
+ // Use the event's name and description when generating an interface
+ // to represent these properties.
+ name: event.name,
+ description: event.description,
+ };
}
// parse transforms a JSON Schema into a standardized Schema.
export function parse(raw: JSONSchema7, name?: string, isRequired?: boolean): Schema {
- // TODO: validate that the raw JSON Schema is a valid JSON Schema before attempting to parse it.
+ // TODO: validate that the raw JSON Schema is a valid JSON Schema before attempting to parse it.
- // Parse the relevant fields from the JSON Schema based on the type.
- const typeSpecificFields = parseTypeSpecificFields(raw, getType(raw))
+ // Parse the relevant fields from the JSON Schema based on the type.
+ const typeSpecificFields = parseTypeSpecificFields(raw, getType(raw));
- const schema: Schema = {
- name: name || raw.title || '',
- ...typeSpecificFields,
- }
+ const schema: Schema = {
+ name: name || raw.title || '',
+ ...typeSpecificFields,
+ };
- if (raw.description) {
- schema.description = raw.description
- }
+ if (raw.description) {
+ schema.description = raw.description;
+ }
- if (isRequired) {
- schema.isRequired = true
- }
+ if (isRequired) {
+ schema.isRequired = true;
+ }
- if (isNullable(raw)) {
- schema.isNullable = true
- }
+ if (isNullable(raw)) {
+ schema.isNullable = true;
+ }
- return schema
+ return schema;
}
// parseTypeSpecificFields extracts the relevant fields from the raw JSON Schema,
// interpreting the schema based on the provided Type.
function parseTypeSpecificFields(raw: JSONSchema7, type: Type): TypeSpecificFields {
- if (type === Type.OBJECT) {
- const fields: ObjectTypeFields = { type, properties: [] }
- const requiredFields = new Set(raw.required || [])
- for (const entry of Object.entries(raw.properties || {})) {
- const [property, propertySchema] = entry
- if (typeof propertySchema !== 'boolean') {
- const isRequired = requiredFields.has(property)
- fields.properties.push(parse(propertySchema, property, isRequired))
- }
- }
-
- return fields
- } else if (type === Type.ARRAY) {
- const fields: ArrayTypeFields = { type, items: { type: Type.ANY } }
- if (typeof raw.items !== 'boolean' && raw.items !== undefined) {
- // `items` can be a single schemas, or an array of schemas, so standardize on an array.
- const definitions = raw.items instanceof Array ? raw.items : [raw.items]
-
- // Convert from JSONSchema7Definition -> JSONSchema7
- const schemas = definitions.filter(def => typeof def !== 'boolean') as JSONSchema7[]
-
- if (schemas.length === 1) {
- const schema = schemas[0]
- fields.items = parseTypeSpecificFields(schema, getType(schema))
- } else if (schemas.length > 1) {
- fields.items = {
- type: Type.UNION,
- types: schemas.map(schema => parseTypeSpecificFields(schema, getType(schema))),
- }
- }
- }
-
- return fields
- } else if (type === Type.UNION) {
- const fields: UnionTypeFields = { type, types: [] }
- for (const val of getRawTypes(raw).values()) {
- // For codegen purposes, we don't consider "null" as a type, so remove it.
- if (val === 'null') {
- continue
- }
-
- fields.types.push(parseTypeSpecificFields(raw, toType(val)))
- }
-
- if (raw.enum) {
- fields.enum = getEnum(raw)
- }
-
- return fields
- } else {
- const fields: PrimitiveTypeFields = { type }
-
- // TODO: Per above comment, consider filtering the enum values to just the matching type (string, boolean, etc.).
- if (raw.enum) {
- fields.enum = getEnum(raw)
- }
-
- // Handle the special case of `type: "null"`. In this case, only the value "null"
- // is allowed, so treat this as a single-value enum.
- const rawTypes = getRawTypes(raw)
- if (rawTypes.has('null') && rawTypes.size === 1) {
- fields.enum = [null]
- }
-
- return fields
- }
+ if (type === Type.OBJECT) {
+ const fields: ObjectTypeFields = { type, properties: [] };
+ const requiredFields = new Set(raw.required || []);
+ for (const entry of Object.entries(raw.properties || {})) {
+ const [property, propertySchema] = entry;
+ if (typeof propertySchema !== 'boolean') {
+ const isRequired = requiredFields.has(property);
+ fields.properties.push(parse(propertySchema, property, isRequired));
+ }
+ }
+
+ return fields;
+ } else if (type === Type.ARRAY) {
+ const fields: ArrayTypeFields = { type, items: { type: Type.ANY } };
+ if (typeof raw.items !== 'boolean' && raw.items !== undefined) {
+ // `items` can be a single schemas, or an array of schemas, so standardize on an array.
+ const definitions = raw.items instanceof Array ? raw.items : [raw.items];
+
+ // Convert from JSONSchema7Definition -> JSONSchema7
+ const schemas = definitions.filter(def => typeof def !== 'boolean') as JSONSchema7[];
+
+ if (schemas.length === 1) {
+ const schema = schemas[0];
+ fields.items = parseTypeSpecificFields(schema, getType(schema));
+ } else if (schemas.length > 1) {
+ fields.items = {
+ type: Type.UNION,
+ types: schemas.map(schema => parseTypeSpecificFields(schema, getType(schema))),
+ };
+ }
+ }
+
+ return fields;
+ } else if (type === Type.UNION) {
+ const fields: UnionTypeFields = { type, types: [] };
+ for (const val of getRawTypes(raw).values()) {
+ // For codegen purposes, we don't consider "null" as a type, so remove it.
+ if (val === 'null') {
+ continue;
+ }
+
+ fields.types.push(parseTypeSpecificFields(raw, toType(val)));
+ }
+
+ if (raw.enum) {
+ fields.enum = getEnum(raw);
+ }
+
+ return fields;
+ } else {
+ const fields: PrimitiveTypeFields = { type };
+
+ // TODO: Per above comment, consider filtering the enum values to just the matching type (string, boolean, etc.).
+ if (raw.enum) {
+ fields.enum = getEnum(raw);
+ }
+
+ // Handle the special case of `type: "null"`. In this case, only the value "null"
+ // is allowed, so treat this as a single-value enum.
+ const rawTypes = getRawTypes(raw);
+ if (rawTypes.has('null') && rawTypes.size === 1) {
+ fields.enum = [null];
+ }
+
+ return fields;
+ }
}
// getRawTypes returns the types for a given raw JSON Schema. These correspond
// with the standard JSON Schema types (null, string, etc.)
function getRawTypes(raw: JSONSchema7): Set {
- // JSON Schema's `type` field is either an array or a string -- standardize it into an array.
- const rawTypes = new Set()
- if (typeof raw.type === 'string') {
- rawTypes.add(raw.type)
- } else if (raw.type instanceof Array) {
- raw.type.forEach(t => rawTypes.add(t))
- }
-
- return rawTypes
+ // JSON Schema's `type` field is either an array or a string -- standardize it into an array.
+ const rawTypes = new Set();
+ if (typeof raw.type === 'string') {
+ rawTypes.add(raw.type);
+ } else if (raw.type instanceof Array) {
+ raw.type.forEach(t => rawTypes.add(t));
+ }
+
+ return rawTypes;
}
// getType parses the raw types from a JSON Schema and returns the standardized Type.
function getType(raw: JSONSchema7): Type {
- const rawTypes = getRawTypes(raw)
- // For codegen purposes, we don't consider "null" as a type, so remove it.
- rawTypes.delete('null')
-
- let type = Type.ANY
- if (rawTypes.size === 1) {
- type = toType(rawTypes.values().next().value)
- } else if (rawTypes.size >= 1) {
- type = Type.UNION
- }
-
- return type
+ const rawTypes = getRawTypes(raw);
+ // For codegen purposes, we don't consider "null" as a type, so remove it.
+ rawTypes.delete('null');
+
+ let type = Type.ANY;
+ if (rawTypes.size === 1) {
+ type = toType(rawTypes.values().next().value);
+ } else if (rawTypes.size >= 1) {
+ type = Type.UNION;
+ }
+
+ return type;
}
// isNullable returns true if `null` is a valid value for this JSON Schema.
function isNullable(raw: JSONSchema7): boolean {
- const typeAllowsNull = getRawTypes(raw).has('null') || getType(raw) === Type.ANY
- const enumAllowsNull = !raw.enum || raw.enum.includes(null) || raw.enum.includes('null')
+ const typeAllowsNull = getRawTypes(raw).has('null') || getType(raw) === Type.ANY;
+ const enumAllowsNull = !raw.enum || raw.enum.includes(null) || raw.enum.includes('null');
- return typeAllowsNull && enumAllowsNull
+ return typeAllowsNull && enumAllowsNull;
}
// getEnum parses the enum, if specified
function getEnum(raw: JSONSchema7): EnumValue[] | undefined {
- if (!raw.enum) {
- return undefined
- }
+ if (!raw.enum) {
+ return undefined;
+ }
- const enm = raw.enum.filter(
- val => ['boolean', 'number', 'string'].includes(typeof val) || val === null
- ) as EnumValue[]
+ const enm = raw.enum.filter(
+ val => ['boolean', 'number', 'string'].includes(typeof val) || val === null,
+ ) as EnumValue[];
- return enm
+ return enm;
}
diff --git a/src/generators/gen.ts b/src/generators/gen.ts
index 299e45a6..028983a0 100644
--- a/src/generators/gen.ts
+++ b/src/generators/gen.ts
@@ -1,87 +1,87 @@
-import { JSONSchema7 } from 'json-schema'
-import { parse, Schema, getPropertiesSchema, Type } from './ast'
-import { javascript } from './javascript'
-import { objc } from './objc'
-import { swift } from './swift'
-import { android } from './android'
-import { Options, SDK, Language } from './options'
-import { registerStandardHelpers, generateFromTemplate } from '../templates'
-import { Namer, Options as NamerOptions } from './namer'
-import stringify from 'json-stable-stringify'
-import { camelCase, upperFirst } from 'lodash'
+import { JSONSchema7 } from 'json-schema';
+import { parse, Schema, getPropertiesSchema, Type } from './ast';
+import { javascript } from './javascript';
+import { objc } from './objc';
+import { swift } from './swift';
+import { android } from './android';
+import { Options, SDK, Language } from './options';
+import { registerStandardHelpers, generateFromTemplate } from '../templates';
+import { Namer, Options as NamerOptions } from './namer';
+import stringify from 'json-stable-stringify';
+import { camelCase, upperFirst } from 'lodash';
export type File = {
- path: string
- contents: string
-}
+ path: string;
+ contents: string;
+};
export type RawTrackingPlan = {
- name: string
- url: string
- id: string
- version: string
- path: string
- trackCalls: JSONSchema7[]
-}
+ name: string;
+ url: string;
+ id: string;
+ version: string;
+ path: string;
+ trackCalls: JSONSchema7[];
+};
export type TrackingPlan = {
- url: string
- id: string
- version: string
- trackCalls: {
- raw: JSONSchema7
- schema: Schema
- }[]
-}
+ url: string;
+ id: string;
+ version: string;
+ trackCalls: {
+ raw: JSONSchema7;
+ schema: Schema;
+ }[];
+};
export type BaseRootContext<
- T extends Record,
- O extends Record,
- P extends Record
+ T extends Record,
+ O extends Record,
+ P extends Record
> = {
- isDevelopment: boolean
- language: string
- rudderTyperVersion: string
- trackingPlanURL: string
- tracks: (T & BaseTrackCallContext)[]
- objects: (O & BaseObjectContext
)[]
-}
+ isDevelopment: boolean;
+ language: string;
+ rudderTyperVersion: string;
+ trackingPlanURL: string;
+ tracks: (T & BaseTrackCallContext
)[];
+ objects: (O & BaseObjectContext
)[];
+};
export type BaseTrackCallContext
> = {
- // The optional function description.
- functionDescription?: string
- // The raw JSON Schema for this event.
- rawJSONSchema: string
- // The raw version of the name of this track call (the name sent to RudderStack).
- rawEventName: string
- // The property parameters on this track call. Included if generatePropertiesObject=false.
- properties?: (P & BasePropertyContext)[]
-}
+ // The optional function description.
+ functionDescription?: string;
+ // The raw JSON Schema for this event.
+ rawJSONSchema: string;
+ // The raw version of the name of this track call (the name sent to RudderStack).
+ rawEventName: string;
+ // The property parameters on this track call. Included if generatePropertiesObject=false.
+ properties?: (P & BasePropertyContext)[];
+};
export type BaseObjectContext
> = {
- description?: string
- properties: (P & BasePropertyContext)[]
-}
+ description?: string;
+ properties: (P & BasePropertyContext)[];
+};
export type BasePropertyContext = {
- // The raw name of this property. ex: "user id"
- rawName: string
- // The AST type of this property. ex: Type.INTEGER
- schemaType: Type
- // The optional description of this property.
- description?: string
- isRequired: boolean
-}
+ // The raw name of this property. ex: "user id"
+ rawName: string;
+ // The AST type of this property. ex: Type.INTEGER
+ schemaType: Type;
+ // The optional description of this property.
+ description?: string;
+ isRequired: boolean;
+};
export type GeneratorClient = {
- options: GenOptions
- namer: Namer
- generateFile: >(
- outputPath: string,
- templatePath: string,
- context: T
- ) => Promise
-}
+ options: GenOptions;
+ namer: Namer;
+ generateFile: >(
+ outputPath: string,
+ templatePath: string,
+ context: T,
+ ) => Promise;
+};
/*
* Adding a new language to RudderTyper involves implementing the interface below. The logic to traverse a
* JSON Schema and apply this generator is in `runGenerator` below.
@@ -91,274 +91,274 @@ export type GeneratorClient = {
* as parameters to each function. You can toggle this behavior with `generatePropertiesObject`.
*/
export declare type Generator<
- R extends Record,
- T extends Record,
- O extends Record,
- P extends Record
+ R extends Record,
+ T extends Record,
+ O extends Record,
+ P extends Record
> = {
- namer: NamerOptions
- setup: (options: GenOptions) => Promise
- generatePrimitive: (client: GeneratorClient, schema: Schema, parentPath: string) => Promise
- generateArray: (
- client: GeneratorClient,
- schema: Schema,
- items: P & BasePropertyContext,
- parentPath: string
- ) => Promise
- generateObject: (
- client: GeneratorClient,
- schema: Schema,
- properties: (P & BasePropertyContext)[],
- parentPath: string
- ) => Promise<{ property: P; object?: O }>
- generateUnion: (
- client: GeneratorClient,
- schema: Schema,
- types: (P & BasePropertyContext)[],
- parentPath: string
- ) => Promise
- generateRoot: (client: GeneratorClient, context: R & BaseRootContext) => Promise
- formatFile?: (client: GeneratorClient, file: File) => File
+ namer: NamerOptions;
+ setup: (options: GenOptions) => Promise;
+ generatePrimitive: (client: GeneratorClient, schema: Schema, parentPath: string) => Promise;
+ generateArray: (
+ client: GeneratorClient,
+ schema: Schema,
+ items: P & BasePropertyContext,
+ parentPath: string,
+ ) => Promise
;
+ generateObject: (
+ client: GeneratorClient,
+ schema: Schema,
+ properties: (P & BasePropertyContext)[],
+ parentPath: string,
+ ) => Promise<{ property: P; object?: O }>;
+ generateUnion: (
+ client: GeneratorClient,
+ schema: Schema,
+ types: (P & BasePropertyContext)[],
+ parentPath: string,
+ ) => Promise
;
+ generateRoot: (client: GeneratorClient, context: R & BaseRootContext) => Promise;
+ formatFile?: (client: GeneratorClient, file: File) => File;
} & (
- | {
- generatePropertiesObject: true
- generateTrackCall: (
- client: GeneratorClient,
- schema: Schema,
- functionName: string,
- propertiesObject: P & BasePropertyContext
- ) => Promise
- }
- | {
- generatePropertiesObject: false
- generateTrackCall: (
- client: GeneratorClient,
- schema: Schema,
- functionName: string,
- properties: (P & BasePropertyContext)[]
- ) => Promise
- }
-)
+ | {
+ generatePropertiesObject: true;
+ generateTrackCall: (
+ client: GeneratorClient,
+ schema: Schema,
+ functionName: string,
+ propertiesObject: P & BasePropertyContext,
+ ) => Promise;
+ }
+ | {
+ generatePropertiesObject: false;
+ generateTrackCall: (
+ client: GeneratorClient,
+ schema: Schema,
+ functionName: string,
+ properties: (P & BasePropertyContext)[],
+ ) => Promise;
+ }
+);
export type GenOptions = {
- // Configuration options configured by the ruddertyper.yml config.
- client: Options
- // The version of the RudderTyper CLI that is being used to generate clients.
- // Used for analytics purposes by the RudderTyper team.
- rudderTyperVersion: string
- // Whether or not to generate a development bundle. If so, analytics payloads will
- // be validated against the full JSON Schema before being sent to the underlying
- // analytics instance.
- isDevelopment: boolean
-}
+ // Configuration options configured by the ruddertyper.yml config.
+ client: Options;
+ // The version of the RudderTyper CLI that is being used to generate clients.
+ // Used for analytics purposes by the RudderTyper team.
+ rudderTyperVersion: string;
+ // Whether or not to generate a development bundle. If so, analytics payloads will
+ // be validated against the full JSON Schema before being sent to the underlying
+ // analytics instance.
+ isDevelopment: boolean;
+};
export async function gen(trackingPlan: RawTrackingPlan, options: GenOptions): Promise {
- const parsedTrackingPlan = {
- url: trackingPlan.url,
- id: trackingPlan.id,
- version: trackingPlan.version,
- trackCalls: trackingPlan.trackCalls.map(s => {
- const sanitizedSchema = {
- $schema: 'http://json-schema.org/draft-07/schema#',
- ...s,
- }
- return {
- raw: sanitizedSchema,
- schema: parse(sanitizedSchema),
- }
- }),
- }
+ const parsedTrackingPlan = {
+ url: trackingPlan.url,
+ id: trackingPlan.id,
+ version: trackingPlan.version,
+ trackCalls: trackingPlan.trackCalls.map(s => {
+ const sanitizedSchema = {
+ $schema: 'http://json-schema.org/draft-07/schema#',
+ ...s,
+ };
+ return {
+ raw: sanitizedSchema,
+ schema: parse(sanitizedSchema),
+ };
+ }),
+ };
- if (options.client.sdk === SDK.WEB || options.client.sdk === SDK.NODE) {
- return await runGenerator(javascript, parsedTrackingPlan, options)
- } else if (options.client.sdk === SDK.IOS) {
- if (options.client.language === Language.SWIFT) {
- return await runGenerator(swift, parsedTrackingPlan, options)
- } else {
- return await runGenerator(objc, parsedTrackingPlan, options)
- }
- } else if (options.client.sdk === SDK.ANDROID) {
- return await runGenerator(android, parsedTrackingPlan, options)
- } else {
- throw new Error(`Invalid SDK: ${options.client.sdk}`)
- }
+ if (options.client.sdk === SDK.WEB || options.client.sdk === SDK.NODE) {
+ return await runGenerator(javascript, parsedTrackingPlan, options);
+ } else if (options.client.sdk === SDK.IOS) {
+ if (options.client.language === Language.SWIFT) {
+ return await runGenerator(swift, parsedTrackingPlan, options);
+ } else {
+ return await runGenerator(objc, parsedTrackingPlan, options);
+ }
+ } else if (options.client.sdk === SDK.ANDROID) {
+ return await runGenerator(android, parsedTrackingPlan, options);
+ } else {
+ throw new Error(`Invalid SDK: ${options.client.sdk}`);
+ }
}
async function runGenerator<
- R extends Record,
- T extends Record,
- O extends Record,
- P extends Record
+ R extends Record,
+ T extends Record,
+ O extends Record,
+ P extends Record
>(
- generator: Generator,
- trackingPlan: TrackingPlan,
- options: GenOptions
+ generator: Generator,
+ trackingPlan: TrackingPlan,
+ options: GenOptions,
): Promise {
- // One-time setup.
- registerStandardHelpers()
- const rootContext = await generator.setup(options)
- const context: R & BaseRootContext = {
- ...rootContext,
- isDevelopment: options.isDevelopment,
- language: options.client.language,
- sdk: options.client.sdk,
- rudderTyperVersion: options.rudderTyperVersion,
- trackingPlanURL: trackingPlan.url,
- trackingPlanId: trackingPlan.id,
- trackingPlanVersion: trackingPlan.version,
- tracks: [],
- objects: [],
- }
+ // One-time setup.
+ registerStandardHelpers();
+ const rootContext = await generator.setup(options);
+ const context: R & BaseRootContext = {
+ ...rootContext,
+ isDevelopment: options.isDevelopment,
+ language: options.client.language,
+ sdk: options.client.sdk,
+ rudderTyperVersion: options.rudderTyperVersion,
+ trackingPlanURL: trackingPlan.url,
+ trackingPlanId: trackingPlan.id,
+ trackingPlanVersion: trackingPlan.version,
+ tracks: [],
+ objects: [],
+ };
- // File output.
- const files: File[] = []
- const generateFile = async >(
- outputPath: string,
- templatePath: string,
- fileContext: C
- ) => {
- files.push({
- path: outputPath,
- contents: await generateFromTemplate>(templatePath, {
- ...context,
- ...fileContext,
- }),
- })
- }
+ // File output.
+ const files: File[] = [];
+ const generateFile = async >(
+ outputPath: string,
+ templatePath: string,
+ fileContext: C,
+ ) => {
+ files.push({
+ path: outputPath,
+ contents: await generateFromTemplate>(templatePath, {
+ ...context,
+ ...fileContext,
+ }),
+ });
+ };
- const client: GeneratorClient = {
- options,
- namer: new Namer(generator.namer),
- generateFile,
- }
+ const client: GeneratorClient = {
+ options,
+ namer: new Namer(generator.namer),
+ generateFile,
+ };
- // Core generator logic. This logic involves traversing over the underlying JSON Schema
- // and calling out to the supplied generator with each "node" in the JSON Schema that,
- // based on its AST type. Each iteration of this loop generates a "property" which
- // represents the type for a given schema. This property contains metadata such as the
- // type name (string, FooBarInterface, etc.), descriptions, etc. that are used in
- // templates.
- const traverseSchema = async (
- schema: Schema,
- parentPath: string,
- eventName: string
- ): Promise => {
- const path = `${parentPath}->${schema.name}`
- const base = {
- rawName: client.namer.escapeString(schema.name),
- schemaType: schema.type,
- description: schema.description,
- isRequired: !!schema.isRequired,
- }
- let p: P
- if ([Type.ANY, Type.STRING, Type.BOOLEAN, Type.INTEGER, Type.NUMBER].includes(schema.type)) {
- // Primitives are any type that doesn't require generating a "subtype".
- p = await generator.generatePrimitive(client, schema, parentPath)
- } else if (schema.type === Type.OBJECT) {
- // For objects, we need to recursively generate each property first.
- const properties: (P & BasePropertyContext)[] = []
- for (const property of schema.properties) {
- properties.push(await traverseSchema(property, path, eventName))
- }
+ // Core generator logic. This logic involves traversing over the underlying JSON Schema
+ // and calling out to the supplied generator with each "node" in the JSON Schema that,
+ // based on its AST type. Each iteration of this loop generates a "property" which
+ // represents the type for a given schema. This property contains metadata such as the
+ // type name (string, FooBarInterface, etc.), descriptions, etc. that are used in
+ // templates.
+ const traverseSchema = async (
+ schema: Schema,
+ parentPath: string,
+ eventName: string,
+ ): Promise
=> {
+ const path = `${parentPath}->${schema.name}`;
+ const base = {
+ rawName: client.namer.escapeString(schema.name),
+ schemaType: schema.type,
+ description: schema.description,
+ isRequired: !!schema.isRequired,
+ };
+ let p: P;
+ if ([Type.ANY, Type.STRING, Type.BOOLEAN, Type.INTEGER, Type.NUMBER].includes(schema.type)) {
+ // Primitives are any type that doesn't require generating a "subtype".
+ p = await generator.generatePrimitive(client, schema, parentPath);
+ } else if (schema.type === Type.OBJECT) {
+ // For objects, we need to recursively generate each property first.
+ const properties: (P & BasePropertyContext)[] = [];
+ for (const property of schema.properties) {
+ properties.push(await traverseSchema(property, path, eventName));
+ }
- if (parentPath !== '') {
- schema.name = eventName + upperFirst(schema.name)
- }
+ if (parentPath !== '') {
+ schema.name = eventName + upperFirst(schema.name);
+ }
- const { property, object } = await generator.generateObject(
- client,
- schema,
- properties,
- parentPath
- )
- if (object) {
- context.objects.push({
- properties,
- ...object,
- })
- }
- p = property
- } else if (schema.type === Type.ARRAY) {
- // Arrays are another special case, because we need to generate a type to represent
- // the items allowed in this array.
- const itemsSchema: Schema = {
- name: schema.name + ' Item',
- description: schema.description,
- ...schema.items,
- }
- const items = await traverseSchema(itemsSchema, path, eventName)
- p = await generator.generateArray(client, schema, items, parentPath)
- } else if (schema.type === Type.UNION) {
- // For unions, we generate a property type to represent each of the possible types
- // then use that list of possible property types to generate a union.
- const types = await Promise.all(
- schema.types.map(async t => {
- const subSchema = {
- name: schema.name,
- description: schema.description,
- ...t,
- }
+ const { property, object } = await generator.generateObject(
+ client,
+ schema,
+ properties,
+ parentPath,
+ );
+ if (object) {
+ context.objects.push({
+ properties,
+ ...object,
+ });
+ }
+ p = property;
+ } else if (schema.type === Type.ARRAY) {
+ // Arrays are another special case, because we need to generate a type to represent
+ // the items allowed in this array.
+ const itemsSchema: Schema = {
+ name: schema.name + ' Item',
+ description: schema.description,
+ ...schema.items,
+ };
+ const items = await traverseSchema(itemsSchema, path, eventName);
+ p = await generator.generateArray(client, schema, items, parentPath);
+ } else if (schema.type === Type.UNION) {
+ // For unions, we generate a property type to represent each of the possible types
+ // then use that list of possible property types to generate a union.
+ const types = await Promise.all(
+ schema.types.map(async t => {
+ const subSchema = {
+ name: schema.name,
+ description: schema.description,
+ ...t,
+ };
- return await traverseSchema(subSchema, path, eventName)
- })
- )
- p = await generator.generateUnion(client, schema, types, parentPath)
- } else {
- throw new Error(`Invalid Schema Type: ${schema.type}`)
- }
+ return await traverseSchema(subSchema, path, eventName);
+ }),
+ );
+ p = await generator.generateUnion(client, schema, types, parentPath);
+ } else {
+ throw new Error(`Invalid Schema Type: ${schema.type}`);
+ }
- return {
- ...base,
- ...p,
- }
- }
- // Generate Track Calls.
- for (const { raw, schema } of trackingPlan.trackCalls) {
- let t: T
- const functionName: string = client.namer.register(schema.name, 'function->track', {
- transform: camelCase,
- })
- if (generator.generatePropertiesObject) {
- const p = await traverseSchema(getPropertiesSchema(schema), '', functionName)
- t = await generator.generateTrackCall(client, schema, functionName, p)
- } else {
- const properties: (P & BasePropertyContext)[] = []
- for (const property of getPropertiesSchema(schema).properties) {
- properties.push(await traverseSchema(property, schema.name, functionName))
- }
- t = {
- ...(await generator.generateTrackCall(client, schema, functionName, properties)),
- properties,
- }
- }
+ return {
+ ...base,
+ ...p,
+ };
+ };
+ // Generate Track Calls.
+ for (const { raw, schema } of trackingPlan.trackCalls) {
+ let t: T;
+ const functionName: string = client.namer.register(schema.name, 'function->track', {
+ transform: camelCase,
+ });
+ if (generator.generatePropertiesObject) {
+ const p = await traverseSchema(getPropertiesSchema(schema), '', functionName);
+ t = await generator.generateTrackCall(client, schema, functionName, p);
+ } else {
+ const properties: (P & BasePropertyContext)[] = [];
+ for (const property of getPropertiesSchema(schema).properties) {
+ properties.push(await traverseSchema(property, schema.name, functionName));
+ }
+ t = {
+ ...(await generator.generateTrackCall(client, schema, functionName, properties)),
+ properties,
+ };
+ }
- context.tracks.push({
- functionDescription: schema.description,
- rawJSONSchema: stringify(raw, {
- space: '\t',
- }),
- rawEventName: client.namer.escapeString(schema.name),
- ...t,
- })
- }
- // Perform any root-level generation.
- await generator.generateRoot(client, context)
+ context.tracks.push({
+ functionDescription: schema.description,
+ rawJSONSchema: stringify(raw, {
+ space: '\t',
+ }),
+ rawEventName: client.namer.escapeString(schema.name),
+ ...t,
+ });
+ }
+ // Perform any root-level generation.
+ await generator.generateRoot(client, context);
- // Format and output all generated files.
- return files.map(f => (generator.formatFile ? generator.formatFile(client, f) : f))
+ // Format and output all generated files.
+ return files.map(f => (generator.formatFile ? generator.formatFile(client, f) : f));
}
// Legacy Code:
export type TemplateBaseContext = {
- isDevelopment: boolean
- language: string
- rudderTyperVersion: string
-}
+ isDevelopment: boolean;
+ language: string;
+ rudderTyperVersion: string;
+};
export function baseContext(options: GenOptions): TemplateBaseContext {
- return {
- isDevelopment: options.isDevelopment,
- language: options.client.language,
- rudderTyperVersion: options.rudderTyperVersion,
- }
+ return {
+ isDevelopment: options.isDevelopment,
+ language: options.client.language,
+ rudderTyperVersion: options.rudderTyperVersion,
+ };
}
diff --git a/src/generators/javascript/index.ts b/src/generators/javascript/index.ts
index 970046f1..7a6d6e55 100644
--- a/src/generators/javascript/index.ts
+++ b/src/generators/javascript/index.ts
@@ -1 +1 @@
-export { javascript } from './javascript'
+export { javascript } from './javascript';
diff --git a/src/generators/javascript/javascript.ts b/src/generators/javascript/javascript.ts
index 24f1733a..6bfebd0b 100644
--- a/src/generators/javascript/javascript.ts
+++ b/src/generators/javascript/javascript.ts
@@ -1,200 +1,200 @@
-import { Type, Schema } from '../ast'
-import { camelCase, upperFirst } from 'lodash'
-import * as prettier from 'prettier'
-import { transpileModule } from 'typescript'
-import { Language, SDK } from '../options'
-import { Generator } from '../gen'
-import { toTarget, toModule } from './targets'
-import { registerPartial } from '../../templates'
+import { Type, Schema } from '../ast';
+import { camelCase, upperFirst } from 'lodash';
+import * as prettier from 'prettier';
+import { transpileModule } from 'typescript';
+import { Language, SDK } from '../options';
+import { Generator } from '../gen';
+import { toTarget, toModule } from './targets';
+import { registerPartial } from '../../templates';
// These contexts are what will be passed to Handlebars to perform rendering.
// Everything in these contexts should be properly sanitized.
type JavaScriptRootContext = {
- isBrowser: boolean
- useProxy: boolean
-}
+ isBrowser: boolean;
+ useProxy: boolean;
+};
// Represents a single exposed track() call.
type JavaScriptTrackCallContext = {
- // The formatted function name, ex: "orderCompleted".
- functionName: string
- // The type of the analytics properties object.
- propertiesType: string
- // The properties field is only optional in analytics.js environments where
- // no properties are required.
- isPropertiesOptional: boolean
-}
+ // The formatted function name, ex: "orderCompleted".
+ functionName: string;
+ // The type of the analytics properties object.
+ propertiesType: string;
+ // The properties field is only optional in analytics.js environments where
+ // no properties are required.
+ isPropertiesOptional: boolean;
+};
type JavaScriptObjectContext = {
- // The formatted name for this object, ex: "Planet"
- name: string
-}
+ // The formatted name for this object, ex: "Planet"
+ name: string;
+};
type JavaScriptPropertyContext = {
- // The formatted name for this property, ex: "numAvocados".
- name: string
- // The type of this property. ex: "number".
- type: string
-}
+ // The formatted name for this property, ex: "numAvocados".
+ name: string;
+ // The type of this property. ex: "number".
+ type: string;
+};
export const javascript: Generator<
- JavaScriptRootContext,
- JavaScriptTrackCallContext,
- JavaScriptObjectContext,
- JavaScriptPropertyContext
+ JavaScriptRootContext,
+ JavaScriptTrackCallContext,
+ JavaScriptObjectContext,
+ JavaScriptPropertyContext
> = {
- generatePropertiesObject: true,
- namer: {
- // See: https://mathiasbynens.be/notes/reserved-keywords#ecmascript-6
- // prettier-ignore
- reservedWords: [
+ generatePropertiesObject: true,
+ namer: {
+ // See: https://mathiasbynens.be/notes/reserved-keywords#ecmascript-6
+ // prettier-ignore
+ reservedWords: [
'do', 'if', 'in', 'for', 'let', 'new', 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this',
'true', 'void', 'with', 'await', 'break', 'catch', 'class', 'const', 'false', 'super', 'throw',
'while', 'yield', 'delete', 'export', 'import', 'public', 'return', 'static', 'switch', 'typeof',
'default', 'extends', 'finally', 'package', 'private', 'continue', 'debugger', 'function', 'arguments',
'interface', 'protected', 'implements', 'instanceof',
],
- quoteChar: "'",
- // Note: we don't support the full range of allowed JS chars, instead focusing on a subset.
- // The full regex 11k+ chars: https://mathiasbynens.be/demo/javascript-identifier-regex
- // See: https://mathiasbynens.be/notes/javascript-identifiers-es6
- allowedIdentifierStartingChars: 'A-Za-z_$',
- allowedIdentifierChars: 'A-Za-z0-9_$',
- },
- setup: async options => {
- await registerPartial(
- 'generators/javascript/templates/setRudderTyperOptionsDocumentation.hbs',
- 'setRudderTyperOptionsDocumentation'
- )
- await registerPartial(
- 'generators/javascript/templates/functionDocumentation.hbs',
- 'functionDocumentation'
- )
+ quoteChar: "'",
+ // Note: we don't support the full range of allowed JS chars, instead focusing on a subset.
+ // The full regex 11k+ chars: https://mathiasbynens.be/demo/javascript-identifier-regex
+ // See: https://mathiasbynens.be/notes/javascript-identifiers-es6
+ allowedIdentifierStartingChars: 'A-Za-z_$',
+ allowedIdentifierChars: 'A-Za-z0-9_$',
+ },
+ setup: async options => {
+ await registerPartial(
+ 'generators/javascript/templates/setRudderTyperOptionsDocumentation.hbs',
+ 'setRudderTyperOptionsDocumentation',
+ );
+ await registerPartial(
+ 'generators/javascript/templates/functionDocumentation.hbs',
+ 'functionDocumentation',
+ );
- return {
- isBrowser: options.client.sdk === SDK.WEB,
- useProxy: true,
- }
- },
- generatePrimitive: async (client, schema) => {
- let type = 'any'
- if (schema.type === Type.STRING) {
- type = 'string'
- } else if (schema.type === Type.BOOLEAN) {
- type = 'boolean'
- } else if (schema.type === Type.INTEGER || schema.type === Type.NUMBER) {
- type = 'number'
- }
+ return {
+ isBrowser: options.client.sdk === SDK.WEB,
+ useProxy: true,
+ };
+ },
+ generatePrimitive: async (client, schema) => {
+ let type = 'any';
+ if (schema.type === Type.STRING) {
+ type = 'string';
+ } else if (schema.type === Type.BOOLEAN) {
+ type = 'boolean';
+ } else if (schema.type === Type.INTEGER || schema.type === Type.NUMBER) {
+ type = 'number';
+ }
- return conditionallyNullable(schema, {
- name: client.namer.escapeString(schema.name),
- type,
- })
- },
- generateArray: async (client, schema, items) =>
- conditionallyNullable(schema, {
- name: client.namer.escapeString(schema.name),
- type: `${items.type}[]`,
- }),
- generateObject: async (client, schema, properties) => {
- if (properties.length === 0) {
- // If no properties are set, replace this object with a untyped map to allow any properties.
- return {
- property: conditionallyNullable(schema, {
- name: client.namer.escapeString(schema.name),
- type: 'Record',
- }),
- }
- } else {
- // Otherwise generate an interface to represent this object.
- const interfaceName = client.namer.register(schema.name, 'interface', {
- transform: (name: string) => upperFirst(camelCase(name)),
- })
- return {
- property: conditionallyNullable(schema, {
- name: client.namer.escapeString(schema.name),
- type: interfaceName,
- }),
- object: {
- name: interfaceName,
- },
- }
- }
- },
- generateUnion: async (client, schema, types) =>
- conditionallyNullable(schema, {
- name: client.namer.escapeString(schema.name),
- type: types.map(t => t.type).join(' | '),
- }),
- generateTrackCall: async (client, _schema, functionName, propertiesObject) => ({
- functionName: functionName,
- propertiesType: propertiesObject.type,
- // The properties object in a.js can be omitted if no properties are required.
- isPropertiesOptional: client.options.client.sdk === SDK.WEB && !propertiesObject.isRequired,
- }),
- generateRoot: async (client, context) => {
- // index.hbs contains all JavaScript client logic.
- await client.generateFile(
- client.options.client.language === Language.TYPESCRIPT ? 'index.ts' : 'index.js',
- 'generators/javascript/templates/index.hbs',
- context
- )
+ return conditionallyNullable(schema, {
+ name: client.namer.escapeString(schema.name),
+ type,
+ });
+ },
+ generateArray: async (client, schema, items) =>
+ conditionallyNullable(schema, {
+ name: client.namer.escapeString(schema.name),
+ type: `${items.type}[]`,
+ }),
+ generateObject: async (client, schema, properties) => {
+ if (properties.length === 0) {
+ // If no properties are set, replace this object with a untyped map to allow any properties.
+ return {
+ property: conditionallyNullable(schema, {
+ name: client.namer.escapeString(schema.name),
+ type: 'Record',
+ }),
+ };
+ } else {
+ // Otherwise generate an interface to represent this object.
+ const interfaceName = client.namer.register(schema.name, 'interface', {
+ transform: (name: string) => upperFirst(camelCase(name)),
+ });
+ return {
+ property: conditionallyNullable(schema, {
+ name: client.namer.escapeString(schema.name),
+ type: interfaceName,
+ }),
+ object: {
+ name: interfaceName,
+ },
+ };
+ }
+ },
+ generateUnion: async (client, schema, types) =>
+ conditionallyNullable(schema, {
+ name: client.namer.escapeString(schema.name),
+ type: types.map(t => t.type).join(' | '),
+ }),
+ generateTrackCall: async (client, _schema, functionName, propertiesObject) => ({
+ functionName: functionName,
+ propertiesType: propertiesObject.type,
+ // The properties object in a.js can be omitted if no properties are required.
+ isPropertiesOptional: client.options.client.sdk === SDK.WEB && !propertiesObject.isRequired,
+ }),
+ generateRoot: async (client, context) => {
+ // index.hbs contains all JavaScript client logic.
+ await client.generateFile(
+ client.options.client.language === Language.TYPESCRIPT ? 'index.ts' : 'index.js',
+ 'generators/javascript/templates/index.hbs',
+ context,
+ );
- // rudder.hbs contains the TypeScript definitions for the Rudder API.
- // It becomes an empty file for JavaScript after being transpiled.
- if (client.options.client.language === Language.TYPESCRIPT) {
- await client.generateFile(
- 'rudder.ts',
- 'generators/javascript/templates/rudder.hbs',
- context
- )
- }
- },
- formatFile: (client, file) => {
- let { contents } = file
- // If we are generating a JavaScript client, transpile the client
- // from TypeScript into JavaScript.
- if (client.options.client.language === Language.JAVASCRIPT) {
- // If we're generating a JavaScript client, compile from TypeScript to JavaScript.
- const { outputText } = transpileModule(contents, {
- compilerOptions: {
- target: toTarget(client.options.client.scriptTarget),
- module: toModule(client.options.client.moduleTarget),
- esModuleInterop: true,
- },
- })
+ // rudder.hbs contains the TypeScript definitions for the Rudder API.
+ // It becomes an empty file for JavaScript after being transpiled.
+ if (client.options.client.language === Language.TYPESCRIPT) {
+ await client.generateFile(
+ 'rudder.ts',
+ 'generators/javascript/templates/rudder.hbs',
+ context,
+ );
+ }
+ },
+ formatFile: (client, file) => {
+ let { contents } = file;
+ // If we are generating a JavaScript client, transpile the client
+ // from TypeScript into JavaScript.
+ if (client.options.client.language === Language.JAVASCRIPT) {
+ // If we're generating a JavaScript client, compile from TypeScript to JavaScript.
+ const { outputText } = transpileModule(contents, {
+ compilerOptions: {
+ target: toTarget(client.options.client.scriptTarget),
+ module: toModule(client.options.client.moduleTarget),
+ esModuleInterop: true,
+ },
+ });
- contents = outputText
- }
+ contents = outputText;
+ }
- // Apply stylistic formatting, via Prettier.
- const formattedContents = prettier.format(contents, {
- parser: client.options.client.language === Language.TYPESCRIPT ? 'typescript' : 'babel',
- // Overwrite a few of the standard prettier settings to match with our RudderTyper configuration:
- tabWidth: 2,
- singleQuote: true,
- semi: false,
- trailingComma:
- client.options.client.language === Language.JAVASCRIPT &&
- client.options.client.scriptTarget === 'ES3'
- ? 'none'
- : 'es5',
- })
+ // Apply stylistic formatting, via Prettier.
+ const formattedContents = prettier.format(contents, {
+ parser: client.options.client.language === Language.TYPESCRIPT ? 'typescript' : 'babel',
+ // Overwrite a few of the standard prettier settings to match with our RudderTyper configuration:
+ tabWidth: 2,
+ singleQuote: true,
+ semi: false,
+ trailingComma:
+ client.options.client.language === Language.JAVASCRIPT &&
+ client.options.client.scriptTarget === 'ES3'
+ ? 'none'
+ : 'es5',
+ });
- return {
- ...file,
- contents: formattedContents,
- }
- },
-}
+ return {
+ ...file,
+ contents: formattedContents,
+ };
+ },
+};
function conditionallyNullable(
- schema: Schema,
- property: JavaScriptPropertyContext
+ schema: Schema,
+ property: JavaScriptPropertyContext,
): JavaScriptPropertyContext {
- return {
- ...property,
- type: !!schema.isNullable ? `${property.type} | null` : property.type,
- }
+ return {
+ ...property,
+ type: !!schema.isNullable ? `${property.type} | null` : property.type,
+ };
}
diff --git a/src/generators/javascript/targets.ts b/src/generators/javascript/targets.ts
index 8a5c51f7..2863fdb3 100644
--- a/src/generators/javascript/targets.ts
+++ b/src/generators/javascript/targets.ts
@@ -1,55 +1,55 @@
// Helpers for mapping ruddertyper configuration options for module/script
// targets to TypeScript's compiler enums.
-import { ModuleKind, ScriptTarget } from 'typescript'
+import { ModuleKind, ScriptTarget } from 'typescript';
export function toTarget(target: string | undefined): ScriptTarget {
- if (!target) {
- return ScriptTarget.ESNext
- }
+ if (!target) {
+ return ScriptTarget.ESNext;
+ }
- switch (target) {
- case 'ES3':
- return ScriptTarget.ES3
- case 'ES5':
- return ScriptTarget.ES5
- case 'ES2015':
- return ScriptTarget.ES2015
- case 'ES2016':
- return ScriptTarget.ES2016
- case 'ES2017':
- return ScriptTarget.ES2017
- case 'ES2018':
- return ScriptTarget.ES2018
- case 'ES2019':
- return ScriptTarget.ES2019
- case 'ESNext':
- return ScriptTarget.ESNext
- case 'Latest':
- return ScriptTarget.Latest
- default:
- throw new Error(`Invalid scriptTarget: '${target}'`)
- }
+ switch (target) {
+ case 'ES3':
+ return ScriptTarget.ES3;
+ case 'ES5':
+ return ScriptTarget.ES5;
+ case 'ES2015':
+ return ScriptTarget.ES2015;
+ case 'ES2016':
+ return ScriptTarget.ES2016;
+ case 'ES2017':
+ return ScriptTarget.ES2017;
+ case 'ES2018':
+ return ScriptTarget.ES2018;
+ case 'ES2019':
+ return ScriptTarget.ES2019;
+ case 'ESNext':
+ return ScriptTarget.ESNext;
+ case 'Latest':
+ return ScriptTarget.Latest;
+ default:
+ throw new Error(`Invalid scriptTarget: '${target}'`);
+ }
}
export function toModule(target: string | undefined): ModuleKind {
- if (!target) {
- return ModuleKind.ESNext
- }
+ if (!target) {
+ return ModuleKind.ESNext;
+ }
- switch (target) {
- case 'CommonJS':
- return ModuleKind.CommonJS
- case 'AMD':
- return ModuleKind.AMD
- case 'UMD':
- return ModuleKind.UMD
- case 'System':
- return ModuleKind.System
- case 'ES2015':
- return ModuleKind.ES2015
- case 'ESNext':
- return ModuleKind.ESNext
- default:
- throw new Error(`Invalid moduleTarget: '${target}'`)
- }
+ switch (target) {
+ case 'CommonJS':
+ return ModuleKind.CommonJS;
+ case 'AMD':
+ return ModuleKind.AMD;
+ case 'UMD':
+ return ModuleKind.UMD;
+ case 'System':
+ return ModuleKind.System;
+ case 'ES2015':
+ return ModuleKind.ES2015;
+ case 'ESNext':
+ return ModuleKind.ESNext;
+ default:
+ throw new Error(`Invalid moduleTarget: '${target}'`);
+ }
}
diff --git a/src/generators/namer.ts b/src/generators/namer.ts
index 277f2bd7..7d11bb3c 100644
--- a/src/generators/namer.ts
+++ b/src/generators/namer.ts
@@ -1,152 +1,152 @@
export type Options = {
- // Words that are reserved by a given language, and which should not be allowed
- // for identifier names.
- reservedWords: string[]
- // String to use for quoted strings. Usually a single or double quote.
- quoteChar: string
- // A character set matching all characters that are allowed as the first character in an identifier.
- allowedIdentifierStartingChars: string
- // A character set matching all characters that are allowed within identifiers.
- allowedIdentifierChars: string
-}
+ // Words that are reserved by a given language, and which should not be allowed
+ // for identifier names.
+ reservedWords: string[];
+ // String to use for quoted strings. Usually a single or double quote.
+ quoteChar: string;
+ // A character set matching all characters that are allowed as the first character in an identifier.
+ allowedIdentifierStartingChars: string;
+ // A character set matching all characters that are allowed within identifiers.
+ allowedIdentifierChars: string;
+};
export type SanitizeOptions = {
- // A transformation that is applied before collision detection, but after registering
- // a name to the internal registry. It's recommended to apply a transform here, rather
- // than before calling register() since transformations are oftentimes lossy (camelcase,
- // f.e.) -- in other words, it can lead to collisions after transformation that don't exist
- // before.
- transform?: (name: string) => string
- // A set of strings that can be used as prefixes to avoid a collision, before
- // falling back on simple numeric transforms.
- prefixes?: string[]
- // An opaque identifier representing whatever this name represents. If the same
- // name + id combination has been seen before, then the same sanitized name will
- // be returned. This might be used, for example, to re-use interfaces if the JSON
- // Schema matches. If not specified, the same
- id?: string
-}
+ // A transformation that is applied before collision detection, but after registering
+ // a name to the internal registry. It's recommended to apply a transform here, rather
+ // than before calling register() since transformations are oftentimes lossy (camelcase,
+ // f.e.) -- in other words, it can lead to collisions after transformation that don't exist
+ // before.
+ transform?: (name: string) => string;
+ // A set of strings that can be used as prefixes to avoid a collision, before
+ // falling back on simple numeric transforms.
+ prefixes?: string[];
+ // An opaque identifier representing whatever this name represents. If the same
+ // name + id combination has been seen before, then the same sanitized name will
+ // be returned. This might be used, for example, to re-use interfaces if the JSON
+ // Schema matches. If not specified, the same
+ id?: string;
+};
export class Namer {
- private options: Options
- // Maps namespace -> Set of sanitized names
- private lookupByName: Record>
- // Maps (namespace, name, id) -> sanitized name
- private lookupByID: Record>>
-
- public constructor(options: Options) {
- this.options = options
- this.lookupByID = {}
- this.lookupByName = {}
-
- // Add the various analytics calls as reserved words.
- this.options.reservedWords.push(
- // v1
- 'track',
- 'identify',
- 'group',
- 'page',
- 'screen',
- 'alias',
- // v2
- 'set'
- )
- }
-
- /**
- * register registers a name within a given namespace. The sanitized, collision-free name is returned.
- *
- * An optional transform function can be supplied, which'll be applied during the sanitization process.
- */
- public register(name: string, namespace: string, options?: SanitizeOptions): string {
- // If an id was provided, check if we have a cached sanitized name for this id.
- if (options && options.id) {
- if (
- this.lookupByID[namespace] &&
- this.lookupByID[namespace][name] &&
- this.lookupByID[namespace][name][options.id]
- ) {
- return this.lookupByID[namespace][name][options.id]
- }
- }
-
- if (!this.lookupByName[namespace]) {
- this.lookupByName[namespace] = new Set()
- }
-
- // Otherwise, we need to generate a new sanitized name.
- const sanitizedName = this.uniqueify(name, namespace, options)
-
- // Reserve this newly generated name so that future calls will not re-reserve this name.
- this.lookupByName[namespace].add(sanitizedName)
- // Cache this newly generated name by the id.
- if (options && options.id) {
- if (!this.lookupByID[namespace]) {
- this.lookupByID[namespace] = {}
- }
- if (!this.lookupByID[namespace][name]) {
- this.lookupByID[namespace][name] = {}
- }
- this.lookupByID[namespace][name][options.id] = sanitizedName
- }
-
- return sanitizedName
- }
-
- /**
- * escapeString escapes quotes (and escapes) within a string so that it can safely generated.
- */
- public escapeString(str: string): string {
- return str
- .replace(/\\/g, '\\\\')
- .replace(new RegExp(this.options.quoteChar, 'g'), `\\` + this.options.quoteChar)
- }
-
- private uniqueify(name: string, namespace: string, options?: SanitizeOptions): string {
- // Find a unique name by using a prefix/suffix if necessary.
- let prefix = ''
- let suffix = ''
- while (this.lookupByName[namespace].has(this.sanitize(prefix + name + suffix, options))) {
- if (options && options.prefixes && options.prefixes.length > 0) {
- // If the user provided prefixes, first try to find a unique sanitized name with those prefixes.
- prefix = options.prefixes.shift()! + '_'
- suffix = ''
- } else {
- // Fallback on a numeric suffix.
- prefix = ''
- suffix = suffix === '' ? '1' : (parseInt(suffix) + 1).toString()
- }
- }
-
- return this.sanitize(prefix + name + suffix, options)
- }
-
- private sanitize(name: string, options?: SanitizeOptions): string {
- // Handle zero length names.
- if (name.length === 0) {
- name = 'EmptyIdentifier'
- }
-
- // Apply the optional user-supplied transform.
- if (options && options.transform) {
- name = options.transform(name)
- }
-
- // Handle names that are reserved words.
- if (this.options.reservedWords.includes(name)) {
- name += '_'
- }
-
- // Replace invalid characters within the name.
- const invalidChars = new RegExp(`[^${this.options.allowedIdentifierChars}]`, 'g')
- name = name.replace(invalidChars, '_')
-
- // Handle names that start with an invalid character.
- const invalidStartingChars = new RegExp(`^[^${this.options.allowedIdentifierStartingChars}]`)
- if (invalidStartingChars.test(name)) {
- name = `I${name}`
- }
-
- return name
- }
+ private options: Options;
+ // Maps namespace -> Set of sanitized names
+ private lookupByName: Record>;
+ // Maps (namespace, name, id) -> sanitized name
+ private lookupByID: Record>>;
+
+ public constructor(options: Options) {
+ this.options = options;
+ this.lookupByID = {};
+ this.lookupByName = {};
+
+ // Add the various analytics calls as reserved words.
+ this.options.reservedWords.push(
+ // v1
+ 'track',
+ 'identify',
+ 'group',
+ 'page',
+ 'screen',
+ 'alias',
+ // v2
+ 'set',
+ );
+ }
+
+ /**
+ * register registers a name within a given namespace. The sanitized, collision-free name is returned.
+ *
+ * An optional transform function can be supplied, which'll be applied during the sanitization process.
+ */
+ public register(name: string, namespace: string, options?: SanitizeOptions): string {
+ // If an id was provided, check if we have a cached sanitized name for this id.
+ if (options && options.id) {
+ if (
+ this.lookupByID[namespace] &&
+ this.lookupByID[namespace][name] &&
+ this.lookupByID[namespace][name][options.id]
+ ) {
+ return this.lookupByID[namespace][name][options.id];
+ }
+ }
+
+ if (!this.lookupByName[namespace]) {
+ this.lookupByName[namespace] = new Set();
+ }
+
+ // Otherwise, we need to generate a new sanitized name.
+ const sanitizedName = this.uniqueify(name, namespace, options);
+
+ // Reserve this newly generated name so that future calls will not re-reserve this name.
+ this.lookupByName[namespace].add(sanitizedName);
+ // Cache this newly generated name by the id.
+ if (options && options.id) {
+ if (!this.lookupByID[namespace]) {
+ this.lookupByID[namespace] = {};
+ }
+ if (!this.lookupByID[namespace][name]) {
+ this.lookupByID[namespace][name] = {};
+ }
+ this.lookupByID[namespace][name][options.id] = sanitizedName;
+ }
+
+ return sanitizedName;
+ }
+
+ /**
+ * escapeString escapes quotes (and escapes) within a string so that it can safely generated.
+ */
+ public escapeString(str: string): string {
+ return str
+ .replace(/\\/g, '\\\\')
+ .replace(new RegExp(this.options.quoteChar, 'g'), `\\` + this.options.quoteChar);
+ }
+
+ private uniqueify(name: string, namespace: string, options?: SanitizeOptions): string {
+ // Find a unique name by using a prefix/suffix if necessary.
+ let prefix = '';
+ let suffix = '';
+ while (this.lookupByName[namespace].has(this.sanitize(prefix + name + suffix, options))) {
+ if (options && options.prefixes && options.prefixes.length > 0) {
+ // If the user provided prefixes, first try to find a unique sanitized name with those prefixes.
+ prefix = options.prefixes.shift()! + '_';
+ suffix = '';
+ } else {
+ // Fallback on a numeric suffix.
+ prefix = '';
+ suffix = suffix === '' ? '1' : (parseInt(suffix) + 1).toString();
+ }
+ }
+
+ return this.sanitize(prefix + name + suffix, options);
+ }
+
+ private sanitize(name: string, options?: SanitizeOptions): string {
+ // Handle zero length names.
+ if (name.length === 0) {
+ name = 'EmptyIdentifier';
+ }
+
+ // Apply the optional user-supplied transform.
+ if (options && options.transform) {
+ name = options.transform(name);
+ }
+
+ // Handle names that are reserved words.
+ if (this.options.reservedWords.includes(name)) {
+ name += '_';
+ }
+
+ // Replace invalid characters within the name.
+ const invalidChars = new RegExp(`[^${this.options.allowedIdentifierChars}]`, 'g');
+ name = name.replace(invalidChars, '_');
+
+ // Handle names that start with an invalid character.
+ const invalidStartingChars = new RegExp(`^[^${this.options.allowedIdentifierStartingChars}]`);
+ if (invalidStartingChars.test(name)) {
+ name = `I${name}`;
+ }
+
+ return name;
+ }
}
diff --git a/src/generators/objc/index.ts b/src/generators/objc/index.ts
index 9039255c..ab9aee3b 100644
--- a/src/generators/objc/index.ts
+++ b/src/generators/objc/index.ts
@@ -1 +1 @@
-export { objc } from './objc'
+export { objc } from './objc';
diff --git a/src/generators/objc/objc.ts b/src/generators/objc/objc.ts
index 9f9f0c4e..31aa94ca 100644
--- a/src/generators/objc/objc.ts
+++ b/src/generators/objc/objc.ts
@@ -1,52 +1,52 @@
-import { camelCase, upperFirst } from 'lodash'
-import { Type, Schema } from '../ast'
-import * as Handlebars from 'handlebars'
-import { Generator, BasePropertyContext, GeneratorClient } from '../gen'
+import { camelCase, upperFirst } from 'lodash';
+import { Type, Schema } from '../ast';
+import * as Handlebars from 'handlebars';
+import { Generator, BasePropertyContext, GeneratorClient } from '../gen';
// These contexts are what will be passed to Handlebars to perform rendering.
// Everything in these contexts should be properly sanitized.
type ObjCObjectContext = {
- // The formatted name for this object, ex: "numAvocados".
- name: string
- // Set of files that need to be imported in this file.
- imports: string[]
-}
+ // The formatted name for this object, ex: "numAvocados".
+ name: string;
+ // Set of files that need to be imported in this file.
+ imports: string[];
+};
type ObjCPropertyContext = {
- // The formatted name for this property, ex: "numAvocados".
- name: string
- // The type of this property. ex: "NSNumber".
- type: string
- // Stringified property modifiers. ex: "nonatomic, copy".
- modifiers: string
- // Whether the property is nullable (nonnull vs nullable modifier).
- isVariableNullable: boolean
- // Whether null is a valid value for this property when sent to Rudderstack.
- isPayloadFieldNullable: boolean
- // Whether the Objective-C type is a pointer (id, SERIALIZABLE_DICT, NSNumber *, ...).
- isPointerType: boolean
- // Note: only set if this is a class.
- // The header file containing the interface for this class.
- importName?: string
-}
+ // The formatted name for this property, ex: "numAvocados".
+ name: string;
+ // The type of this property. ex: "NSNumber".
+ type: string;
+ // Stringified property modifiers. ex: "nonatomic, copy".
+ modifiers: string;
+ // Whether the property is nullable (nonnull vs nullable modifier).
+ isVariableNullable: boolean;
+ // Whether null is a valid value for this property when sent to Rudderstack.
+ isPayloadFieldNullable: boolean;
+ // Whether the Objective-C type is a pointer (id, SERIALIZABLE_DICT, NSNumber *, ...).
+ isPointerType: boolean;
+ // Note: only set if this is a class.
+ // The header file containing the interface for this class.
+ importName?: string;
+};
type ObjCTrackCallContext = {
- // The formatted function name, ex: "orderCompleted".
- functionName: string
-}
+ // The formatted function name, ex: "orderCompleted".
+ functionName: string;
+};
export const objc: Generator<
- Record,
- ObjCTrackCallContext,
- ObjCObjectContext,
- ObjCPropertyContext
+ Record,
+ ObjCTrackCallContext,
+ ObjCObjectContext,
+ ObjCPropertyContext
> = {
- generatePropertiesObject: false,
- namer: {
- // See: https://github.com/AnanthaRajuCprojects/Reserved-Key-Words-list-of-various-programming-languages/blob/master/Objective-C%20Reserved%20Words.md
- // prettier-ignore
- reservedWords: [
+ generatePropertiesObject: false,
+ namer: {
+ // See: https://github.com/AnanthaRajuCprojects/Reserved-Key-Words-list-of-various-programming-languages/blob/master/Objective-C%20Reserved%20Words.md
+ // prettier-ignore
+ reservedWords: [
'asm', 'atomic', 'auto', 'bool', 'break', 'bycopy', 'byref', 'case', 'catch', 'char',
'class', 'const', 'continue', 'copy', 'debugDescription', 'default', 'description',
'do', 'double', 'dynamic', 'else', 'end', 'enum', 'extern', 'false', 'finally', 'float',
@@ -58,262 +58,258 @@ export const objc: Generator<
'throw', 'true', 'try', 'typedef', 'typeof', 'union', 'unsigned', 'void', 'volatile', 'while',
'yes'
],
- quoteChar: '"',
- allowedIdentifierStartingChars: 'A-Za-z_$',
- allowedIdentifierChars: 'A-Za-z0-9_$',
- },
- setup: async () => {
- Handlebars.registerHelper('propertiesDictionary', generatePropertiesDictionary)
- Handlebars.registerHelper('functionCall', generateFunctionCall)
- Handlebars.registerHelper('functionSignature', generateFunctionSignature)
- Handlebars.registerHelper('variableSeparator', variableSeparator)
- return {}
- },
- generatePrimitive: async (client, schema, parentPath) => {
- let type = 'id'
- let isPointerType = !schema.isRequired || !!schema.isNullable
+ quoteChar: '"',
+ allowedIdentifierStartingChars: 'A-Za-z_$',
+ allowedIdentifierChars: 'A-Za-z0-9_$',
+ },
+ setup: async () => {
+ Handlebars.registerHelper('propertiesDictionary', generatePropertiesDictionary);
+ Handlebars.registerHelper('functionCall', generateFunctionCall);
+ Handlebars.registerHelper('functionSignature', generateFunctionSignature);
+ Handlebars.registerHelper('variableSeparator', variableSeparator);
+ return {};
+ },
+ generatePrimitive: async (client, schema, parentPath) => {
+ let type = 'id';
+ let isPointerType = !schema.isRequired || !!schema.isNullable;
- if (schema.type === Type.STRING) {
- type = 'NSString *'
- isPointerType = true
- } else if (schema.type === Type.BOOLEAN) {
- // BOOLs cannot nullable in Objective-C. Instead, use an NSNumber which can be
- // initialized like a boolean like so: [NSNumber numberWithBool:YES]
- // This is what is done behind the scenes by ruddertyper if this boolean is nonnull.
- type = isPointerType ? 'NSNumber *' : 'BOOL'
- } else if (schema.type === Type.INTEGER) {
- type = isPointerType ? 'NSNumber *' : 'NSInteger'
- } else if (schema.type === Type.NUMBER) {
- type = 'NSNumber *'
- isPointerType = true
- }
+ if (schema.type === Type.STRING) {
+ type = 'NSString *';
+ isPointerType = true;
+ } else if (schema.type === Type.BOOLEAN) {
+ // BOOLs cannot nullable in Objective-C. Instead, use an NSNumber which can be
+ // initialized like a boolean like so: [NSNumber numberWithBool:YES]
+ // This is what is done behind the scenes by ruddertyper if this boolean is nonnull.
+ type = isPointerType ? 'NSNumber *' : 'BOOL';
+ } else if (schema.type === Type.INTEGER) {
+ type = isPointerType ? 'NSNumber *' : 'NSInteger';
+ } else if (schema.type === Type.NUMBER) {
+ type = 'NSNumber *';
+ isPointerType = true;
+ }
- return defaultPropertyContext(client, schema, type, parentPath, isPointerType)
- },
- generateArray: async (client, schema, items, parentPath) => {
- // Objective-C doesn't support NSArray's of primitives. Therefore, we
- // map booleans and integers to NSNumbers.
- const itemsType = [Type.BOOLEAN, Type.INTEGER].includes(items.schemaType)
- ? 'NSNumber *'
- : items.type
+ return defaultPropertyContext(client, schema, type, parentPath, isPointerType);
+ },
+ generateArray: async (client, schema, items, parentPath) => {
+ // Objective-C doesn't support NSArray's of primitives. Therefore, we
+ // map booleans and integers to NSNumbers.
+ const itemsType = [Type.BOOLEAN, Type.INTEGER].includes(items.schemaType)
+ ? 'NSNumber *'
+ : items.type;
- return {
- ...defaultPropertyContext(client, schema, `NSArray<${itemsType}> *`, parentPath, true),
- importName: items.importName,
- }
- },
- generateObject: async (client, schema, properties, parentPath) => {
- const property = defaultPropertyContext(client, schema, 'NSDictionary *', parentPath, true)
- let object: ObjCObjectContext | undefined = undefined
+ return {
+ ...defaultPropertyContext(client, schema, `NSArray<${itemsType}> *`, parentPath, true),
+ importName: items.importName,
+ };
+ },
+ generateObject: async (client, schema, properties, parentPath) => {
+ const property = defaultPropertyContext(client, schema, 'NSDictionary *', parentPath, true);
+ let object: ObjCObjectContext | undefined = undefined;
- if (properties.length > 0) {
- // If at least one property is set, generate a class that only allows the explicitly
- // allowed properties.
- const className = client.namer.register(schema.name, 'class', {
- transform: (name: string) => {
- return `RS${upperFirst(camelCase(name))}`
- },
- })
- property.type = `${className} *`
- property.importName = `"${className}.h"`
- object = {
- name: className,
- imports: properties.filter(p => !!p.importName).map(p => p.importName!),
- }
- }
+ if (properties.length > 0) {
+ // If at least one property is set, generate a class that only allows the explicitly
+ // allowed properties.
+ const className = client.namer.register(schema.name, 'class', {
+ transform: (name: string) => {
+ return `RS${upperFirst(camelCase(name))}`;
+ },
+ });
+ property.type = `${className} *`;
+ property.importName = `"${className}.h"`;
+ object = {
+ name: className,
+ imports: properties.filter(p => !!p.importName).map(p => p.importName!),
+ };
+ }
- return { property, object }
- },
- generateUnion: async (client, schema, _, parentPath) => {
- // TODO: support unions in iOS
- return defaultPropertyContext(client, schema, 'id', parentPath, true)
- },
- generateTrackCall: async (_client, _schema, functionName) => ({
- functionName: functionName,
- }),
- generateRoot: async (client, context) => {
- await Promise.all([
- client.generateFile(
- 'RSRudderTyperAnalytics.h',
- 'generators/objc/templates/analytics.h.hbs',
- context
- ),
- client.generateFile(
- 'RSRudderTyperAnalytics.m',
- 'generators/objc/templates/analytics.m.hbs',
- context
- ),
- client.generateFile(
- 'RSRudderTyperUtils.h',
- 'generators/objc/templates/RSRudderTyperUtils.h.hbs',
- context
- ),
- client.generateFile(
- 'RSRudderTyperUtils.m',
- 'generators/objc/templates/RSRudderTyperUtils.m.hbs',
- context
- ),
- client.generateFile(
- 'RSRudderTyperSerializable.h',
- 'generators/objc/templates/RSRudderTyperSerializable.h.hbs',
- context
- ),
- ...context.objects.map(o =>
- client.generateFile(`${o.name}.h`, 'generators/objc/templates/class.h.hbs', o)
- ),
- ...context.objects.map(o =>
- client.generateFile(`${o.name}.m`, 'generators/objc/templates/class.m.hbs', o)
- ),
- ])
- },
-}
+ return { property, object };
+ },
+ generateUnion: async (client, schema, _, parentPath) => {
+ // TODO: support unions in iOS
+ return defaultPropertyContext(client, schema, 'id', parentPath, true);
+ },
+ generateTrackCall: async (_client, _schema, functionName) => ({
+ functionName: functionName,
+ }),
+ generateRoot: async (client, context) => {
+ await Promise.all([
+ client.generateFile(
+ 'RSRudderTyperAnalytics.h',
+ 'generators/objc/templates/analytics.h.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'RSRudderTyperAnalytics.m',
+ 'generators/objc/templates/analytics.m.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'RSRudderTyperUtils.h',
+ 'generators/objc/templates/RSRudderTyperUtils.h.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'RSRudderTyperUtils.m',
+ 'generators/objc/templates/RSRudderTyperUtils.m.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'RSRudderTyperSerializable.h',
+ 'generators/objc/templates/RSRudderTyperSerializable.h.hbs',
+ context,
+ ),
+ ...context.objects.map(o =>
+ client.generateFile(`${o.name}.h`, 'generators/objc/templates/class.h.hbs', o),
+ ),
+ ...context.objects.map(o =>
+ client.generateFile(`${o.name}.m`, 'generators/objc/templates/class.m.hbs', o),
+ ),
+ ]);
+ },
+};
function defaultPropertyContext(
- client: GeneratorClient,
- schema: Schema,
- type: string,
- namespace: string,
- isPointerType: boolean
+ client: GeneratorClient,
+ schema: Schema,
+ type: string,
+ namespace: string,
+ isPointerType: boolean,
): ObjCPropertyContext {
- return {
- name: client.namer.register(schema.name, namespace, {
- transform: camelCase,
- }),
- type,
- modifiers: isPointerType
- ? schema.isRequired
- ? 'strong, nonatomic, nonnull'
- : 'strong, nonatomic, nullable'
- : 'nonatomic',
- isVariableNullable: !schema.isRequired || !!schema.isNullable,
- isPayloadFieldNullable: !!schema.isNullable && !!schema.isRequired,
- isPointerType,
- }
+ return {
+ name: client.namer.register(schema.name, namespace, {
+ transform: camelCase,
+ }),
+ type,
+ modifiers: isPointerType
+ ? schema.isRequired
+ ? 'strong, nonatomic, nonnull'
+ : 'strong, nonatomic, nullable'
+ : 'nonatomic',
+ isVariableNullable: !schema.isRequired || !!schema.isNullable,
+ isPayloadFieldNullable: !!schema.isNullable && !!schema.isRequired,
+ isPointerType,
+ };
}
// Handlebars partials
function generateFunctionSignature(
- functionName: string,
- properties: (BasePropertyContext & ObjCPropertyContext)[],
- withOptions: boolean
+ functionName: string,
+ properties: (BasePropertyContext & ObjCPropertyContext)[],
+ withOptions: boolean,
): string {
- let signature = functionName
- const parameters: {
- type: string
- name: string
- isPointerType: boolean
- isVariableNullable: boolean
- }[] = [...properties]
- if (withOptions) {
- parameters.push({
- name: 'options',
- type: 'RSOption *',
- isPointerType: true,
- isVariableNullable: true,
- })
- }
+ let signature = functionName;
+ const parameters: {
+ type: string;
+ name: string;
+ isPointerType: boolean;
+ isVariableNullable: boolean;
+ }[] = [...properties];
+ if (withOptions) {
+ parameters.push({
+ name: 'options',
+ type: 'RSOption *',
+ isPointerType: true,
+ isVariableNullable: true,
+ });
+ }
- const withNullability = (property: {
- type: string
- isPointerType: boolean
- isVariableNullable: boolean
- }) => {
- const { isPointerType, type, isVariableNullable } = property
- return isPointerType ? `${isVariableNullable ? 'nullable' : 'nonnull'} ${type}` : type
- }
+ const withNullability = (property: {
+ type: string;
+ isPointerType: boolean;
+ isVariableNullable: boolean;
+ }) => {
+ const { isPointerType, type, isVariableNullable } = property;
+ return isPointerType ? `${isVariableNullable ? 'nullable' : 'nonnull'} ${type}` : type;
+ };
- // Mutate the function name to match standard Objective-C naming standards (FooBar vs. FooBarWithSparkles:sparkles).
- if (parameters.length > 0) {
- const first = parameters[0]
- signature += `With${upperFirst(first.name)}:(${withNullability(first)})${first.name}\n`
- }
- for (const parameter of parameters.slice(1)) {
- signature += `${parameter.name}:(${withNullability(parameter)})${parameter.name}\n`
- }
+ // Mutate the function name to match standard Objective-C naming standards (FooBar vs. FooBarWithSparkles:sparkles).
+ if (parameters.length > 0) {
+ const first = parameters[0];
+ signature += `With${upperFirst(first.name)}:(${withNullability(first)})${first.name}\n`;
+ }
+ for (const parameter of parameters.slice(1)) {
+ signature += `${parameter.name}:(${withNullability(parameter)})${parameter.name}\n`;
+ }
- return signature.trim()
+ return signature.trim();
}
function generateFunctionCall(
- caller: string,
- functionName: string,
- properties: (BasePropertyContext & ObjCPropertyContext)[],
- extraParameterName?: string,
- extraParameterValue?: string
+ caller: string,
+ functionName: string,
+ properties: (BasePropertyContext & ObjCPropertyContext)[],
+ extraParameterName?: string,
+ extraParameterValue?: string,
): string {
- let functionCall = functionName
- const parameters: { name: string; value: string }[] = properties.map(p => ({
- name: p.name,
- value: p.name,
- }))
- if (extraParameterName && extraParameterValue) {
- parameters.push({
- name: extraParameterName,
- value: extraParameterValue,
- })
- }
+ let functionCall = functionName;
+ const parameters: { name: string; value: string }[] = properties.map(p => ({
+ name: p.name,
+ value: p.name,
+ }));
+ if (extraParameterName && extraParameterValue) {
+ parameters.push({
+ name: extraParameterName,
+ value: extraParameterValue,
+ });
+ }
- if (parameters.length > 0) {
- const { name, value } = parameters[0]
- functionCall += `With${upperFirst(name)}:${value}`
- }
- for (const { name, value } of parameters.slice(1)) {
- functionCall += ` ${name}:${value}`
- }
+ if (parameters.length > 0) {
+ const { name, value } = parameters[0];
+ functionCall += `With${upperFirst(name)}:${value}`;
+ }
+ for (const { name, value } of parameters.slice(1)) {
+ functionCall += ` ${name}:${value}`;
+ }
- return `[${caller} ${functionCall.trim()}];`
+ return `[${caller} ${functionCall.trim()}];`;
}
function generatePropertiesDictionary(
- properties: (BasePropertyContext & ObjCPropertyContext)[],
- prefix?: string
+ properties: (BasePropertyContext & ObjCPropertyContext)[],
+ prefix?: string,
): string {
- let out = 'NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];\n'
- for (const property of properties) {
- const name = prefix && prefix.length > 0 ? `${prefix}${property.name}` : property.name
- const serializableName =
- property.schemaType === Type.BOOLEAN
- ? property.isPointerType
- ? name
- : `[NSNumber numberWithBool:${name}]`
- : property.schemaType === Type.INTEGER
- ? property.isPointerType
- ? name
- : `[NSNumber numberWithInteger:${name}]`
- : property.schemaType === Type.OBJECT && !property.type.includes('NSDictionary')
- ? `[${name} toDictionary]`
- : property.schemaType === Type.ARRAY
- ? `[RSRudderTyperUtils toSerializableArray:${name}]`
- : name
+ let out = 'NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];\n';
+ for (const property of properties) {
+ const name = prefix && prefix.length > 0 ? `${prefix}${property.name}` : property.name;
+ const serializableName =
+ property.schemaType === Type.BOOLEAN
+ ? property.isPointerType
+ ? name
+ : `[NSNumber numberWithBool:${name}]`
+ : property.schemaType === Type.INTEGER
+ ? property.isPointerType
+ ? name
+ : `[NSNumber numberWithInteger:${name}]`
+ : property.schemaType === Type.OBJECT && !property.type.includes('NSDictionary')
+ ? `[${name} toDictionary]`
+ : property.schemaType === Type.ARRAY
+ ? `[RSRudderTyperUtils toSerializableArray:${name}]`
+ : name;
- let setter: string
- if (property.isPointerType) {
- if (property.isPayloadFieldNullable) {
- // If the value is nil, we need to convert it from a primitive nil to NSNull (an object).
- setter = `properties[@"${
- property.rawName
- }"] = ${name} == nil ? [NSNull null] : ${serializableName};\n`
- } else {
- // If the property is not nullable, but is a pointer, then we need to guard on nil
- // values. In that case, we don't set any value to the field.
- // TODO: do we need these guards if we've already set a field as nonnull? TBD
- setter = `if (${name} != nil) {\n properties[@"${
- property.rawName
- }"] = ${serializableName};\n}\n`
- }
- } else {
- setter = `properties[@"${property.rawName}"] = ${serializableName};\n`
- }
+ let setter: string;
+ if (property.isPointerType) {
+ if (property.isPayloadFieldNullable) {
+ // If the value is nil, we need to convert it from a primitive nil to NSNull (an object).
+ setter = `properties[@"${property.rawName}"] = ${name} == nil ? [NSNull null] : ${serializableName};\n`;
+ } else {
+ // If the property is not nullable, but is a pointer, then we need to guard on nil
+ // values. In that case, we don't set any value to the field.
+ // TODO: do we need these guards if we've already set a field as nonnull? TBD
+ setter = `if (${name} != nil) {\n properties[@"${property.rawName}"] = ${serializableName};\n}\n`;
+ }
+ } else {
+ setter = `properties[@"${property.rawName}"] = ${serializableName};\n`;
+ }
- out += setter
- }
+ out += setter;
+ }
- return out
+ return out;
}
// Render `NSString *foo` not `NSString * foo` and `BOOL foo` not `BOOLfoo` or `BOOL foo` by doing:
// `{{type}}{{variableSeparator type}}{{name}}`
function variableSeparator(type: string): string {
- return type.endsWith('*') ? '' : ' '
+ return type.endsWith('*') ? '' : ' ';
}
diff --git a/src/generators/options.ts b/src/generators/options.ts
index 62d2345f..e3303b93 100644
--- a/src/generators/options.ts
+++ b/src/generators/options.ts
@@ -1,60 +1,60 @@
// Which RudderStack SDK to generate for.
export enum SDK {
- WEB = 'analytics.js',
- NODE = 'analytics-node',
- IOS = 'analytics-ios',
- ANDROID = 'analytics-android',
+ WEB = 'analytics.js',
+ NODE = 'analytics-node',
+ IOS = 'analytics-ios',
+ ANDROID = 'analytics-android',
}
// Which language to generate clients for.
export enum Language {
- TYPESCRIPT = 'typescript',
- JAVASCRIPT = 'javascript',
- OBJECTIVE_C = 'objective-c',
- SWIFT = 'swift',
- JAVA = 'java',
+ TYPESCRIPT = 'typescript',
+ JAVASCRIPT = 'javascript',
+ OBJECTIVE_C = 'objective-c',
+ SWIFT = 'swift',
+ JAVA = 'java',
}
export type TypeScriptOptions = {
- sdk: SDK.WEB | SDK.NODE
- language: Language.TYPESCRIPT
-}
+ sdk: SDK.WEB | SDK.NODE;
+ language: Language.TYPESCRIPT;
+};
export type JavaScriptOptions = {
- sdk: SDK.WEB | SDK.NODE
- language: Language.JAVASCRIPT
- // JavaScript transpilation settings:
- scriptTarget?:
- | 'ES3'
- | 'ES5'
- | 'ES2015'
- | 'ES2016'
- | 'ES2017'
- | 'ES2018'
- | 'ES2019'
- | 'ESNext'
- | 'Latest'
- moduleTarget?: 'CommonJS' | 'AMD' | 'UMD' | 'System' | 'ES2015' | 'ESNext'
-}
+ sdk: SDK.WEB | SDK.NODE;
+ language: Language.JAVASCRIPT;
+ // JavaScript transpilation settings:
+ scriptTarget?:
+ | 'ES3'
+ | 'ES5'
+ | 'ES2015'
+ | 'ES2016'
+ | 'ES2017'
+ | 'ES2018'
+ | 'ES2019'
+ | 'ESNext'
+ | 'Latest';
+ moduleTarget?: 'CommonJS' | 'AMD' | 'UMD' | 'System' | 'ES2015' | 'ESNext';
+};
export type ObjectiveCOptions = {
- sdk: SDK.IOS
- language: Language.OBJECTIVE_C
-}
+ sdk: SDK.IOS;
+ language: Language.OBJECTIVE_C;
+};
export type SwiftOptions = {
- sdk: SDK.IOS
- language: Language.SWIFT
-}
+ sdk: SDK.IOS;
+ language: Language.SWIFT;
+};
export type JavaOptions = {
- sdk: SDK.ANDROID
- language: Language.JAVA
-}
+ sdk: SDK.ANDROID;
+ language: Language.JAVA;
+};
export type Options =
- | JavaScriptOptions
- | ObjectiveCOptions
- | SwiftOptions
- | JavaOptions
- | TypeScriptOptions
+ | JavaScriptOptions
+ | ObjectiveCOptions
+ | SwiftOptions
+ | JavaOptions
+ | TypeScriptOptions;
diff --git a/src/generators/swift/index.ts b/src/generators/swift/index.ts
index bd5f1404..d564d2bf 100644
--- a/src/generators/swift/index.ts
+++ b/src/generators/swift/index.ts
@@ -1 +1 @@
-export { swift } from './swift'
+export { swift } from './swift';
diff --git a/src/generators/swift/swift.ts b/src/generators/swift/swift.ts
index 93e25917..18513df3 100644
--- a/src/generators/swift/swift.ts
+++ b/src/generators/swift/swift.ts
@@ -1,50 +1,50 @@
-import { camelCase, upperFirst } from 'lodash'
-import { Type, Schema } from '../ast'
-import * as Handlebars from 'handlebars'
-import { Generator, BasePropertyContext, GeneratorClient } from '../gen'
+import { camelCase, upperFirst } from 'lodash';
+import { Type, Schema } from '../ast';
+import * as Handlebars from 'handlebars';
+import { Generator, BasePropertyContext, GeneratorClient } from '../gen';
// These contexts are what will be passed to Handlebars to perform rendering.
// Everything in these contexts should be properly sanitized.
type SwiftObjectContext = {
- // The formatted name for this object, ex: "numAvocados".
- name: string
- // Set of files that need to be imported in this file.
- imports: string[]
-}
+ // The formatted name for this object, ex: "numAvocados".
+ name: string;
+ // Set of files that need to be imported in this file.
+ imports: string[];
+};
type SwiftPropertyContext = {
- // The formatted name for this property, ex: "numAvocados".
- name: string
- // The type of this property. ex: "NSNumber".
- type: string
- // Whether the property is nullable (nonnull vs nullable modifier).
- isVariableNullable: boolean
- // Whether null is a valid value for this property when sent to Rudderstack.
- isPayloadFieldNullable: boolean
- // Whether the Objective-C type is a pointer (id, SERIALIZABLE_DICT, NSNumber *, ...).
- isPointerType: boolean
- // Note: only set if this is a class.
- // The header file containing the interface for this class.
- importName?: string
-}
+ // The formatted name for this property, ex: "numAvocados".
+ name: string;
+ // The type of this property. ex: "NSNumber".
+ type: string;
+ // Whether the property is nullable (nonnull vs nullable modifier).
+ isVariableNullable: boolean;
+ // Whether null is a valid value for this property when sent to Rudderstack.
+ isPayloadFieldNullable: boolean;
+ // Whether the Objective-C type is a pointer (id, SERIALIZABLE_DICT, NSNumber *, ...).
+ isPointerType: boolean;
+ // Note: only set if this is a class.
+ // The header file containing the interface for this class.
+ importName?: string;
+};
type SwiftTrackCallContext = {
- // The formatted function name, ex: "orderCompleted".
- functionName: string
-}
+ // The formatted function name, ex: "orderCompleted".
+ functionName: string;
+};
export const swift: Generator<
- Record,
- SwiftTrackCallContext,
- SwiftObjectContext,
- SwiftPropertyContext
+ Record,
+ SwiftTrackCallContext,
+ SwiftObjectContext,
+ SwiftPropertyContext
> = {
- generatePropertiesObject: false,
- namer: {
- // See: https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413
- // prettier-ignore
- reservedWords: [
+ generatePropertiesObject: false,
+ namer: {
+ // See: https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413
+ // prettier-ignore
+ reservedWords: [
'associatedtype', 'class', 'deinit', 'enum', 'extension', 'fileprivate', 'func', 'import', 'init',
'inout', 'internal', 'let', 'open', 'operator', 'private', 'protocol', 'public', 'rethrows', 'static',
'struct', 'subscript', 'typealias', 'var', 'break', 'case', 'continue', 'default', 'defer', 'do', 'else',
@@ -54,239 +54,237 @@ export const swift: Generator<
'mutating', 'none', 'nonmutating', 'optional', 'override', 'postfix', 'precedence', 'prefix', 'Protocol',
'required', 'right', 'set', 'Type', 'unowned', 'weak', 'willSet'
],
- quoteChar: '"',
- allowedIdentifierStartingChars: 'A-Za-z_$',
- allowedIdentifierChars: 'A-Za-z0-9_$',
- },
- setup: async () => {
- Handlebars.registerHelper('propertiesDictionary', generatePropertiesDictionary)
- Handlebars.registerHelper('functionCall', generateFunctionCall)
- Handlebars.registerHelper('functionSignature', generateFunctionSignature)
- return {}
- },
- generatePrimitive: async (client, schema, parentPath) => {
- let type = 'Any'
- let isPointerType = !schema.isRequired || !!schema.isNullable
+ quoteChar: '"',
+ allowedIdentifierStartingChars: 'A-Za-z_$',
+ allowedIdentifierChars: 'A-Za-z0-9_$',
+ },
+ setup: async () => {
+ Handlebars.registerHelper('propertiesDictionary', generatePropertiesDictionary);
+ Handlebars.registerHelper('functionCall', generateFunctionCall);
+ Handlebars.registerHelper('functionSignature', generateFunctionSignature);
+ return {};
+ },
+ generatePrimitive: async (client, schema, parentPath) => {
+ let type = 'Any';
+ let isPointerType = !schema.isRequired || !!schema.isNullable;
- if (schema.type === Type.STRING) {
- type = 'String'
- isPointerType = true
- } else if (schema.type === Type.BOOLEAN) {
- // BOOLs cannot nullable in Objective-C. Instead, use an NSNumber which can be
- // initialized like a boolean like so: [NSNumber numberWithBool:YES]
- // This is what is done behind the scenes by ruddertyper if this boolean is nonnull.
- type = 'Bool'
- } else if (schema.type === Type.INTEGER) {
- type = 'Int'
- } else if (schema.type === Type.NUMBER) {
- type = 'Decimal'
- isPointerType = true
- }
+ if (schema.type === Type.STRING) {
+ type = 'String';
+ isPointerType = true;
+ } else if (schema.type === Type.BOOLEAN) {
+ // BOOLs cannot nullable in Objective-C. Instead, use an NSNumber which can be
+ // initialized like a boolean like so: [NSNumber numberWithBool:YES]
+ // This is what is done behind the scenes by ruddertyper if this boolean is nonnull.
+ type = 'Bool';
+ } else if (schema.type === Type.INTEGER) {
+ type = 'Int';
+ } else if (schema.type === Type.NUMBER) {
+ type = 'Decimal';
+ isPointerType = true;
+ }
- return defaultPropertyContext(client, schema, type, parentPath, isPointerType)
- },
- generateArray: async (client, schema, items, parentPath) => {
- // Objective-C doesn't support NSArray's of primitives. Therefore, we
- // map booleans and integers to NSNumbers.
- const itemsType = items.type
+ return defaultPropertyContext(client, schema, type, parentPath, isPointerType);
+ },
+ generateArray: async (client, schema, items, parentPath) => {
+ // Objective-C doesn't support NSArray's of primitives. Therefore, we
+ // map booleans and integers to NSNumbers.
+ const itemsType = items.type;
- return {
- ...defaultPropertyContext(client, schema, `[${itemsType}]`, parentPath, true),
- importName: items.importName,
- }
- },
- generateObject: async (client, schema, properties, parentPath) => {
- const property = defaultPropertyContext(client, schema, '[String: Any]', parentPath, true)
- let object: SwiftObjectContext | undefined = undefined
+ return {
+ ...defaultPropertyContext(client, schema, `[${itemsType}]`, parentPath, true),
+ importName: items.importName,
+ };
+ },
+ generateObject: async (client, schema, properties, parentPath) => {
+ const property = defaultPropertyContext(client, schema, '[String: Any]', parentPath, true);
+ let object: SwiftObjectContext | undefined = undefined;
- if (properties.length > 0) {
- // If at least one property is set, generate a class that only allows the explicitly
- // allowed properties.
- const className = client.namer.register(schema.name, 'class', {
- transform: (name: string) => {
- return `${upperFirst(camelCase(name))}`
- },
- })
- property.type = `${className}`
- property.importName = `"${className}.h"`
- object = {
- name: className,
- imports: properties.filter(p => !!p.importName).map(p => p.importName!),
- }
- }
+ if (properties.length > 0) {
+ // If at least one property is set, generate a class that only allows the explicitly
+ // allowed properties.
+ const className = client.namer.register(schema.name, 'class', {
+ transform: (name: string) => {
+ return `${upperFirst(camelCase(name))}`;
+ },
+ });
+ property.type = `${className}`;
+ property.importName = `"${className}.h"`;
+ object = {
+ name: className,
+ imports: properties.filter(p => !!p.importName).map(p => p.importName!),
+ };
+ }
- return { property, object }
- },
- generateUnion: async (client, schema, _, parentPath) => {
- // TODO: support unions in iOS
- return defaultPropertyContext(client, schema, 'Any', parentPath, true)
- },
- generateTrackCall: async (_client, _schema, functionName) => ({
- functionName: functionName,
- }),
- generateRoot: async (client, context) => {
- await Promise.all([
- client.generateFile(
- 'RudderTyperAnalytics.swift',
- 'generators/swift/templates/analytics.swift.hbs',
- context
- ),
- client.generateFile(
- 'RudderTyperUtils.swift',
- 'generators/swift/templates/RudderTyperUtils.swift.hbs',
- context
- ),
- client.generateFile(
- 'RudderTyperSerializable.swift',
- 'generators/swift/templates/RudderTyperSerializable.swift.hbs',
- context
- ),
- ...context.objects.map(o =>
- client.generateFile(`${o.name}.swift`, 'generators/swift/templates/class.swift.hbs', o)
- ),
- ])
- },
-}
+ return { property, object };
+ },
+ generateUnion: async (client, schema, _, parentPath) => {
+ // TODO: support unions in iOS
+ return defaultPropertyContext(client, schema, 'Any', parentPath, true);
+ },
+ generateTrackCall: async (_client, _schema, functionName) => ({
+ functionName: functionName,
+ }),
+ generateRoot: async (client, context) => {
+ await Promise.all([
+ client.generateFile(
+ 'RudderTyperAnalytics.swift',
+ 'generators/swift/templates/analytics.swift.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'RudderTyperUtils.swift',
+ 'generators/swift/templates/RudderTyperUtils.swift.hbs',
+ context,
+ ),
+ client.generateFile(
+ 'RudderTyperSerializable.swift',
+ 'generators/swift/templates/RudderTyperSerializable.swift.hbs',
+ context,
+ ),
+ ...context.objects.map(o =>
+ client.generateFile(`${o.name}.swift`, 'generators/swift/templates/class.swift.hbs', o),
+ ),
+ ]);
+ },
+};
function defaultPropertyContext(
- client: GeneratorClient,
- schema: Schema,
- type: string,
- namespace: string,
- isPointerType: boolean
+ client: GeneratorClient,
+ schema: Schema,
+ type: string,
+ namespace: string,
+ isPointerType: boolean,
): SwiftPropertyContext {
- return {
- name: client.namer.register(schema.name, namespace, {
- transform: camelCase,
- }),
- type,
- isVariableNullable: !schema.isRequired || !!schema.isNullable,
- isPayloadFieldNullable: !!schema.isNullable && !!schema.isRequired,
- isPointerType,
- }
+ return {
+ name: client.namer.register(schema.name, namespace, {
+ transform: camelCase,
+ }),
+ type,
+ isVariableNullable: !schema.isRequired || !!schema.isNullable,
+ isPayloadFieldNullable: !!schema.isNullable && !!schema.isRequired,
+ isPointerType,
+ };
}
// Handlebars partials
function generateFunctionSignature(
- functionName: string,
- properties: (BasePropertyContext & SwiftPropertyContext)[],
- withOptions: boolean
+ functionName: string,
+ properties: (BasePropertyContext & SwiftPropertyContext)[],
+ withOptions: boolean,
): string {
- let signature = functionName
- const parameters: {
- type: string
- name: string
- isPointerType: boolean
- isVariableNullable: boolean
- }[] = [...properties]
- if (withOptions) {
- parameters.push({
- name: 'options',
- type: 'RSOption',
- isPointerType: true,
- isVariableNullable: true,
- })
- }
+ let signature = functionName;
+ const parameters: {
+ type: string;
+ name: string;
+ isPointerType: boolean;
+ isVariableNullable: boolean;
+ }[] = [...properties];
+ if (withOptions) {
+ parameters.push({
+ name: 'options',
+ type: 'RSOption',
+ isPointerType: true,
+ isVariableNullable: true,
+ });
+ }
- const withNullability = (property: {
- type: string
- isPointerType: boolean
- isVariableNullable: boolean
- }) => {
- const { type, isVariableNullable } = property
- if (isVariableNullable) {
- return `${type}? = nil`
- } else {
- return `${type}`
- }
- }
+ const withNullability = (property: {
+ type: string;
+ isPointerType: boolean;
+ isVariableNullable: boolean;
+ }) => {
+ const { type, isVariableNullable } = property;
+ if (isVariableNullable) {
+ return `${type}? = nil`;
+ } else {
+ return `${type}`;
+ }
+ };
- signature += `(`
- for (let index = 0; index < parameters.length; index++) {
- const parameter = parameters[index]
- signature += `${parameter.name}: ${withNullability(parameter)}`
- if (index != parameters.length - 1) {
- signature += `, `
- }
- }
- signature += `)`
+ signature += `(`;
+ for (let index = 0; index < parameters.length; index++) {
+ const parameter = parameters[index];
+ signature += `${parameter.name}: ${withNullability(parameter)}`;
+ if (index != parameters.length - 1) {
+ signature += `, `;
+ }
+ }
+ signature += `)`;
- return signature.trim()
+ return signature.trim();
}
function generateFunctionCall(
- caller: string,
- functionName: string,
- properties: (BasePropertyContext & SwiftPropertyContext)[],
- extraParameterName?: string,
- extraParameterValue?: string
+ caller: string,
+ functionName: string,
+ properties: (BasePropertyContext & SwiftPropertyContext)[],
+ extraParameterName?: string,
+ extraParameterValue?: string,
): string {
- let functionCall = functionName
- const parameters: { name: string; value: string }[] = properties.map(p => ({
- name: p.name,
- value: p.name,
- }))
- if (extraParameterName && extraParameterValue) {
- parameters.push({
- name: extraParameterName,
- value: extraParameterValue,
- })
- }
+ let functionCall = functionName;
+ const parameters: { name: string; value: string }[] = properties.map(p => ({
+ name: p.name,
+ value: p.name,
+ }));
+ if (extraParameterName && extraParameterValue) {
+ parameters.push({
+ name: extraParameterName,
+ value: extraParameterValue,
+ });
+ }
- functionCall += `(`
- for (let index = 0; index < parameters.length; index++) {
- const parameter = parameters[index]
- functionCall += `${parameter.name}: ${parameter.value}`
- if (index != parameters.length - 1) {
- functionCall += `, `
- }
- }
- functionCall += `)`
+ functionCall += `(`;
+ for (let index = 0; index < parameters.length; index++) {
+ const parameter = parameters[index];
+ functionCall += `${parameter.name}: ${parameter.value}`;
+ if (index != parameters.length - 1) {
+ functionCall += `, `;
+ }
+ }
+ functionCall += `)`;
- return `${caller}.${functionCall.trim()}`
+ return `${caller}.${functionCall.trim()}`;
}
function generatePropertiesDictionary(
- properties: (BasePropertyContext & SwiftPropertyContext)[],
- prefix?: string
+ properties: (BasePropertyContext & SwiftPropertyContext)[],
+ prefix?: string,
): string {
- const varOrLet = properties.length > 0 ? `var` : `let`
- let out = varOrLet + ` properties = [String: Any]()\n`
+ const varOrLet = properties.length > 0 ? `var` : `let`;
+ let out = varOrLet + ` properties = [String: Any]()\n`;
- for (const property of properties) {
- const name = prefix && prefix.length > 0 ? `${prefix}${property.name}` : property.name
- const serializableName =
- property.schemaType === Type.BOOLEAN
- ? name
- : property.schemaType === Type.INTEGER
- ? name
- : property.schemaType === Type.OBJECT && !property.type.includes('[String: Any]')
- ? property.isVariableNullable
- ? `${name}?.serializableDictionary()`
- : `${name}.serializableDictionary()`
- : property.schemaType === Type.ARRAY
- ? property.isVariableNullable
- ? `${name}?.serializableArray()`
- : `${name}.serializableArray()`
- : name
+ for (const property of properties) {
+ const name = prefix && prefix.length > 0 ? `${prefix}${property.name}` : property.name;
+ const serializableName =
+ property.schemaType === Type.BOOLEAN
+ ? name
+ : property.schemaType === Type.INTEGER
+ ? name
+ : property.schemaType === Type.OBJECT && !property.type.includes('[String: Any]')
+ ? property.isVariableNullable
+ ? `${name}?.serializableDictionary()`
+ : `${name}.serializableDictionary()`
+ : property.schemaType === Type.ARRAY
+ ? property.isVariableNullable
+ ? `${name}?.serializableArray()`
+ : `${name}.serializableArray()`
+ : name;
- let setter: string
- if (property.isPointerType) {
- if (property.isPayloadFieldNullable) {
- // If the value is nil, we need to convert it from a primitive nil to NSNull (an object).
- setter = `properties["${
- property.rawName
- }"] = ${name} == nil ? NSNull() : ${serializableName}\n`
- } else {
- setter = `properties["${property.rawName}"] = ${serializableName};\n`
- }
- } else {
- setter = `properties["${property.rawName}"] = ${serializableName};\n`
- }
+ let setter: string;
+ if (property.isPointerType) {
+ if (property.isPayloadFieldNullable) {
+ // If the value is nil, we need to convert it from a primitive nil to NSNull (an object).
+ setter = `properties["${property.rawName}"] = ${name} == nil ? NSNull() : ${serializableName}\n`;
+ } else {
+ setter = `properties["${property.rawName}"] = ${serializableName};\n`;
+ }
+ } else {
+ setter = `properties["${property.rawName}"] = ${serializableName};\n`;
+ }
- out += setter
- }
+ out += setter;
+ }
- return out
+ return out;
}
diff --git a/src/templates.ts b/src/templates.ts
index 148b9807..ec7522fe 100644
--- a/src/templates.ts
+++ b/src/templates.ts
@@ -1,9 +1,9 @@
-import * as fs from 'fs'
-import * as Handlebars from 'handlebars'
-import { promisify } from 'util'
-import { resolve } from 'path'
+import * as fs from 'fs';
+import * as Handlebars from 'handlebars';
+import { promisify } from 'util';
+import { resolve } from 'path';
-const readFile = promisify(fs.readFile)
+const readFile = promisify(fs.readFile);
/**
* Header used to mark generated files that are safe to remove during generation.
@@ -15,58 +15,58 @@ const readFile = promisify(fs.readFile)
* versions of this header when identifying files safe to remove.
*/
export const RUDDER_AUTOGENERATED_FILE_WARNING =
- 'This client was automatically generated by RudderTyper. ** Do Not Edit **'
+ 'This client was automatically generated by RudderTyper. ** Do Not Edit **';
// Renders a string generated from a template using the provided context.
// The template path is relative to the `src` directory.
export async function generateFromTemplate>(
- templatePath: string,
- context: Context,
- needsWarning?: boolean
+ templatePath: string,
+ context: Context,
+ needsWarning?: boolean,
): Promise {
- const path = resolve(__dirname, templatePath)
- const template = await readFile(path, {
- encoding: 'utf-8',
- })
- const templater = Handlebars.compile(template, {
- noEscape: true,
- })
+ const path = resolve(__dirname, templatePath);
+ const template = await readFile(path, {
+ encoding: 'utf-8',
+ });
+ const templater = Handlebars.compile(template, {
+ noEscape: true,
+ });
- const content = templater(context)
+ const content = templater(context);
- if (needsWarning && !content.includes(RUDDER_AUTOGENERATED_FILE_WARNING)) {
- throw new Error(
- `This autogenerated file (${templatePath}) is missing a warning, and therefore will not be cleaned up in future runs.`
- )
- }
+ if (needsWarning && !content.includes(RUDDER_AUTOGENERATED_FILE_WARNING)) {
+ throw new Error(
+ `This autogenerated file (${templatePath}) is missing a warning, and therefore will not be cleaned up in future runs.`,
+ );
+ }
- return content
+ return content;
}
export async function registerPartial(partialPath: string, partialName: string): Promise {
- const path = resolve(__dirname, partialPath)
- const template = await readFile(path, {
- encoding: 'utf-8',
- })
- const templater = Handlebars.compile(template, {
- noEscape: true,
- })
+ const path = resolve(__dirname, partialPath);
+ const template = await readFile(path, {
+ encoding: 'utf-8',
+ });
+ const templater = Handlebars.compile(template, {
+ noEscape: true,
+ });
- Handlebars.registerPartial(partialName, templater)
+ Handlebars.registerPartial(partialName, templater);
}
export async function registerStandardHelpers(): Promise {
- // Register a helper for indenting multi-line output from other helpers.
- Handlebars.registerHelper('indent', (indentation: string, content: string) => {
- return content
- .split('\n')
- .join(`\n${indentation}`)
- .trim()
- })
- // Register a helper to output a warning that a given file was automatically
- // generated by RudderTyper. Note that the exact phrasing is important, since
- // it is used to clear generated files. See `clearFolder` in `commands.ts`.
- Handlebars.registerHelper('autogeneratedFileWarning', () => {
- return RUDDER_AUTOGENERATED_FILE_WARNING
- })
+ // Register a helper for indenting multi-line output from other helpers.
+ Handlebars.registerHelper('indent', (indentation: string, content: string) => {
+ return content
+ .split('\n')
+ .join(`\n${indentation}`)
+ .trim();
+ });
+ // Register a helper to output a warning that a given file was automatically
+ // generated by RudderTyper. Note that the exact phrasing is important, since
+ // it is used to clear generated files. See `clearFolder` in `commands.ts`.
+ Handlebars.registerHelper('autogeneratedFileWarning', () => {
+ return RUDDER_AUTOGENERATED_FILE_WARNING;
+ });
}
diff --git a/yarn.lock b/yarn.lock
index 714c73cf..482f9881 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5531,7 +5531,7 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
-prettier@^1.17.0:
+prettier@^1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==