type-crafter 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21188,11 +21188,13 @@ function getReferencedTypeModules(_referencedTypes, _writtenAt) {
21188
21188
  resolveFilePath(outputFile.filePath) !== resolveFilePath(writtenAt)) {
21189
21189
  if (typeof referencedTypeModules[outputFile.modulePath] === 'undefined') {
21190
21190
  const rawRelativePath = generateRelativePath(writtenAt, outputFile.modulePath);
21191
+ const moduleName = outputFile.modulePath.split('/').pop() ?? '';
21191
21192
  referencedTypeModules[outputFile.modulePath] = {
21192
21193
  modulePath: outputFile.modulePath,
21193
21194
  moduleRelativePath: formatModulePath(rawRelativePath, writtenAt, modulePathConfig),
21194
21195
  referencedTypes: [referenceType],
21195
- moduleName: outputFile.modulePath.split('/').pop() ?? ''
21196
+ moduleName,
21197
+ fileBasedModules: moduleName === (modulePathConfig?.moduleFileName ?? '')
21196
21198
  };
21197
21199
  }
21198
21200
  else {
@@ -21234,6 +21236,20 @@ function toSnakeCaseHelper(input) {
21234
21236
  }
21235
21237
  return toSnakeCase(inputString);
21236
21238
  }
21239
+ function toCamelCase(input) {
21240
+ const pascal = toPascalCase(input);
21241
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
21242
+ }
21243
+ function formatCase(input, fontCase) {
21244
+ switch (fontCase) {
21245
+ case 'snake_case':
21246
+ return toSnakeCase(input);
21247
+ case 'PascalCase':
21248
+ return toPascalCase(input);
21249
+ case 'camelCase':
21250
+ return toCamelCase(input);
21251
+ }
21252
+ }
21237
21253
  function refineJSONKey(input) {
21238
21254
  if (typeof input === 'string' && input.includes('-')) {
21239
21255
  return `'${input}'`;
@@ -21252,6 +21268,19 @@ function refineIndexKey(input) {
21252
21268
  }
21253
21269
  return input;
21254
21270
  }
21271
+ function escapeReservedWord(input) {
21272
+ if (typeof input !== 'string') {
21273
+ return input;
21274
+ }
21275
+ const config = Runtime.getConfig().language.reservedKeywords;
21276
+ if (typeof config === 'undefined') {
21277
+ return input;
21278
+ }
21279
+ if (config.words.includes(input)) {
21280
+ return config.prefix + input;
21281
+ }
21282
+ return input;
21283
+ }
21255
21284
  function registerTemplateHelpers() {
21256
21285
  Handlebars.registerHelper('getOptionalKeys', getOptionalKeys);
21257
21286
  Handlebars.registerHelper('getRequiredKeys', getRequiredKeys);
@@ -21267,6 +21296,7 @@ function registerTemplateHelpers() {
21267
21296
  Handlebars.registerHelper('jsonKey', refineJSONKey);
21268
21297
  Handlebars.registerHelper('variableName', refineVariableName);
21269
21298
  Handlebars.registerHelper('indexKey', refineIndexKey);
21299
+ Handlebars.registerHelper('escapeReservedWord', escapeReservedWord);
21270
21300
  Handlebars.registerHelper('stringify', (value) => JSON.stringify(value));
21271
21301
  Handlebars.registerHelper('not', (value) => {
21272
21302
  if (typeof value === 'boolean') {
@@ -21582,8 +21612,7 @@ async function generateObjectType(typeName, typeInfo, parentTypes) {
21582
21612
  const referencedType = await generateReferencedType(propertyName, propertyDetails, parentTypes);
21583
21613
  recursivePropertyName = referencedType.templateInput.typeName;
21584
21614
  languageDataType = recursivePropertyName;
21585
- references.push(...referencedType.references);
21586
- primitives.push(...referencedType.primitives);
21615
+ references.push(recursivePropertyName);
21587
21616
  isReferenced = true;
21588
21617
  }
21589
21618
  else if (enumValues !== null) {
@@ -21697,6 +21726,18 @@ async function generateArrayType(typeName, typeInfo, parentTypes) {
21697
21726
  }
21698
21727
  else {
21699
21728
  arrayItemsType = await generateType(typeName + 'Item', typeInfo.items, parentTypes);
21729
+ // For referenced types (e.g. enums via $ref), use the type name as the item type
21730
+ // identifier, consistent with how inline enums are handled above.
21731
+ // Also only keep the direct reference — the referenced type handles its own imports.
21732
+ // Create new objects to avoid mutating cached data from generateReferencedType.
21733
+ if (typeInfo.items.$ref !== null) {
21734
+ const refItemTypeName = arrayItemsType.templateInput.typeName;
21735
+ arrayItemsType = {
21736
+ ...arrayItemsType,
21737
+ templateInput: { ...arrayItemsType.templateInput, type: refItemTypeName },
21738
+ references: new Set([refItemTypeName])
21739
+ };
21740
+ }
21700
21741
  }
21701
21742
  if (typeof arrayItemsType.templateInput?.type === 'undefined') {
21702
21743
  throw new InvalidSpecFileError('Invalid array type for: ' + typeName);
@@ -21962,28 +22003,41 @@ async function generator(specFileData) {
21962
22003
  }
21963
22004
  }
21964
22005
  result.groupedTypes = groupedTypes;
22006
+ // remove self references from grouped types
22007
+ for (const groupName in result.groupedTypes) {
22008
+ for (const typeName in result.groupedTypes[groupName]) {
22009
+ result.groupedTypes[groupName][typeName].references.delete(typeName);
22010
+ }
22011
+ }
21965
22012
  return result;
21966
22013
  }
21967
22014
 
22015
+ function formatModuleName(name) {
22016
+ const moduleNameCase = Runtime.getConfig().language.modulePathConfig.moduleNameCase;
22017
+ return typeof moduleNameCase !== 'undefined' ? formatCase(name, moduleNameCase) : name;
22018
+ }
21968
22019
  function generateTypesOutputFiles(types, groupName = null) {
21969
22020
  const config = Runtime.getConfig();
21970
22021
  const extension = config.output.fileExtension;
21971
22022
  const outputDir = config.output.directory;
21972
22023
  const moduleFileName = config.language.exporterModuleName;
21973
22024
  const writerMode = groupName === null ? config.output.writerMode.types : config.output.writerMode.groupedTypes;
22025
+ const formattedGroupName = groupName !== null ? formatModuleName(groupName) : null;
21974
22026
  const result = new Map();
21975
22027
  for (const typeName in types) {
21976
22028
  const typeProperties = types[typeName];
21977
22029
  if (typeof typeProperties !== 'undefined') {
22030
+ const formattedTypeName = formatModuleName(typeName);
21978
22031
  result.set(typeName, {
21979
22032
  modulePath: outputDir +
21980
22033
  '/' +
21981
- (writerMode === 'FolderWithFiles' ? (groupName ?? '') + '/' : '') +
21982
- moduleFileName,
22034
+ (formattedGroupName
22035
+ ? formattedGroupName + (writerMode === 'FolderWithFiles' ? '/' + moduleFileName : '')
22036
+ : moduleFileName),
21983
22037
  filePath: outputDir +
21984
22038
  (writerMode === 'Files' || writerMode === 'FolderWithFiles'
21985
- ? '/' + (groupName ?? '') + typeName
21986
- : '/' + (groupName ?? 'types')),
22039
+ ? '/' + (formattedGroupName ?? '') + formattedTypeName
22040
+ : '/' + (formattedGroupName ?? 'types')),
21987
22041
  extension
21988
22042
  });
21989
22043
  }
@@ -22041,7 +22095,7 @@ async function writeTypesToFiles(config, types, folderName = '') {
22041
22095
  const typeNames = Object.keys(types);
22042
22096
  for (const typeName in types) {
22043
22097
  const typeData = types[typeName];
22044
- const file = typeName + config.output.fileExtension;
22098
+ const file = formatModuleName(typeName) + config.output.fileExtension;
22045
22099
  const references = filterReferences
22046
22100
  ? [...types[typeName].references].filter((x) => !typeNames.includes(x))
22047
22101
  : [...types[typeName].references];
@@ -22125,11 +22179,12 @@ async function writeOutput(generationResult) {
22125
22179
  if (config.output.writerMode.groupedTypes === 'FolderWithFiles') {
22126
22180
  for (const groupName in generationResult.groupedTypes) {
22127
22181
  let groupFilesWritten = null;
22128
- await createFolderWithBasePath(config.output.directory, groupName);
22182
+ const formattedGroupName = formatModuleName(groupName);
22183
+ await createFolderWithBasePath(config.output.directory, formattedGroupName);
22129
22184
  addValuesToMappedSet(writtenFiles, await getCompleteFolderPath(config.output.directory), [
22130
- groupName
22185
+ formattedGroupName
22131
22186
  ]);
22132
- groupFilesWritten = await writeTypesToFiles(config, generationResult.groupedTypes[groupName], groupName);
22187
+ groupFilesWritten = await writeTypesToFiles(config, generationResult.groupedTypes[groupName], formattedGroupName);
22133
22188
  if (groupFilesWritten !== null) {
22134
22189
  addValuesToMappedSet(writtenFiles, await getCompleteFolderPath(groupFilesWritten.folderName), groupFilesWritten.files);
22135
22190
  }
@@ -22138,7 +22193,7 @@ async function writeOutput(generationResult) {
22138
22193
  else if (config.output.writerMode.groupedTypes === 'SingleFile') {
22139
22194
  for (const groupName in generationResult.groupedTypes) {
22140
22195
  let groupFilesWritten = null;
22141
- groupFilesWritten = await writeTypesToFile(config, generationResult.groupedTypes[groupName], groupName);
22196
+ groupFilesWritten = await writeTypesToFile(config, generationResult.groupedTypes[groupName], formatModuleName(groupName));
22142
22197
  if (groupFilesWritten !== null) {
22143
22198
  addValuesToMappedSet(writtenFiles, await getCompleteFolderPath(groupFilesWritten.folderName), groupFilesWritten.files);
22144
22199
  }
@@ -22301,7 +22356,64 @@ async function config(inputFilePath, outputDirectory, typesWriterMode, groupedTy
22301
22356
  parentRef: 'super',
22302
22357
  selfRef: 'self',
22303
22358
  moduleFileName: 'mod',
22304
- fileBasedModules: true
22359
+ fileBasedModules: true,
22360
+ moduleNameCase: 'snake_case'
22361
+ },
22362
+ reservedKeywords: {
22363
+ prefix: 'r#',
22364
+ words: [
22365
+ 'as',
22366
+ 'break',
22367
+ 'const',
22368
+ 'continue',
22369
+ 'crate',
22370
+ 'else',
22371
+ 'enum',
22372
+ 'extern',
22373
+ 'false',
22374
+ 'fn',
22375
+ 'for',
22376
+ 'if',
22377
+ 'impl',
22378
+ 'in',
22379
+ 'let',
22380
+ 'loop',
22381
+ 'match',
22382
+ 'mod',
22383
+ 'move',
22384
+ 'mut',
22385
+ 'pub',
22386
+ 'ref',
22387
+ 'return',
22388
+ 'self',
22389
+ 'Self',
22390
+ 'static',
22391
+ 'struct',
22392
+ 'super',
22393
+ 'trait',
22394
+ 'true',
22395
+ 'type',
22396
+ 'unsafe',
22397
+ 'use',
22398
+ 'where',
22399
+ 'while',
22400
+ 'async',
22401
+ 'await',
22402
+ 'dyn',
22403
+ 'abstract',
22404
+ 'become',
22405
+ 'box',
22406
+ 'do',
22407
+ 'final',
22408
+ 'macro',
22409
+ 'override',
22410
+ 'priv',
22411
+ 'typeof',
22412
+ 'unsized',
22413
+ 'virtual',
22414
+ 'yield',
22415
+ 'try'
22416
+ ]
22305
22417
  }
22306
22418
  }
22307
22419
  };
@@ -22374,7 +22486,7 @@ async function runner(language, inputFilePath, outputDirectory, _typesWriterMode
22374
22486
  }
22375
22487
  }
22376
22488
  greeting();
22377
- const program = new Command().version('0.13.0');
22489
+ const program = new Command().version('0.13.1');
22378
22490
  program
22379
22491
  .command('generate')
22380
22492
  .description('Generate types for your language from a type spec file')
@@ -6,13 +6,13 @@ pub struct {{typeName}} {
6
6
  {{#each compositions}}
7
7
  {{#if (eq this.source 'referenced')}}
8
8
  #[serde(flatten)]
9
- pub {{toSnakeCase this.referencedType}}: {{this.referencedType}},
9
+ pub {{escapeReservedWord (toSnakeCase this.referencedType)}}: {{this.referencedType}},
10
10
  {{else if (eq this.source 'inline')}}
11
11
  {{#if (eq this.dataType 'object')}}
12
12
  #[serde(flatten)]
13
- pub {{toSnakeCase this.templateInput.typeName}}: {{this.templateInput.typeName}},
13
+ pub {{escapeReservedWord (toSnakeCase this.templateInput.typeName)}}: {{this.templateInput.typeName}},
14
14
  {{else}}
15
- pub {{toSnakeCase this.templateInput.typeName}}: {{{this.templateInput.type}}},
15
+ pub {{escapeReservedWord (toSnakeCase this.templateInput.typeName)}}: {{{this.templateInput.type}}},
16
16
  {{/if}}
17
17
  {{/if}}
18
18
  {{/each}}
@@ -14,7 +14,7 @@ pub struct {{typeName}} {
14
14
  {{#unless this.required}}
15
15
  #[serde(skip_serializing_if = "Option::is_none")]
16
16
  {{/unless}}
17
- pub {{toSnakeCase @key}}: {{#unless this.required}}Option<{{/unless}}{{{this.type}}}{{#unless this.required}}>{{/unless}},
17
+ pub {{escapeReservedWord (toSnakeCase @key)}}: {{#unless this.required}}Option<{{/unless}}{{{this.type}}}{{#unless this.required}}>{{/unless}},
18
18
  {{/each}}
19
19
  {{#if additionalProperties}}
20
20
  #[serde(flatten)]
@@ -1,7 +1,11 @@
1
1
  use serde::{Serialize, Deserialize};
2
2
  {{#each (getReferencedTypeModules referencedTypes writtenAt)}}
3
3
  {{#each this.referencedTypes}}
4
- use {{{../moduleRelativePath}}}::{{this}}::{{this}};
4
+ {{#if ../fileBasedModules}}
5
+ use {{{../moduleRelativePath}}}::{{toSnakeCase this}}::{{this}};
6
+ {{else}}
7
+ use {{{../moduleRelativePath}}}::{{this}};
8
+ {{/if}}
5
9
  {{/each}}
6
10
  {{/each}}
7
11
 
@@ -25,17 +25,24 @@ export type Template = {
25
25
  oneOfSyntax: string;
26
26
  allOfSyntax: string;
27
27
  };
28
+ export type FontCase = 'snake_case' | 'PascalCase' | 'camelCase';
28
29
  export type ModulePathConfig = {
29
30
  separator: string;
30
31
  parentRef: string;
31
32
  selfRef: string;
32
33
  moduleFileName: string;
33
34
  fileBasedModules: boolean;
35
+ moduleNameCase?: FontCase;
36
+ };
37
+ export type ReservedKeywordsConfig = {
38
+ words: string[];
39
+ prefix: string;
34
40
  };
35
41
  export type LanguageConfig = {
36
42
  exporterModuleName: string;
37
43
  typeMapper: LanguageTypeMapper;
38
44
  modulePathConfig: ModulePathConfig;
45
+ reservedKeywords?: ReservedKeywordsConfig;
39
46
  };
40
47
  /**
41
48
  * @description Mappers for all the types supported by OpenAPI 3.0.0
@@ -149,6 +156,7 @@ export type ReferencedModule = {
149
156
  moduleRelativePath: string;
150
157
  referencedTypes: string[];
151
158
  moduleName: string;
159
+ fileBasedModules: boolean;
152
160
  };
153
161
  export type EnumTemplateInput = TypeDescriptors & {
154
162
  typeName: string;
@@ -1,4 +1,4 @@
1
- import { type ModulePathConfig, type TypeInfo, type TypeDataType } from '$types';
1
+ import { type ModulePathConfig, type FontCase, type TypeInfo, type TypeDataType } from '$types';
2
2
  import { type JSONObject } from 'type-decoder';
3
3
  export * from './file-system';
4
4
  export * from './logger';
@@ -11,9 +11,12 @@ export declare function toPascalCase(input: string): string;
11
11
  export declare function toPascalCaseHelper(input: unknown): string | unknown;
12
12
  export declare function toSnakeCase(input: string): string;
13
13
  export declare function toSnakeCaseHelper(input: unknown): string | unknown;
14
+ export declare function toCamelCase(input: string): string;
15
+ export declare function formatCase(input: string, fontCase: FontCase): string;
14
16
  export declare function refineJSONKey(input: unknown): unknown;
15
17
  export declare function refineVariableName(input: unknown): unknown;
16
18
  export declare function refineIndexKey(input: unknown): unknown;
19
+ export declare function escapeReservedWord(input: unknown): unknown;
17
20
  export declare function registerTemplateHelpers(): void;
18
21
  export declare function readNestedValue(json: unknown, keyPath: string[]): JSONObject;
19
22
  export declare function generateRelativePath(fromPath: string, toPath: string): string;
@@ -1,3 +1,4 @@
1
1
  import { type TypeFilePath, type Types } from '$types';
2
+ export declare function formatModuleName(name: string): string;
2
3
  export declare function generateTypesOutputFiles(types: Types | null, groupName?: string | null): Map<string, TypeFilePath>;
3
4
  export declare function generateExpectedOutputFile(): Map<string, TypeFilePath>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "type-crafter",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "A tool to generate types from a yaml schema for any language",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -15,8 +15,8 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "dev": "tsc --watch",
18
- "format:all": "npx prettier --write .",
19
- "lint:all": "eslint src",
18
+ "format": "npx prettier --write .",
19
+ "lint": "eslint src",
20
20
  "clean:output": "rm -rf dist",
21
21
  "build": "npm run clean:output && rollup --config rollup.config.js"
22
22
  },