Skip to content

Commit

Permalink
Merge pull request #1 from ivawzh/inherit-custom-abstract-class
Browse files Browse the repository at this point in the history
Support abstract class codegen.
  • Loading branch information
battika authored Nov 16, 2020
2 parents 26d531f + 894b641 commit ac7120c
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 8 deletions.
6 changes: 6 additions & 0 deletions src/IGenerationOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ export default interface IGenerationOptions {
propertyVisibility: "public" | "protected" | "private" | "none";
lazy: boolean;
activeRecord: boolean;
skipRelationships: boolean;
extendAbstractClass: string;
generateConstructor: boolean;
customNamingStrategyPath: string;
relationIds: boolean;
strictMode: "none" | "?" | "!";
skipSchema: boolean;
indexFile: boolean;
exportType: "named" | "default";
exportAbstractClass: boolean;
}

export const eolConverter = {
Expand All @@ -42,13 +45,16 @@ export function getDefaultGenerationOptions(): IGenerationOptions {
propertyVisibility: "none",
lazy: false,
activeRecord: false,
skipRelationships: false,
extendAbstractClass: "",
generateConstructor: false,
customNamingStrategyPath: "",
relationIds: false,
strictMode: "none",
skipSchema: false,
indexFile: false,
exportType: "named",
exportAbstractClass: false,
};
return generationOptions;
}
12 changes: 12 additions & 0 deletions src/ModelCustomization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,21 @@ function addImportsAndGenerationOptions(
if (generationOptions.activeRecord) {
entity.activeRecord = true;
}
if (generationOptions.skipRelationships) {
entity.skipRelationships = true;
}
if (generationOptions.extendAbstractClass) {
entity.extendAbstractClass = generationOptions.extendAbstractClass;
}
if (generationOptions.generateConstructor) {
entity.generateConstructor = true;
}
if (generationOptions.exportAbstractClass) {
entity.exportAbstractClass = true;
}
entity.generateSuper = !!(
entity.activeRecord || entity.extendAbstractClass
);
});
return dbModel;
}
Expand Down
2 changes: 1 addition & 1 deletion src/ModelGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ function createHandlebarsHelpers(generationOptions: IGenerationOptions): void {
}
);
Handlebars.registerHelper("defaultExport", () =>
generationOptions.exportType === "default" ? "default" : ""
generationOptions.exportType === "default" ? " default" : ""
);
Handlebars.registerHelper("localImport", (entityName: string) =>
generationOptions.exportType === "default"
Expand Down
91 changes: 90 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ function validateConfig(options: options): options {
false
);
options.generationOptions.relationIds = false;
} else if (
options.generationOptions.activeRecord &&
options.generationOptions.extendAbstractClass
) {
TomgUtils.LogError(
"Typeorm cannot use ActiveRecord and extend-abstract-class at the same time.",
false
);
options.generationOptions.activeRecord = false;
}
return options;
}
Expand Down Expand Up @@ -244,6 +253,24 @@ function checkYargsParameters(options: options): options {
default: options.generationOptions.activeRecord,
describe: "Use ActiveRecord syntax for generated models",
},
skipRelationships: {
alias: "skip-relationships",
boolean: true,
default: options.generationOptions.skipRelationships,
describe: "Skip relationship declarations",
},
extendAbstractClass: {
alias: "extend-abstract-class",
string: true,
default: options.generationOptions.extendAbstractClass,
describe: "Make generated models extend a custom abstract class",
},
exportAbstractClass: {
alias: "export-abstract-class",
boolean: true,
default: options.generationOptions.exportAbstractClass,
describe: "Export generated models as abstract classes",
},
namingStrategy: {
describe: "Use custom naming strategy",
default: options.generationOptions.customNamingStrategyPath,
Expand Down Expand Up @@ -325,6 +352,8 @@ function checkYargsParameters(options: options): options {
options.connectionOptions.skipTables = skipTables;
options.connectionOptions.onlyTables = tables;
options.generationOptions.activeRecord = argv.a;
options.generationOptions.skipRelationships = argv.skipRelationships;
options.generationOptions.extendAbstractClass = argv.extendAbstractClass;
options.generationOptions.generateConstructor = argv.generateConstructor;
options.generationOptions.convertCaseEntity = argv.ce as IGenerationOptions["convertCaseEntity"];
options.generationOptions.convertCaseFile = argv.cf as IGenerationOptions["convertCaseFile"];
Expand All @@ -343,7 +372,7 @@ function checkYargsParameters(options: options): options {
options.generationOptions.exportType = argv.defaultExport
? "default"
: "named";

options.generationOptions.exportAbstractClass = argv.exportAbstractClass;
return options;
}

Expand Down Expand Up @@ -551,6 +580,19 @@ async function useInquirer(options: options): Promise<options> {
value: "activeRecord",
checked: options.generationOptions.activeRecord,
},
{
name: "Skip relationship declarations",
value: "skipRelationships",
checked:
options.generationOptions.skipRelationships,
},
{
name:
"Generated models extend a custom abstract class",
value: "extendAbstractClass",
checked:
options.generationOptions.extendAbstractClass,
},
{
name: "Use custom naming strategy",
value: "namingStrategy",
Expand Down Expand Up @@ -610,6 +652,12 @@ async function useInquirer(options: options): Promise<options> {
options.generationOptions.exportType ===
"default",
},
{
name: "Export generated models as abstract classes",
value: "exportAbstractClass",
checked:
options.generationOptions.exportAbstractClass,
},
],
message: "Available customizations",
name: "selected",
Expand Down Expand Up @@ -653,6 +701,44 @@ async function useInquirer(options: options): Promise<options> {
options.generationOptions.activeRecord = customizations.includes(
"activeRecord"
);
options.generationOptions.skipRelationships = customizations.includes(
"skipRelationships"
);
if (customizations.includes("extendAbstractClass")) {
const { extendAbstractClass } = await inquirer.prompt([
{
default: options.generationOptions.extendAbstractClass,
message: "Relative path to custom abstract class file:",
name: "extendAbstractClass",
type: "input",
validate(value) {
const valid = value === "" || fs.existsSync(value);
return (
valid ||
"Please enter a valid relative path to custom abstract class file"
);
},
},
]);

if (extendAbstractClass && extendAbstractClass !== "") {
const resultsAbsolutePath = path.join(
process.cwd(),
options.generationOptions.resultsPath
);
const abstractClassAbsolutePath = path.join(
process.cwd(),
options.generationOptions.resultsPath
);
const relativePath = path.relative(
abstractClassAbsolutePath,
resultsAbsolutePath
);
options.generationOptions.extendAbstractClass = relativePath;
} else {
options.generationOptions.extendAbstractClass = "";
}
}
options.generationOptions.relationIds = customizations.includes(
"relationId"
);
Expand All @@ -668,6 +754,9 @@ async function useInquirer(options: options): Promise<options> {
)
? "default"
: "named";
options.generationOptions.exportAbstractClass = customizations.includes(
"exportAbstractClass"
);

if (customizations.includes("namingStrategy")) {
const namingStrategyPath = (
Expand Down
4 changes: 4 additions & 0 deletions src/models/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ export type Entity = {
// TODO: move to sub-object or use handlebars helpers(?)
fileImports: string[];
activeRecord?: true;
skipRelationships?: boolean;
extendAbstractClass?: string;
generateSuper?: boolean;
generateConstructor?: true;
exportAbstractClass?: true;
};
13 changes: 7 additions & 6 deletions src/templates/entity.mst
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@ import {{localImport (toEntityName .)}} from './{{toFileName .}}'
{{/inline}}
{{#*inline "Constructor"}}
{{printPropertyVisibility}}constructor(init?: Partial<{{toEntityName entityName}}>) {
{{#activeRecord}}super();
{{/activeRecord}}Object.assign(this, init);
{{#generateSuper}}super();
{{/generateSuper}}Object.assign(this, init);
}
{{/inline}}
{{#*inline "Entity"}}
{{#indices}}{{> Index}}{{/indices~}}
@Entity("{{sqlName}}"{{#schema}} ,{schema:"{{.}}"{{#if ../database}}, database:"{{../database}}"{{/if}} } {{/schema}})
export {{defaultExport}} class {{toEntityName tscName}}{{#activeRecord}} extends BaseEntity{{/activeRecord}} {
{{#unless exportAbstractClass}}@Entity("{{sqlName}}"{{#schema}} ,{schema:"{{.}}"{{#if ../database}}, database:"{{../database}}"{{/if}} } {{/schema}}){{/unless}}
export{{defaultExport}}{{#exportAbstractClass}} abstract{{/exportAbstractClass}} class {{toEntityName tscName}}{{#activeRecord}} extends BaseEntity{{/activeRecord}}{{#extendAbstractClass}} extends BaseClass{{/extendAbstractClass}} {

{{#columns}}{{> Column}}{{/columns~}}
{{#relations}}{{> Relation}}{{/relations~}}
{{#unless skipRelationships}}{{#relations}}{{> Relation}}{{/relations~}}{{/unless}}
{{#relationIds}}{{> RelationId entityName=../tscName}}{{/relationIds~}}
{{#if generateConstructor}}{{>Constructor entityName=tscName}}{{/if~}}
}
{{/inline}}
import {BaseEntity,Column,Entity,Index,JoinColumn,JoinTable,ManyToMany,ManyToOne,OneToMany,OneToOne,PrimaryColumn,PrimaryGeneratedColumn,RelationId} from "typeorm";
{{#fileImports}}{{> Import}}{{/fileImports}}
{{#unless skipRelationships}}{{#fileImports}}{{> Import}}{{/fileImports}}{{/unless}}
{{#extendAbstractClass}}import BaseClass from "{{.}}";{{/extendAbstractClass}}

{{> Entity}}
112 changes: 112 additions & 0 deletions test/modelCustomization/modelCustomization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,118 @@ describe("Model customization phase", async () => {

compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("skipRelationships", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();

generationOptions.skipRelationships = true;
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.not.have.string(
`JoinColumn`,
);
expect(postContent).to.not.have.string(
`import { PostAuthor } from "./PostAuthor";`,
);
expect(postAuthorContent).to.not.have.string(
`OneToMany`,
);
expect(postAuthorContent).to.not.have.string(
`import { Post } from "./Post";`,
);

compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("extendAbstractClass", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();

generationOptions.extendAbstractClass = '../../test/integration/examples/sample28-abstract-class-inheritance/BaseClass';
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(
`export class Post extends BaseClass `
);
expect(postAuthorContent).to.have.string(
`export class PostAuthor extends BaseClass `
);
expect(postContent).to.have.string(
`import BaseClass from "../../test/integration/examples/sample28-abstract-class-inheritance/BaseClass"`
);
expect(postAuthorContent).to.have.string(
`import BaseClass from "../../test/integration/examples/sample28-abstract-class-inheritance/BaseClass"`
);
});
it("exportAbstractClass", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
clearGenerationDir();

generationOptions.exportAbstractClass = true;
const customizedModel = modelCustomizationPhase(
data,
generationOptions,
{}
);
modelGenerationPhase(
getDefaultConnectionOptions(),
generationOptions,
customizedModel
);
const filesGenPath = path.resolve(resultsPath, "entities");
const postContent = fs
.readFileSync(path.resolve(filesGenPath, "Post.ts"))
.toString();
const postAuthorContent = fs
.readFileSync(path.resolve(filesGenPath, "PostAuthor.ts"))
.toString();
expect(postContent).to.have.string(
`export abstract class Post `
);
expect(postAuthorContent).to.have.string(
`export abstract class PostAuthor `
);
expect(postContent).to.not.have.string(
`@Entity`
);
expect(postAuthorContent).to.not.have.string(
`@Entity`
);

compileGeneratedModel(generationOptions.resultsPath, [""]);
});
it("skipSchema", async () => {
const data = generateSampleData();
const generationOptions = generateGenerationOptions();
Expand Down

0 comments on commit ac7120c

Please sign in to comment.