Skip to content

Commit

Permalink
feat: optimize imports
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael M committed May 10, 2022
1 parent d93fd36 commit 2126a92
Show file tree
Hide file tree
Showing 22 changed files with 413 additions and 252 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ indent_size = 2

[*.{yaml,yml}]
indent_size = 2

[*.handlebars]
indent_size = 2
insert_final_newline = false
82 changes: 55 additions & 27 deletions packages/ng-openapi-gen/src/lib/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import { OaService } from './oa-service.js';
import { Options } from './options.js';
import { Templates } from './templates.js';
import { fileWrite, resolveFromIMU, syncDirs } from './utils/file-system.js';
import { methodName, removeTrailingSlash, simpleName } from './utils/string.js';
import { methodName, refName, trimTrailingSlash } from './utils/string.js';

export class Generator {
protected globals: Globals;
protected hbProvider: HbProvider;
protected templates: Templates;
protected imports = new Map<string, OaImport>();
protected models = new Map<string, OaModel>();
protected services = new Map<string, OaService>();
protected operationsByTag = new Map<string, OaOperation[]>();
Expand All @@ -26,7 +27,7 @@ export class Generator {
protected tempDir: string;

constructor(public openApi: OpenAPIObject, public options: Options) {
this.outDir = removeTrailingSlash(options.output);
this.outDir = trimTrailingSlash(options.output);
this.tempDir = this.outDir + '$';
}

Expand All @@ -47,21 +48,26 @@ export class Generator {
// Generate each model
const models = Array.from(this.models.values());
for (const model of models) {
this.write('model', model, model.fileName, 'models');
this.write('model', model, model.fileName, this.options.modelsDir);
}

// Generate each service
const services = Array.from(this.services.values());
for (const service of services) {
this.write('service', service, service.fileName, 'services');
this.write('service', service, service.fileName, this.options.servicesDir);
}

// Context objects passed to general templates
const general = { services, models };
// todo: shorten models here
const modelImports =
this.globals.modelIndexFile || this.options.indexFile
? models.map((m) => new OaImport(m.name, './models', m.options))
? models.map((model) => new OaImport().fromModel(model))
: null;
// const modelImports =
// this.globals.modelIndexFile || this.options.indexFile
// ? models.map((model) => new OaImport().fromRef(this.options, model.name))
// : null;

// Generate the general files
this.write('configuration', general, this.globals.configurationFile);
Expand All @@ -84,9 +90,7 @@ export class Generator {
// Now synchronize the temp to the output folder
syncDirs(this.tempDir, this.outDir, this.options.removeStaleFiles !== false);

console.info(
`Generation from ${this.options.input} finished with ${models.length} models and ${services.length} services.`,
);
// console.info(`Generation from ${this.options.input} finished with ${models.length} models and ${services.length} services.`);
} finally {
// Always remove the temporary directory
fse.removeSync(this.tempDir);
Expand Down Expand Up @@ -132,8 +136,15 @@ export class Generator {
protected collectModels(): void {
const schemas = (this.openApi.components || {}).schemas || {};
for (const [name, schema] of Object.entries(schemas)) {
const model = new OaModel(this.openApi, schema, name, this.options);
this.models.set(name, model);
this.models.set(name, new OaModel(this.openApi, schema, name, this.options));
}
// todo: shorten models here
this.shortenModels();
for (const [refName, model] of this.models.entries()) {
this.imports.set(refName, new OaImport().fromModel(model));
}
for (const model of this.models.values()) {
model.collectImports(this.imports);
}
}

Expand All @@ -146,12 +157,7 @@ export class Generator {
}

let id = methodSpec.operationId;
if (id) {
id = methodName(id); // Make sure the id is valid
} else {
id = methodName(`${opPath}.${method}`); // Generate an id
console.warn(`Operation '${opPath}.${method}' didn't specify an 'operationId'. Assuming '${id}'.`);
}
id = id ? methodName(id) : methodName(`${opPath}.${method}`);

if (this.operations.has(id)) {
// Duplicated id. Add a suffix
Expand All @@ -160,18 +166,14 @@ export class Generator {
while (this.operations.has(tryId)) {
tryId = `${id}_${++suffix}`;
}
console.warn(
`Duplicate operation id '${id}'. Assuming id ${tryId} for operation '${opPath}.${method}'.`,
);
// console.warn(`Duplicate operation id '${id}'. Assuming id ${tryId} for operation '${opPath}.${method}'.`);
id = tryId;
}

const operation = new OaOperation(this.openApi, opPath, pathSpec, method, id, methodSpec, this.options);
// Set a default tag if no tags are found
if (operation.tags.length === 0) {
console.warn(
`No tags set on operation '${opPath}.${method}'. Assuming '${this.options.defaultTag}'.`,
);
// console.warn(`No tags set on operation '${opPath}.${method}'. Assuming '${this.options.defaultTag}'.`);
operation.tags.push(this.options.defaultTag);
}
for (const tag of operation.tags) {
Expand All @@ -192,15 +194,16 @@ export class Generator {
const tags = this.openApi.tags || [];
for (const [tagName, operations] of this.operationsByTag.entries()) {
if (this.options.includeTags.length > 0 && !this.options.includeTags.includes(tagName)) {
console.debug(`Ignoring tag ${tagName} because it is not listed in the 'includeTags' option`);
// console.debug(`Ignoring tag ${tagName} because it is not listed in the 'includeTags' option`);
continue;
}
if (this.options.excludeTags.length > 0 && this.options.excludeTags.includes(tagName)) {
console.debug(`Ignoring tag ${tagName} because it is listed in the 'excludeTags' option`);
// console.debug(`Ignoring tag ${tagName} because it is listed in the 'excludeTags' option`);
continue;
}
const tag = tags.find((t) => t.name === tagName) || { name: tagName };
const service = new OaService(tag, operations || [], this.options);
service.collectImports(this.imports);
this.services.set(tag.name, service);
}
}
Expand All @@ -210,7 +213,7 @@ export class Generator {
const usedNames = new Set<string>();
for (const service of this.services.values()) {
for (const imp of service.imports) {
usedNames.add(imp.name);
usedNames.add(imp.refName);
}
for (const imp of service.additionalDependencies) {
usedNames.add(imp);
Expand All @@ -227,7 +230,7 @@ export class Generator {
// Then delete all unused models
for (const model of this.models.values()) {
if (!usedNames.has(model.name)) {
console.debug(`Ignoring model ${model.name} because it is not used anywhere`);
// console.debug(`Ignoring model ${model.name} because it is not used anywhere`);
this.models.delete(model.name);
}
}
Expand All @@ -252,7 +255,7 @@ export class Generator {
}

if (schema.$ref) {
return [simpleName(schema.$ref)];
return [refName(schema.$ref)];
}

const result: string[] = [];
Expand All @@ -279,4 +282,29 @@ export class Generator {
}
return result;
}

protected shortenModels(): void {
const typeNamesCount = new Map<string, Set<string>>();
for (const [refName, model] of this.models.entries()) {
const occurrences = typeNamesCount.get(model.typeName) || new Set<string>();
occurrences.add(refName);
typeNamesCount.set(model.typeName, occurrences);
}
for (const model of this.models.values()) {
if (typeNamesCount.get(model.typeName).size === 1) {
model.assumedName = model.typeName;
continue;
}
if (model.typeName !== model.assumedName && !typeNamesCount.has(model.assumedName)) {
continue;
}

let suffix = 1;
let tryName = `${model.typeName}${suffix}`;
while (typeNamesCount.has(tryName)) {
tryName = `${model.typeName}${++suffix}`;
}
model.assumedName = tryName;
}
}
}
2 changes: 2 additions & 0 deletions packages/ng-openapi-gen/src/lib/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class Globals {
public requestBuilderFile: string;
public responseClass: string;
public responseFile: string;
public pathToModelsDir: string;
public moduleClass?: string;
public moduleFile?: string;
public modelIndexFile?: string;
Expand All @@ -27,6 +28,7 @@ export class Globals {
this.requestBuilderFile = fileName(this.requestBuilderClass);
this.responseClass = options.response;
this.responseFile = fileName(this.responseClass);
this.pathToModelsDir = `./${options.modelsDir}/`;

if (options.module !== false && options.module !== '') {
this.moduleClass = typeof options.module === 'string' ? options.module : (defaultOptions.module as string);
Expand Down
79 changes: 51 additions & 28 deletions packages/ng-openapi-gen/src/lib/oa-base.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ReferenceObject, SchemaObject } from 'openapi3-ts';

import { OaImport } from './oa-import.js';
import { OaImports } from './oa-imports.js';
import { Options } from './options.js';
import { fileName, namespace, simpleName, typeName } from './utils/string.js';
import { fileName, namespace, refName, typeName } from './utils/string.js';

export abstract class OaBase {
/** Name of the generated type / class */
Expand All @@ -16,20 +15,22 @@ export abstract class OaBase {
public qualifiedName: string;
/** TypeScript comments for this type */
public tsComments: string;
/** Relative path to modelsDir, ending with `/` */
public pathToModels: string;

public imports: OaImport[];
private _imports: OaImports;
protected _imports = new Map<string, OaImport>();

public additionalDependencies: string[];
private _additionalDependencies = new Set<string>();

protected constructor(
public name: string, // oaName
public options: Options,
public readonly refName: string,
public readonly options: Options,
typeNameTransform: (oaName: string, options: Options) => string,
) {
this.typeName = typeNameTransform(name, options);
this.namespace = namespace(name);
this.typeName = typeNameTransform(refName, options);
this.namespace = namespace(refName);

if (this.namespace) {
this.fileName = this.namespace + '/' + fileName(this.typeName);
Expand All @@ -38,26 +39,32 @@ export abstract class OaBase {
this.fileName = fileName(this.typeName);
this.qualifiedName = this.typeName;
}
}

this._imports = new OaImports(options);
public get name(): string {
return this.refName;
}

protected abstract skipImport(name: string): boolean;
public abstract collectImports(imports: Map<string, OaImport>): void;

/**
* Must be implemented to return the relative path to the models, ending with `/`
*/
protected abstract pathToModels(): string;
protected abstract skipImport(refName: string): boolean;

protected addImport(name: string): void {
if (!this.skipImport(name)) {
// Don't have to import to this own file
this._imports.add(name, this.pathToModels());
protected addImport(refName: string, imports: Map<string, OaImport>): void {
// Prevent self imports
if (!this.skipImport(refName)) {
const existingImport = imports.get(refName);
this._imports.set(
refName,
existingImport
? new OaImport().fromImport(existingImport)
: new OaImport().fromRef(this.options, refName),
);
}
}

protected collectImports(
protected createImports(
schema: SchemaObject | ReferenceObject | undefined,
imports: Map<string, OaImport>,
additional = false,
processOneOf = false,
): void {
Expand All @@ -66,45 +73,61 @@ export abstract class OaBase {
}

if (schema.$ref) {
const dep = simpleName(schema.$ref);
const dep = refName(schema.$ref);
if (additional) {
this._additionalDependencies.add(dep);
} else {
this.addImport(dep);
this.addImport(dep, imports);
}
return;
}

schema = schema as SchemaObject;
for (const imp of schema.oneOf || []) {
this.collectImports(imp, additional);
this.createImports(imp, imports, additional);
}
for (const imp of schema.allOf || []) {
this.collectImports(imp, additional);
this.createImports(imp, imports, additional);
}
for (const imp of schema.anyOf || []) {
this.collectImports(imp, additional);
this.createImports(imp, imports, additional);
}
if (processOneOf) {
for (const imp of schema.oneOf || []) {
this.collectImports(imp, additional);
this.createImports(imp, imports, additional);
}
}
if (schema.items) {
this.collectImports(schema.items, additional);
this.createImports(schema.items, imports, additional);
}
if (schema.properties) {
for (const prop of Object.values(schema.properties)) {
this.collectImports(prop, additional, true);
this.createImports(prop, imports, additional, true);
}
}
if (typeof schema.additionalProperties === 'object') {
this.collectImports(schema.additionalProperties, additional);
this.createImports(schema.additionalProperties, imports, additional);
}
}

protected updateImports(): void {
this.imports = this._imports.toArray();
const typeNamesCount = new Map<string, number>([[this.typeName, 1]]);
for (const imp of this._imports.values()) {
typeNamesCount.set(imp.typeName, (typeNamesCount.get(imp.typeName) || 0) + 1);
}

const namespaceSplit = (this.namespace || '').split('/').filter(Boolean);
for (const imp of this._imports.values()) {
imp.file = imp.getRelativePath(namespaceSplit, this.pathToModels);

if (typeNamesCount.get(imp.typeName) > 1) {
imp.useAlias = true;
}
}
this.imports = Array.from(this._imports.keys())
.sort()
.map((key: string) => this._imports.get(key));

this.additionalDependencies = [...this._additionalDependencies];
}
}
11 changes: 7 additions & 4 deletions packages/ng-openapi-gen/src/lib/oa-content.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { MediaTypeObject, OpenAPIObject } from 'openapi3-ts';

import { OaImport } from './oa-import.js';
import { Options } from './options.js';
import { tsType } from './utils/open-api.js';
import { tsTypeVal } from './utils/open-api.js';

export class OaContent {
public type: string;

constructor(
public mediaType: string,
public spec: MediaTypeObject,
public options: Options,
public openApi: OpenAPIObject,
) {
this.type = tsType(spec.schema, options, openApi);
public options: Options,
) {}

public updateProperties(imports: Map<string, OaImport>): void {
this.type = tsTypeVal(this.spec.schema, this.openApi, this.options, imports);
}
}
Loading

0 comments on commit 2126a92

Please sign in to comment.