diff --git a/src/IGenerationOptions.ts b/src/IGenerationOptions.ts index 1a1b534..a12b3c4 100644 --- a/src/IGenerationOptions.ts +++ b/src/IGenerationOptions.ts @@ -5,6 +5,7 @@ import path = require("path"); // eslint-disable-next-line @typescript-eslint/interface-name-prefix export default interface IGenerationOptions { resultsPath: string; + pluralizeNames: boolean; noConfigs: boolean; convertCaseFile: "pascal" | "param" | "camel" | "none"; convertCaseEntity: "pascal" | "camel" | "none"; @@ -21,6 +22,7 @@ export default interface IGenerationOptions { export function getDefaultGenerationOptions(): IGenerationOptions { const generationOptions: IGenerationOptions = { resultsPath: path.resolve(process.cwd(), "output"), + pluralizeNames: true, noConfigs: false, convertCaseFile: "pascal", convertCaseEntity: "pascal", diff --git a/src/ModelCustomization.ts b/src/ModelCustomization.ts index 3833d70..46f80d0 100644 --- a/src/ModelCustomization.ts +++ b/src/ModelCustomization.ts @@ -9,6 +9,7 @@ import { RelationId } from "./models/RelationId"; import { Column } from "./models/Column"; type NamingStrategy = { + enablePluralization: (value: boolean) => void; relationIdName: ( relationId: RelationId, relation: Relation, @@ -25,6 +26,7 @@ export default function modelCustomizationPhase( defaultValues: DataTypeDefaults ): Entity[] { const namingStrategy: NamingStrategy = { + enablePluralization: NamingStrategy.enablePluralization, columnName: NamingStrategy.columnName, entityName: NamingStrategy.entityName, relationIdName: NamingStrategy.relationIdName, @@ -78,7 +80,18 @@ export default function modelCustomizationPhase( `[${new Date().toLocaleTimeString()}] Using standard naming strategy for relation field names.` ); } + if (req.enablePluralization) { + console.log( + `[${new Date().toLocaleTimeString()}] Using custom pluralization method for OneToMany, ManyToMany relation field names.` + ); + namingStrategy.enablePluralization = req.enablePluralization; + } else { + console.log( + `[${new Date().toLocaleTimeString()}] Using custom pluralization method for OneToMany, ManyToMany relation field names.` + ); + } } + namingStrategy.enablePluralization(generationOptions.pluralizeNames); let retVal = removeIndicesGeneratedByTypeorm(dbModel); retVal = removeColumnsInRelation(dbModel); retVal = applyNamingStrategy(namingStrategy, dbModel); diff --git a/src/NamingStrategy.ts b/src/NamingStrategy.ts index 8ff4546..4836377 100644 --- a/src/NamingStrategy.ts +++ b/src/NamingStrategy.ts @@ -3,6 +3,12 @@ import * as changeCase from "change-case"; import { Relation } from "./models/Relation"; import { RelationId } from "./models/RelationId"; +let pluralize: boolean; + +export function enablePluralization(value: boolean) { + pluralize = value; +} + export function relationIdName( relationId: RelationId, relation: Relation @@ -22,7 +28,7 @@ export function relationIdName( if (!Number.isNaN(parseInt(newColumnName[newColumnName.length - 1], 10))) { newColumnName = newColumnName.substring(0, newColumnName.length - 1); } - if (isRelationToMany) { + if (isRelationToMany && pluralize) { newColumnName = plural(newColumnName); } @@ -54,7 +60,7 @@ export function relationName(relation: Relation): string { if (!Number.isNaN(parseInt(newColumnName[newColumnName.length - 1], 10))) { newColumnName = newColumnName.substring(0, newColumnName.length - 1); } - if (isRelationToMany) { + if (isRelationToMany && pluralize) { newColumnName = plural(newColumnName); } return newColumnName; diff --git a/src/index.ts b/src/index.ts index 2ac40d3..e84e68e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -239,6 +239,12 @@ function checkYargsParameters(options: options): options { default: options.generationOptions.generateConstructor, describe: "Generate constructor allowing partial initialization" }, + disablePluralization: { + boolean: true, + default: !options.generationOptions.pluralizeNames, + describe: + "Disable pluralization of OneToMany, ManyToMany relation names." + }, strictMode: { choices: ["none", "?", "!"], default: options.generationOptions.strictMode, @@ -272,6 +278,7 @@ function checkYargsParameters(options: options): options { options.generationOptions.relationIds = argv.relationIds; options.generationOptions.skipSchema = argv.skipSchema; options.generationOptions.resultsPath = argv.o; + options.generationOptions.pluralizeNames = !argv.disablePluralization; options.generationOptions.strictMode = argv.strictMode as IGenerationOptions["strictMode"]; return options; @@ -399,6 +406,7 @@ async function useInquirer(options: options): Promise { } ]); if (customizeGeneration) { + const defaultGenerationOptions = getDefaultGenerationOptions(); const customizations: string[] = ( await inquirer.prompt([ { @@ -446,7 +454,20 @@ async function useInquirer(options: options): Promise { { name: "Use specific naming convention", value: "namingConvention", - checked: options.generationOptions.lazy + checked: + options.generationOptions.convertCaseEntity !== + defaultGenerationOptions.convertCaseEntity || + options.generationOptions + .convertCaseProperty !== + defaultGenerationOptions.convertCaseProperty || + options.generationOptions.convertCaseFile !== + defaultGenerationOptions.convertCaseFile + }, + { + name: + "Pluralize OneToMany, ManyToMany relation names.", + value: "pluralize", + checked: options.generationOptions.pluralizeNames } ], message: "Available customizations", @@ -484,6 +505,9 @@ async function useInquirer(options: options): Promise { options.generationOptions.noConfigs = !customizations.includes( "config" ); + options.generationOptions.pluralizeNames = customizations.includes( + "pluralize" + ); options.generationOptions.lazy = customizations.includes("lazy"); options.generationOptions.activeRecord = customizations.includes( "activeRecord" diff --git a/test/modelCustomization/modelCustomization.test.ts b/test/modelCustomization/modelCustomization.test.ts index 7b0a3a1..da60c12 100644 --- a/test/modelCustomization/modelCustomization.test.ts +++ b/test/modelCustomization/modelCustomization.test.ts @@ -47,7 +47,7 @@ describe("Model customization phase", async () => { fieldName: "Post", relatedField: "authorId", relatedTable: "Post", - relationType: "OneToOne" + relationType: "OneToMany" } ], relationIds: [], @@ -101,7 +101,7 @@ describe("Model customization phase", async () => { { name: "authorId", referencedColumnName: "id" } ], relatedTable: "PostAuthor", - relationType: "OneToOne" + relationType: "ManyToOne" } ], relationIds: [], @@ -252,7 +252,7 @@ describe("Model customization phase", async () => { .readFileSync(path.resolve(filesGenPath, "PostAuthor.ts")) .toString(); expect(postContent).to.contain("Title: string;"); - expect(postAuthorContent).to.contain("Post: Post;"); + expect(postAuthorContent).to.contain("Posts: Post[];"); compileGeneratedModel(generationOptions.resultsPath, [""]); }); @@ -280,7 +280,7 @@ describe("Model customization phase", async () => { .readFileSync(path.resolve(filesGenPath, "PostAuthor.ts")) .toString(); expect(postContent).to.contain("title: string;"); - expect(postAuthorContent).to.contain("post: Post;"); + expect(postAuthorContent).to.contain("posts: Post[];"); compileGeneratedModel(generationOptions.resultsPath, [""]); }); @@ -310,7 +310,7 @@ describe("Model customization phase", async () => { .readFileSync(path.resolve(filesGenPath, "PostAuthor.ts")) .toString(); expect(postContent).to.have.string(" public title: string"); - expect(postAuthorContent).to.have.string(" public post: Post;"); + expect(postAuthorContent).to.have.string(" public posts: Post[];"); compileGeneratedModel(generationOptions.resultsPath, [""]); }); @@ -338,7 +338,7 @@ describe("Model customization phase", async () => { .readFileSync(path.resolve(filesGenPath, "PostAuthor.ts")) .toString(); expect(postContent).to.have.string(" title: string"); - expect(postAuthorContent).to.have.string(" post: Post;"); + expect(postAuthorContent).to.have.string(" posts: Post[];"); compileGeneratedModel(generationOptions.resultsPath, [""]); }); @@ -349,6 +349,7 @@ describe("Model customization phase", async () => { clearGenerationDir(); generationOptions.lazy = true; + generationOptions.pluralizeNames = false; const customizedModel = modelCustomizationPhase( data, generationOptions, @@ -369,7 +370,7 @@ describe("Model customization phase", async () => { expect(postContent).to.have.string("lazy: true"); expect(postContent).to.have.string("Promise;"); expect(postAuthorContent).to.have.string("lazy: true"); - expect(postAuthorContent).to.have.string("Promise"); + expect(postAuthorContent).to.have.string("Promise"); compileGeneratedModel(generationOptions.resultsPath, [""]); }); @@ -530,7 +531,7 @@ describe("Model customization phase", async () => { expect(postContent).to.have.string(`author!: PostAuthor;`); expect(postAuthorContent).to.have.string(`id!: number;`); expect(postAuthorContent).to.have.string(`name!: string;`); - expect(postAuthorContent).to.have.string(`post!: Post;`); + expect(postAuthorContent).to.have.string(`posts!: Post[];`); compileGeneratedModel(generationOptions.resultsPath, [""]); }); @@ -563,7 +564,7 @@ describe("Model customization phase", async () => { expect(postContent).to.have.string(`author?: PostAuthor;`); expect(postAuthorContent).to.have.string(`id?: number;`); expect(postAuthorContent).to.have.string(`name?: string;`); - expect(postAuthorContent).to.have.string(`post?: Post;`); + expect(postAuthorContent).to.have.string(`posts?: Post[];`); compileGeneratedModel(generationOptions.resultsPath, [""]); }); @@ -612,4 +613,54 @@ describe("Model customization phase", async () => { compileGeneratedModel(generationOptions.resultsPath, [""]); }); + describe("pluralization", () => { + it("enabled", async () => { + + const data = generateSampleData(); + const generationOptions = generateGenerationOptions(); + generationOptions.pluralizeNames = true; + clearGenerationDir(); + + const customizedModel = modelCustomizationPhase( + data, + generationOptions, + {} + ); + modelGenerationPhase( + getDefaultConnectionOptions(), + generationOptions, + customizedModel + ); + const filesGenPath = path.resolve(resultsPath, "entities"); + const postAuthorContent = fs + .readFileSync(path.resolve(filesGenPath, "PostAuthor.ts")) + .toString(); + expect(postAuthorContent).to.contain("posts: Post[];"); + compileGeneratedModel(generationOptions.resultsPath, [""]); + }) + it("disabled", async () => { + + const data = generateSampleData(); + const generationOptions = generateGenerationOptions(); + generationOptions.pluralizeNames = false; + clearGenerationDir(); + + const customizedModel = modelCustomizationPhase( + data, + generationOptions, + {} + ); + modelGenerationPhase( + getDefaultConnectionOptions(), + generationOptions, + customizedModel + ); + const filesGenPath = path.resolve(resultsPath, "entities"); + const postAuthorContent = fs + .readFileSync(path.resolve(filesGenPath, "PostAuthor.ts")) + .toString(); + expect(postAuthorContent).to.contain("post: Post[];"); + compileGeneratedModel(generationOptions.resultsPath, [""]); + }) + }) }); diff --git a/test/utils/GeneralTestUtils.ts b/test/utils/GeneralTestUtils.ts index 84d7c09..bf2fe82 100644 --- a/test/utils/GeneralTestUtils.ts +++ b/test/utils/GeneralTestUtils.ts @@ -1,7 +1,7 @@ import { ConnectionOptions, createConnection } from "typeorm"; import * as ts from "typescript"; import * as yn from "yn"; -import IGenerationOptions from "../../src/IGenerationOptions"; +import IGenerationOptions, { getDefaultGenerationOptions } from "../../src/IGenerationOptions"; import IConnectionOptions from "../../src/IConnectionOptions"; import MssqlDriver from "../../src/drivers/MssqlDriver"; import MariaDbDriver from "../../src/drivers/MariaDbDriver"; @@ -12,21 +12,9 @@ import MysqlDriver from "../../src/drivers/MysqlDriver"; import path = require("path"); export function getGenerationOptions(resultsPath: string): IGenerationOptions { - return { - resultsPath, - noConfigs: false, - convertCaseEntity: "pascal", - convertCaseFile: "pascal", - convertCaseProperty: "camel", - propertyVisibility: "none", - lazy: false, - generateConstructor: false, - customNamingStrategyPath: "", - relationIds: false, - skipSchema: false, - activeRecord: false, - strictMode: "none" - }; + const retVal = getDefaultGenerationOptions(); + retVal.resultsPath = resultsPath; + return retVal; } export async function createMSSQLModels( @@ -316,7 +304,7 @@ export function compileTsFiles( ); console.log( `${diagnostic.file!.fileName} (${lineAndCharacter.line + - 1},${lineAndCharacter.character + 1}): ${message}` + 1},${lineAndCharacter.character + 1}): ${message}` ); compiledWithoutErrors = false; });