@vaadin/hilla-generator-plugin-subtypes 25.3.0-beta1 → 25.3.0-beta2

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.
@@ -1,6 +1,6 @@
1
1
  import { type SourceFile } from "@typescript/typescript6";
2
2
  export declare class ModelFixProcessor {
3
3
  #private;
4
- constructor(source: SourceFile);
4
+ constructor(source: SourceFile, discriminatorPropertyName: string);
5
5
  process(): SourceFile;
6
6
  }
@@ -1,20 +1,22 @@
1
1
  import ts from "@typescript/typescript6";
2
2
  import createSourceFile from "@vaadin/hilla-generator-utils/createSourceFile.js";
3
- import { propertyNameToString } from "./utils.js";
3
+ import { propertyNameToString, removeUnusedImports } from "./utils.js";
4
4
  export class ModelFixProcessor {
5
5
  #source;
6
- constructor(source) {
6
+ #discriminatorPropertyName;
7
+ constructor(source, discriminatorPropertyName) {
7
8
  this.#source = source;
9
+ this.#discriminatorPropertyName = discriminatorPropertyName;
8
10
  }
9
11
  process() {
10
12
  const statements = this.#source.statements.map((statement) => {
11
13
  if (ts.isClassDeclaration(statement)) {
12
- const members = statement.members.filter((member) => !(ts.isGetAccessor(member) && propertyNameToString(member.name) === "@type"));
14
+ const members = statement.members.filter((member) => !(ts.isGetAccessor(member) && propertyNameToString(member.name) === this.#discriminatorPropertyName));
13
15
  return ts.factory.createClassDeclaration(statement.modifiers, statement.name, statement.typeParameters, statement.heritageClauses, members);
14
16
  }
15
17
  return statement;
16
18
  });
17
- return createSourceFile(statements, this.#source.fileName);
19
+ return createSourceFile(removeUnusedImports(statements), this.#source.fileName);
18
20
  }
19
21
  }
20
22
  //# sourceMappingURL=./ModelFixProcessor.js.map
@@ -1 +1 @@
1
- {"mappings":"AAAA,OAAO,iCAAuD;AAC9D,OAAO,yEAA0E;AACjF,SAAS,wCAAyC;AAElD,OAAO,MAAM,kBAAkB;CAC7B,AAASA;CAET,YAAYC,QAAoB;AAC9B,OAAKD,UAAU;CAChB;CAED,UAAsB;EACpB,MAAM,aAAa,KAAKA,QAAQ,WAAW,IAAI,CAAC,cAAc;AAE5D,OAAI,GAAG,mBAAmB,UAAU,EAAE;IACpC,MAAM,UAAU,UAAU,QAAQ,OAChC,CAAC,aAAa,GAAG,cAAc,OAAO,IAAI,qBAAqB,OAAO,KAAK,KAAK,SACjF;AAED,WAAO,GAAG,QAAQ,uBAChB,UAAU,WACV,UAAU,MACV,UAAU,gBACV,UAAU,iBACV,QACD;GACF;AAED,UAAO;EACR,EAAC;AAEF,SAAO,iBAAiB,YAAY,KAAKA,QAAQ,SAAS;CAC3D;AACF","names":["#source","source: SourceFile"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/ModelFixProcessor.ts"],"sourcesContent":["import ts, { type SourceFile } from '@typescript/typescript6';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport { propertyNameToString } from './utils.js';\n\nexport class ModelFixProcessor {\n readonly #source: SourceFile;\n\n constructor(source: SourceFile) {\n this.#source = source;\n }\n\n process(): SourceFile {\n const statements = this.#source.statements.map((statement) => {\n // filter out the @type property from all models\n if (ts.isClassDeclaration(statement)) {\n const members = statement.members.filter(\n (member) => !(ts.isGetAccessor(member) && propertyNameToString(member.name) === '@type'),\n );\n\n return ts.factory.createClassDeclaration(\n statement.modifiers,\n statement.name,\n statement.typeParameters,\n statement.heritageClauses,\n members,\n );\n }\n\n return statement;\n });\n\n return createSourceFile(statements, this.#source.fileName);\n }\n}\n"],"version":3}
1
+ {"mappings":"AAAA,OAAO,iCAAuD;AAC9D,OAAO,yEAA0E;AACjF,SAAS,sBAAsB,uCAAwC;AAEvE,OAAO,MAAM,kBAAkB;CAC7B,AAASA;CACT,AAASC;CAET,YAAYC,QAAoBC,2BAAmC;AACjE,OAAKH,UAAU;AACf,OAAKC,6BAA6B;CACnC;CAED,UAAsB;EACpB,MAAM,aAAa,KAAKD,QAAQ,WAAW,IAAI,CAAC,cAAc;AAE5D,OAAI,GAAG,mBAAmB,UAAU,EAAE;IACpC,MAAM,UAAU,UAAU,QAAQ,OAChC,CAAC,aACG,GAAG,cAAc,OAAO,IAAI,qBAAqB,OAAO,KAAK,KAAK,KAAKC,4BAC5E;AAED,WAAO,GAAG,QAAQ,uBAChB,UAAU,WACV,UAAU,MACV,UAAU,gBACV,UAAU,iBACV,QACD;GACF;AAED,UAAO;EACR,EAAC;AAEF,SAAO,iBAAiB,oBAAoB,WAAW,EAAE,KAAKD,QAAQ,SAAS;CAChF;AACF","names":["#source","#discriminatorPropertyName","source: SourceFile","discriminatorPropertyName: string"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/ModelFixProcessor.ts"],"sourcesContent":["import ts, { type SourceFile } from '@typescript/typescript6';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport { propertyNameToString, removeUnusedImports } from './utils.js';\n\nexport class ModelFixProcessor {\n readonly #source: SourceFile;\n readonly #discriminatorPropertyName: string;\n\n constructor(source: SourceFile, discriminatorPropertyName: string) {\n this.#source = source;\n this.#discriminatorPropertyName = discriminatorPropertyName;\n }\n\n process(): SourceFile {\n const statements = this.#source.statements.map((statement) => {\n // filter out the discriminator property from all models\n if (ts.isClassDeclaration(statement)) {\n const members = statement.members.filter(\n (member) =>\n !(ts.isGetAccessor(member) && propertyNameToString(member.name) === this.#discriminatorPropertyName),\n );\n\n return ts.factory.createClassDeclaration(\n statement.modifiers,\n statement.name,\n statement.typeParameters,\n statement.heritageClauses,\n members,\n );\n }\n\n return statement;\n });\n\n return createSourceFile(removeUnusedImports(statements), this.#source.fileName);\n }\n}\n"],"version":3}
@@ -1,7 +1,8 @@
1
1
  import { type SourceFile } from "@typescript/typescript6";
2
2
  import { type ReferenceSchema } from "@vaadin/hilla-generator-core/Schema.js";
3
+ import type { Discriminator } from "./utils.js";
3
4
  export declare class SubTypesProcessor {
4
5
  #private;
5
- constructor(typeName: string, source: SourceFile, oneOf: readonly ReferenceSchema[]);
6
+ constructor(typeName: string, source: SourceFile, oneOf: readonly ReferenceSchema[], discriminator: Discriminator);
6
7
  process(): SourceFile;
7
8
  }
@@ -4,15 +4,18 @@ import { convertReferenceSchemaToPath, convertReferenceSchemaToSpecifier, simpli
4
4
  import createSourceFile from "@vaadin/hilla-generator-utils/createSourceFile.js";
5
5
  import DependencyManager from "@vaadin/hilla-generator-utils/dependencies/DependencyManager.js";
6
6
  import PathManager from "@vaadin/hilla-generator-utils/dependencies/PathManager.js";
7
+ import { createDiscriminatedType, schemaKey } from "./utils.js";
7
8
  export class SubTypesProcessor {
8
9
  #typeName;
9
10
  #source;
10
11
  #oneOf;
12
+ #discriminator;
11
13
  #dependencies;
12
- constructor(typeName, source, oneOf) {
14
+ constructor(typeName, source, oneOf, discriminator) {
13
15
  this.#typeName = typeName;
14
16
  this.#source = source;
15
17
  this.#oneOf = oneOf;
18
+ this.#discriminator = discriminator;
16
19
  this.#dependencies = new DependencyManager(new PathManager({
17
20
  extension: ".js",
18
21
  relativeTo: dirname(source.fileName)
@@ -23,17 +26,18 @@ export class SubTypesProcessor {
23
26
  const subTypes = this.#oneOf.map((schema) => {
24
27
  const path = paths.createRelativePath(convertReferenceSchemaToPath(schema));
25
28
  const subType = convertReferenceSchemaToSpecifier(schema);
26
- return imports.default.add(path, subType, true);
29
+ const type = ts.factory.createTypeReferenceNode(imports.default.add(path, subType, true));
30
+ const typeValues = this.#discriminator.values.get(schemaKey(schema)) ?? [];
31
+ return typeValues.length > 1 ? createDiscriminatedType(type, this.#discriminator.propertyName, typeValues[0]) : type;
27
32
  });
28
- const union = ts.factory.createUnionTypeNode(subTypes.map((subType) => ts.factory.createTypeReferenceNode(subType)));
29
- const { fileName, statements } = this.#source;
33
+ const union = ts.factory.createUnionTypeNode(subTypes);
34
+ const { fileName } = this.#source;
30
35
  const unionTypeName = `${simplifyFullyQualifiedName(this.#typeName)}`;
31
36
  const unionIdentifier = ts.factory.createIdentifier(unionTypeName);
32
37
  const statement = ts.factory.createTypeAliasDeclaration(undefined, unionIdentifier, undefined, union);
33
38
  exports.default.set(unionTypeName);
34
39
  return createSourceFile([
35
40
  ...imports.toCode(),
36
- ...statements,
37
41
  statement,
38
42
  ...exports.toCode()
39
43
  ], fileName);
@@ -1 +1 @@
1
- {"mappings":"AAAA,SAAS,2BAA4B;AACrC,OAAO,iCAAuD;AAC9D,SACE,8BACA,mCAEA,0EAC8C;AAChD,OAAO,yEAA0E;AACjF,OAAO,wFAAyF;AAChG,OAAO,4EAA6E;AAEpF,OAAO,MAAM,kBAAkB;CAC7B,AAASA;CACT,AAASC;CACT,AAASC;CACT,AAASC;CAET,YAAYC,UAAkBC,QAAoBC,OAAmC;AACnF,OAAKN,YAAY;AACjB,OAAKC,UAAU;AACf,OAAKC,SAAS;AACd,OAAKC,gBAAgB,IAAI,kBACvB,IAAI,YAAY;GAAE,WAAW;GAAO,YAAY,QAAQ,OAAO,SAAS;EAAE;CAE7E;CAED,UAAsB;EACpB,MAAM,EAAE,SAAS,SAAS,OAAO,GAAG,KAAKA;EAGzC,MAAM,WAAW,KAAKD,OAAO,IAAI,CAAC,WAAW;GAC3C,MAAM,OAAO,MAAM,mBAAmB,6BAA6B,OAAO,CAAC;GAC3E,MAAM,UAAU,kCAAkC,OAAO;AACzD,UAAO,QAAQ,QAAQ,IAAI,MAAM,SAAS,KAAK;EAChD,EAAC;EAGF,MAAM,QAAQ,GAAG,QAAQ,oBACvB,SAAS,IAAI,CAAC,YAAY,GAAG,QAAQ,wBAAwB,QAAQ,CAAC,CACvE;EAGD,MAAM,EAAE,UAAU,YAAY,GAAG,KAAKD;EACtC,MAAM,iBAAiB,EAAE,2BAA2B,KAAKD,UAAU,CAAC;EACpE,MAAM,kBAAkB,GAAG,QAAQ,iBAAiB,cAAc;EAClE,MAAM,YAAY,GAAG,QAAQ,2BAA2B,WAAW,iBAAiB,WAAW,MAAM;AAErG,UAAQ,QAAQ,IAAI,cAAc;AAElC,SAAO,iBAAiB;GAAC,GAAG,QAAQ,QAAQ;GAAE,GAAG;GAAY;GAAW,GAAG,QAAQ,QAAQ;EAAC,GAAE,SAAS;CACxG;AACF","names":["#typeName","#source","#oneOf","#dependencies","typeName: string","source: SourceFile","oneOf: readonly ReferenceSchema[]"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/SubTypesProcessor.ts"],"sourcesContent":["import { dirname } from 'path/posix';\nimport ts, { type SourceFile } from '@typescript/typescript6';\nimport {\n convertReferenceSchemaToPath,\n convertReferenceSchemaToSpecifier,\n type ReferenceSchema,\n simplifyFullyQualifiedName,\n} from '@vaadin/hilla-generator-core/Schema.js';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';\nimport PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.js';\n\nexport class SubTypesProcessor {\n readonly #typeName: string;\n readonly #source: SourceFile;\n readonly #oneOf: readonly ReferenceSchema[];\n readonly #dependencies: DependencyManager;\n\n constructor(typeName: string, source: SourceFile, oneOf: readonly ReferenceSchema[]) {\n this.#typeName = typeName;\n this.#source = source;\n this.#oneOf = oneOf;\n this.#dependencies = new DependencyManager(\n new PathManager({ extension: '.js', relativeTo: dirname(source.fileName) }),\n );\n }\n\n process(): SourceFile {\n const { exports, imports, paths } = this.#dependencies;\n\n // import all subtypes and return them\n const subTypes = this.#oneOf.map((schema) => {\n const path = paths.createRelativePath(convertReferenceSchemaToPath(schema));\n const subType = convertReferenceSchemaToSpecifier(schema);\n return imports.default.add(path, subType, true);\n });\n\n // create a union type from the subtypes\n const union = ts.factory.createUnionTypeNode(\n subTypes.map((subType) => ts.factory.createTypeReferenceNode(subType)),\n );\n\n // create the statement\n const { fileName, statements } = this.#source;\n const unionTypeName = `${simplifyFullyQualifiedName(this.#typeName)}`;\n const unionIdentifier = ts.factory.createIdentifier(unionTypeName);\n const statement = ts.factory.createTypeAliasDeclaration(undefined, unionIdentifier, undefined, union);\n\n exports.default.set(unionTypeName);\n\n return createSourceFile([...imports.toCode(), ...statements, statement, ...exports.toCode()], fileName);\n }\n}\n"],"version":3}
1
+ {"mappings":"AAAA,SAAS,2BAA4B;AACrC,OAAO,iCAAsE;AAC7E,SACE,8BACA,mCAEA,0EAC8C;AAChD,OAAO,yEAA0E;AACjF,OAAO,wFAAyF;AAChG,OAAO,4EAA6E;AAEpF,SAAS,yBAAyB,6BAA8B;AAEhE,OAAO,MAAM,kBAAkB;CAC7B,AAASA;CACT,AAASC;CACT,AAASC;CACT,AAASC;CACT,AAASC;CAET,YAAYC,UAAkBC,QAAoBC,OAAmCC,eAA8B;AACjH,OAAKR,YAAY;AACjB,OAAKC,UAAU;AACf,OAAKC,SAAS;AACd,OAAKC,iBAAiB;AACtB,OAAKC,gBAAgB,IAAI,kBACvB,IAAI,YAAY;GAAE,WAAW;GAAO,YAAY,QAAQ,OAAO,SAAS;EAAE;CAE7E;CAED,UAAsB;EACpB,MAAM,EAAE,SAAS,SAAS,OAAO,GAAG,KAAKA;EAGzC,MAAM,WAAW,KAAKF,OAAO,IAAI,CAAC,WAAqB;GACrD,MAAM,OAAO,MAAM,mBAAmB,6BAA6B,OAAO,CAAC;GAC3E,MAAM,UAAU,kCAAkC,OAAO;GACzD,MAAM,OAAO,GAAG,QAAQ,wBAAwB,QAAQ,QAAQ,IAAI,MAAM,SAAS,KAAK,CAAC;GAIzF,MAAM,aAAa,KAAKC,eAAe,OAAO,IAAI,UAAU,OAAO,CAAC,IAAI,CAAE;AAE1E,UAAO,WAAW,SAAS,IACvB,wBAAwB,MAAM,KAAKA,eAAe,cAAc,WAAW,GAAG,GAC9E;EACL,EAAC;EAGF,MAAM,QAAQ,GAAG,QAAQ,oBAAoB,SAAS;EAItD,MAAM,EAAE,UAAU,GAAG,KAAKF;EAC1B,MAAM,iBAAiB,EAAE,2BAA2B,KAAKD,UAAU,CAAC;EACpE,MAAM,kBAAkB,GAAG,QAAQ,iBAAiB,cAAc;EAClE,MAAM,YAAY,GAAG,QAAQ,2BAA2B,WAAW,iBAAiB,WAAW,MAAM;AAErG,UAAQ,QAAQ,IAAI,cAAc;AAElC,SAAO,iBAAiB;GAAC,GAAG,QAAQ,QAAQ;GAAE;GAAW,GAAG,QAAQ,QAAQ;EAAC,GAAE,SAAS;CACzF;AACF","names":["#typeName","#source","#oneOf","#discriminator","#dependencies","typeName: string","source: SourceFile","oneOf: readonly ReferenceSchema[]","discriminator: Discriminator"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/SubTypesProcessor.ts"],"sourcesContent":["import { dirname } from 'path/posix';\nimport ts, { type SourceFile, type TypeNode } from '@typescript/typescript6';\nimport {\n convertReferenceSchemaToPath,\n convertReferenceSchemaToSpecifier,\n type ReferenceSchema,\n simplifyFullyQualifiedName,\n} from '@vaadin/hilla-generator-core/Schema.js';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport DependencyManager from '@vaadin/hilla-generator-utils/dependencies/DependencyManager.js';\nimport PathManager from '@vaadin/hilla-generator-utils/dependencies/PathManager.js';\nimport type { Discriminator } from './utils.js';\nimport { createDiscriminatedType, schemaKey } from './utils.js';\n\nexport class SubTypesProcessor {\n readonly #typeName: string;\n readonly #source: SourceFile;\n readonly #oneOf: readonly ReferenceSchema[];\n readonly #discriminator: Discriminator;\n readonly #dependencies: DependencyManager;\n\n constructor(typeName: string, source: SourceFile, oneOf: readonly ReferenceSchema[], discriminator: Discriminator) {\n this.#typeName = typeName;\n this.#source = source;\n this.#oneOf = oneOf;\n this.#discriminator = discriminator;\n this.#dependencies = new DependencyManager(\n new PathManager({ extension: '.js', relativeTo: dirname(source.fileName) }),\n );\n }\n\n process(): SourceFile {\n const { exports, imports, paths } = this.#dependencies;\n\n // import all subtypes and return them\n const subTypes = this.#oneOf.map((schema): TypeNode => {\n const path = paths.createRelativePath(convertReferenceSchemaToPath(schema));\n const subType = convertReferenceSchemaToSpecifier(schema);\n const type = ts.factory.createTypeReferenceNode(imports.default.add(path, subType, true));\n\n // a subtype that is the supertype of another subtype accepts the values\n // of the subtypes below it too, so its own value has to be pinned here\n const typeValues = this.#discriminator.values.get(schemaKey(schema)) ?? [];\n\n return typeValues.length > 1\n ? createDiscriminatedType(type, this.#discriminator.propertyName, typeValues[0])\n : type;\n });\n\n // create a union type from the subtypes\n const union = ts.factory.createUnionTypeNode(subTypes);\n\n // create the statement: the source is fully replaced, as whatever the\n // backbone plugin made of a schema that only has a `oneOf` is of no use\n const { fileName } = this.#source;\n const unionTypeName = `${simplifyFullyQualifiedName(this.#typeName)}`;\n const unionIdentifier = ts.factory.createIdentifier(unionTypeName);\n const statement = ts.factory.createTypeAliasDeclaration(undefined, unionIdentifier, undefined, union);\n\n exports.default.set(unionTypeName);\n\n return createSourceFile([...imports.toCode(), statement, ...exports.toCode()], fileName);\n }\n}\n"],"version":3}
@@ -1,6 +1,10 @@
1
1
  import ts, { type SourceFile } from "@typescript/typescript6";
2
2
  export declare class TypeFixProcessor {
3
3
  #private;
4
- constructor(source: ts.SourceFile, typeValue: string);
4
+ /**
5
+ * @param typeValues - the discriminator values the type accepts: its own
6
+ * value, followed by the values of the subtypes below it, if any.
7
+ */
8
+ constructor(source: ts.SourceFile, discriminatorPropertyName: string, typeValues: readonly string[]);
5
9
  process(): SourceFile;
6
10
  }
@@ -1,21 +1,27 @@
1
1
  import ts from "@typescript/typescript6";
2
2
  import createSourceFile from "@vaadin/hilla-generator-utils/createSourceFile.js";
3
- import { propertyNameToString } from "./utils.js";
3
+ import { createDiscriminatorProperty, propertyNameToString } from "./utils.js";
4
4
  export class TypeFixProcessor {
5
5
  #source;
6
- #typeValue;
7
- constructor(source, typeValue) {
6
+ #discriminatorPropertyName;
7
+ #typeValues;
8
+ /**
9
+ * @param typeValues - the discriminator values the type accepts: its own
10
+ * value, followed by the values of the subtypes below it, if any.
11
+ */
12
+ constructor(source, discriminatorPropertyName, typeValues) {
8
13
  this.#source = source;
9
- this.#typeValue = typeValue;
14
+ this.#discriminatorPropertyName = discriminatorPropertyName;
15
+ this.#typeValues = typeValues;
10
16
  }
11
17
  process() {
12
18
  const statements = this.#source.statements.map((statement) => {
13
19
  if (ts.isInterfaceDeclaration(statement)) {
14
20
  const members = statement.members.map((member) => {
15
- if (ts.isPropertySignature(member) && propertyNameToString(member.name) === "@type") {
16
- return ts.factory.createPropertySignature(undefined, ts.factory.createStringLiteral("@type"), undefined, ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(this.#typeValue)));
21
+ if (!ts.isPropertySignature(member) || propertyNameToString(member.name) !== this.#discriminatorPropertyName) {
22
+ return member;
17
23
  }
18
- return member;
24
+ return createDiscriminatorProperty(this.#discriminatorPropertyName, this.#typeValues);
19
25
  });
20
26
  return ts.factory.createInterfaceDeclaration(statement.modifiers, statement.name, statement.typeParameters, statement.heritageClauses, members);
21
27
  }
@@ -1 +1 @@
1
- {"mappings":"AAAA,OAAO,iCAAuD;AAC9D,OAAO,yEAA0E;AACjF,SAAS,wCAAyC;AAElD,OAAO,MAAM,iBAAiB;CAC5B,AAASA;CACT,AAASC;CAET,YAAYC,QAAuBC,WAAmB;AACpD,OAAKH,UAAU;AACf,OAAKC,aAAa;CACnB;CAED,UAAsB;EACpB,MAAM,aAAa,KAAKD,QAAQ,WAAW,IAAI,CAAC,cAAc;AAE5D,OAAI,GAAG,uBAAuB,UAAU,EAAE;IACxC,MAAM,UAAU,UAAU,QAAQ,IAAI,CAAC,WAAW;AAEhD,SAAI,GAAG,oBAAoB,OAAO,IAAI,qBAAqB,OAAO,KAAK,KAAK,SAAS;AACnF,aAAO,GAAG,QAAQ,wBAChB,WACA,GAAG,QAAQ,oBAAoB,QAAQ,EACvC,WACA,GAAG,QAAQ,sBAAsB,GAAG,QAAQ,oBAAoB,KAAKC,WAAW,CAAC,CAClF;KACF;AAED,YAAO;IACR,EAAC;AAEF,WAAO,GAAG,QAAQ,2BAChB,UAAU,WACV,UAAU,MACV,UAAU,gBACV,UAAU,iBACV,QACD;GACF;AAED,UAAO;EACR,EAAC;AAEF,SAAO,iBAAiB,YAAY,KAAKD,QAAQ,SAAS;CAC3D;AACF","names":["#source","#typeValue","source: ts.SourceFile","typeValue: string"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/TypeFixProcessor.ts"],"sourcesContent":["import ts, { type SourceFile } from '@typescript/typescript6';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport { propertyNameToString } from './utils.js';\n\nexport class TypeFixProcessor {\n readonly #source: SourceFile;\n readonly #typeValue: string;\n\n constructor(source: ts.SourceFile, typeValue: string) {\n this.#source = source;\n this.#typeValue = typeValue;\n }\n\n process(): SourceFile {\n const statements = this.#source.statements.map((statement) => {\n // search in the interface definition\n if (ts.isInterfaceDeclaration(statement)) {\n const members = statement.members.map((member) => {\n // search for the @type property and replace it with a quoted string\n if (ts.isPropertySignature(member) && propertyNameToString(member.name) === '@type') {\n return ts.factory.createPropertySignature(\n undefined,\n ts.factory.createStringLiteral('@type'),\n undefined,\n ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(this.#typeValue)),\n );\n }\n\n return member;\n });\n\n return ts.factory.createInterfaceDeclaration(\n statement.modifiers,\n statement.name,\n statement.typeParameters,\n statement.heritageClauses,\n members,\n );\n }\n\n return statement;\n });\n\n return createSourceFile(statements, this.#source.fileName);\n }\n}\n"],"version":3}
1
+ {"mappings":"AAAA,OAAO,iCAAuD;AAC9D,OAAO,yEAA0E;AACjF,SAAS,6BAA6B,wCAAyC;AAE/E,OAAO,MAAM,iBAAiB;CAC5B,AAASA;CACT,AAASC;CACT,AAASC;;;;;CAMT,YAAYC,QAAuBC,2BAAmCC,YAA+B;AACnG,OAAKL,UAAU;AACf,OAAKC,6BAA6B;AAClC,OAAKC,cAAc;CACpB;CAED,UAAsB;EACpB,MAAM,aAAa,KAAKF,QAAQ,WAAW,IAAI,CAAC,cAAc;AAE5D,OAAI,GAAG,uBAAuB,UAAU,EAAE;IACxC,MAAM,UAAU,UAAU,QAAQ,IAAI,CAAC,WAAW;AAEhD,UACG,GAAG,oBAAoB,OAAO,IAC/B,qBAAqB,OAAO,KAAK,KAAK,KAAKC,4BAC3C;AACA,aAAO;KACR;AAED,YAAO,4BAA4B,KAAKA,4BAA4B,KAAKC,YAAY;IACtF,EAAC;AAEF,WAAO,GAAG,QAAQ,2BAChB,UAAU,WACV,UAAU,MACV,UAAU,gBACV,UAAU,iBACV,QACD;GACF;AAED,UAAO;EACR,EAAC;AAEF,SAAO,iBAAiB,YAAY,KAAKF,QAAQ,SAAS;CAC3D;AACF","names":["#source","#discriminatorPropertyName","#typeValues","source: ts.SourceFile","discriminatorPropertyName: string","typeValues: readonly string[]"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/TypeFixProcessor.ts"],"sourcesContent":["import ts, { type SourceFile } from '@typescript/typescript6';\nimport createSourceFile from '@vaadin/hilla-generator-utils/createSourceFile.js';\nimport { createDiscriminatorProperty, propertyNameToString } from './utils.js';\n\nexport class TypeFixProcessor {\n readonly #source: SourceFile;\n readonly #discriminatorPropertyName: string;\n readonly #typeValues: readonly string[];\n\n /**\n * @param typeValues - the discriminator values the type accepts: its own\n * value, followed by the values of the subtypes below it, if any.\n */\n constructor(source: ts.SourceFile, discriminatorPropertyName: string, typeValues: readonly string[]) {\n this.#source = source;\n this.#discriminatorPropertyName = discriminatorPropertyName;\n this.#typeValues = typeValues;\n }\n\n process(): SourceFile {\n const statements = this.#source.statements.map((statement) => {\n // search in the interface definition\n if (ts.isInterfaceDeclaration(statement)) {\n const members = statement.members.map((member) => {\n // search for the discriminator property\n if (\n !ts.isPropertySignature(member) ||\n propertyNameToString(member.name) !== this.#discriminatorPropertyName\n ) {\n return member;\n }\n\n return createDiscriminatorProperty(this.#discriminatorPropertyName, this.#typeValues);\n });\n\n return ts.factory.createInterfaceDeclaration(\n statement.modifiers,\n statement.name,\n statement.typeParameters,\n statement.heritageClauses,\n members,\n );\n }\n\n return statement;\n });\n\n return createSourceFile(statements, this.#source.fileName);\n }\n}\n"],"version":3}
package/index.js CHANGED
@@ -3,6 +3,72 @@ import { isReferenceSchema, convertFullyQualifiedNameToRelativePath } from "@vaa
3
3
  import { ModelFixProcessor } from "./ModelFixProcessor.js";
4
4
  import { SubTypesProcessor } from "./SubTypesProcessor.js";
5
5
  import { TypeFixProcessor } from "./TypeFixProcessor.js";
6
+ import { schemaKey } from "./utils.js";
7
+ const DEFAULT_DISCRIMINATOR = "@type";
8
+ /**
9
+ * Returns the schemas that hold the properties declared by the type itself: a
10
+ * subtype is a composed schema whose `anyOf` list contains a reference to the
11
+ * supertype and an object schema with the own properties, while a type that has
12
+ * no supertype is a plain object schema.
13
+ */
14
+ function ownSchemas(component) {
15
+ if (isReferenceSchema(component)) {
16
+ return [];
17
+ }
18
+ return component.anyOf ? component.anyOf.filter((schema) => "properties" in schema) : [component];
19
+ }
20
+ /**
21
+ * Returns the values that the discriminator property of the type accepts: the
22
+ * Java parser stores them as the `enum` of that property, the own value of the
23
+ * type first, followed by the values of the subtypes below it, which narrow the
24
+ * discriminator further. The property is left alone when the values are
25
+ * missing, as pinning a type to its own value alone would clash with the
26
+ * subtypes below it.
27
+ */
28
+ function discriminatorValues(component, discriminatorPropertyName) {
29
+ if (!component) {
30
+ return undefined;
31
+ }
32
+ for (const schema of ownSchemas(component)) {
33
+ const property = schema.properties?.[discriminatorPropertyName];
34
+ if (property && "enum" in property && property.enum?.length) {
35
+ return property.enum.filter((value) => typeof value === "string");
36
+ }
37
+ }
38
+ return undefined;
39
+ }
40
+ /**
41
+ * Returns the values that the discriminator accepts in each subtype, mapped by
42
+ * schema name. A subtype that is also the supertype of another subtype accepts
43
+ * more than one value: pinning it to its own value would leave the subtype
44
+ * unable to extend it, or to be used as the type parameter of its model, so the
45
+ * union type pins the own value of such a subtype instead.
46
+ */
47
+ function findDiscriminatorValues(components, keys, discriminatorPropertyName) {
48
+ const values = new Map();
49
+ keys.forEach((key) => {
50
+ const subTypeValues = discriminatorValues(components[key], discriminatorPropertyName);
51
+ if (subTypeValues?.length) {
52
+ values.set(key, subTypeValues);
53
+ }
54
+ });
55
+ return values;
56
+ }
57
+ function fixSubType(sources, subKey, discriminator) {
58
+ const { propertyName: discriminatorPropertyName, values } = discriminator;
59
+ const typeValues = values.get(subKey);
60
+ if (!typeValues) {
61
+ return;
62
+ }
63
+ const subFn = `${convertFullyQualifiedNameToRelativePath(subKey)}.ts`;
64
+ const subSource = sources.find(({ fileName }) => fileName === subFn);
65
+ const fixedSource = new TypeFixProcessor(subSource, discriminatorPropertyName, typeValues).process();
66
+ sources.splice(sources.indexOf(subSource), 1, fixedSource);
67
+ const modelFn = `${convertFullyQualifiedNameToRelativePath(subKey)}Model.ts`;
68
+ const modelSource = sources.find(({ fileName }) => fileName === modelFn);
69
+ const fixedModelSource = new ModelFixProcessor(modelSource, discriminatorPropertyName).process();
70
+ sources.splice(sources.indexOf(modelSource), 1, fixedModelSource);
71
+ }
6
72
  export default class SubTypesPlugin extends Plugin {
7
73
  get path() {
8
74
  return import.meta.url;
@@ -15,31 +81,18 @@ export default class SubTypesPlugin extends Plugin {
15
81
  }
16
82
  Object.entries(components).forEach(([baseKey, baseComponent]) => {
17
83
  if ("oneOf" in baseComponent && Array.isArray(baseComponent.oneOf) && baseComponent.oneOf.every((schema) => isReferenceSchema(schema))) {
84
+ const discriminatorPropertyName = baseComponent.discriminator?.propertyName ?? DEFAULT_DISCRIMINATOR;
85
+ const subKeys = baseComponent.oneOf.map(schemaKey);
86
+ const discriminator = {
87
+ propertyName: discriminatorPropertyName,
88
+ values: findDiscriminatorValues(components, subKeys, discriminatorPropertyName)
89
+ };
18
90
  const fn = `${convertFullyQualifiedNameToRelativePath(baseKey)}.ts`;
19
91
  const source = sources.find(({ fileName }) => fileName === fn);
20
- const newSource = new SubTypesProcessor(baseKey, source, baseComponent.oneOf).process();
92
+ const newSource = new SubTypesProcessor(baseKey, source, baseComponent.oneOf, discriminator).process();
21
93
  sources.splice(sources.indexOf(source), 1, newSource);
22
- baseComponent.oneOf.forEach((schema) => {
23
- if ("$ref" in schema) {
24
- const path = schema.$ref;
25
- Object.entries(components).forEach(([subKey, subComponent]) => {
26
- if ("anyOf" in subComponent && subKey === path.substring("#/components/schemas/".length)) {
27
- subComponent.anyOf?.forEach((s) => {
28
- if ("properties" in s && "@type" in s.properties && "example" in s.properties["@type"]) {
29
- const typeValue = s.properties["@type"].example;
30
- const subFn = `${convertFullyQualifiedNameToRelativePath(subKey)}.ts`;
31
- const subSource = sources.find(({ fileName }) => fileName === subFn);
32
- const fixedSource = new TypeFixProcessor(subSource, typeValue).process();
33
- sources.splice(sources.indexOf(subSource), 1, fixedSource);
34
- const modelFn = `${convertFullyQualifiedNameToRelativePath(subKey)}Model.ts`;
35
- const modelSource = sources.find(({ fileName }) => fileName === modelFn);
36
- const fixedModelSource = new ModelFixProcessor(modelSource).process();
37
- sources.splice(sources.indexOf(modelSource), 1, fixedModelSource);
38
- }
39
- });
40
- }
41
- });
42
- }
94
+ subKeys.forEach((subKey) => {
95
+ fixSubType(sources, subKey, discriminator);
43
96
  });
44
97
  const unionFn = `${convertFullyQualifiedNameToRelativePath(baseKey)}Model.ts`;
45
98
  const unionSource = sources.find(({ fileName }) => fileName === unionFn);
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"mappings":"AAAA,OAAO,oDAAqD;AAC5D,SAAS,mBAAmB,uFAAwF;AAEpH,SAAS,iDAAkD;AAC3D,SAAS,iDAAkD;AAC3D,SAAS,+CAAgD;AAEzD,eAAe,MAAM,uBAAuB,OAAO;CAGjD,IAAa,OAAe;AAC1B,SAAO,OAAO,KAAK;CACpB;CAGD,MAAe,QAAQA,SAAuC;EAC5D,MAAM,EAAE,KAAK,SAAS,GAAG;EAEzB,MAAM,aAAa,IAAI,YAAY;AAEnC,OAAK,YAAY;AACf;EACD;AAED,SAAO,QAAQ,WAAW,CAAC,QAAQ,CAAC,CAAC,SAAS,cAAc,KAAK;AAE/D,OACE,WAAW,iBACX,MAAM,QAAQ,cAAc,MAAM,IAClC,cAAc,MAAM,MAAM,CAAC,WAAW,kBAAkB,OAAO,CAAC,EAChE;IACA,MAAM,MAAM,EAAE,wCAAwC,QAAQ,CAAC;IAC/D,MAAM,SAAS,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,GAAG;IAE9D,MAAM,YAAY,IAAI,kBAAkB,SAAS,QAAQ,cAAc,OAAO,SAAS;AACvF,YAAQ,OAAO,QAAQ,QAAQ,OAAO,EAAE,GAAG,UAAU;AAGrD,kBAAc,MAAM,QAAQ,CAAC,WAAW;AACtC,SAAI,UAAU,QAAQ;MACpB,MAAM,OAAO,OAAO;AACpB,aAAO,QAAQ,WAAW,CAAC,QAAQ,CAAC,CAAC,QAAQ,aAAa,KAAK;AAC7D,WAAI,WAAW,gBAAgB,WAAW,KAAK,UAAU,wBAAwB,OAAO,EAAE;AACxF,qBAAa,OAAO,QAAQ,CAAC,MAAM;AACjC,aAAI,gBAAgB,KAAK,WAAW,EAAE,cAAe,aAAa,EAAE,WAAW,UAAU;UACvF,MAAM,YAAY,EAAE,WAAW,SAAS;UACxC,MAAM,SAAS,EAAE,wCAAwC,OAAO,CAAC;UACjE,MAAM,YAAY,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,MAAM;UAEpE,MAAM,cAAc,IAAI,iBAAiB,WAAW,WAAW,SAAS;AACxE,kBAAQ,OAAO,QAAQ,QAAQ,UAAU,EAAE,GAAG,YAAY;UAG1D,MAAM,WAAW,EAAE,wCAAwC,OAAO,CAAC;UACnE,MAAM,cAAc,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,QAAQ;UACxE,MAAM,mBAAmB,IAAI,kBAAkB,aAAa,SAAS;AACrE,kBAAQ,OAAO,QAAQ,QAAQ,YAAY,EAAE,GAAG,iBAAiB;SAClE;QACF,EAAC;OACH;MACF,EAAC;KACH;IACF,EAAC;IAGF,MAAM,WAAW,EAAE,wCAAwC,QAAQ,CAAC;IACpE,MAAM,cAAc,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,QAAQ;AACxE,YAAQ,OAAO,QAAQ,QAAQ,YAAY,EAAE,EAAE;GAChD;EACF,EAAC;CACH;AACF","names":["storage: SharedStorage"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/index.ts"],"sourcesContent":["import Plugin from '@vaadin/hilla-generator-core/Plugin.js';\nimport { isReferenceSchema, convertFullyQualifiedNameToRelativePath } from '@vaadin/hilla-generator-core/Schema.js';\nimport type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';\nimport { ModelFixProcessor } from './ModelFixProcessor.js';\nimport { SubTypesProcessor } from './SubTypesProcessor.js';\nimport { TypeFixProcessor } from './TypeFixProcessor.js';\n\nexport default class SubTypesPlugin extends Plugin {\n declare ['constructor']: typeof SubTypesPlugin;\n\n override get path(): string {\n return import.meta.url;\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n override async execute(storage: SharedStorage): Promise<void> {\n const { api, sources } = storage;\n\n const components = api.components?.schemas;\n\n if (!components) {\n return;\n }\n\n Object.entries(components).forEach(([baseKey, baseComponent]) => {\n // search for components with oneOf: those are union types\n if (\n 'oneOf' in baseComponent &&\n Array.isArray(baseComponent.oneOf) &&\n baseComponent.oneOf.every((schema) => isReferenceSchema(schema))\n ) {\n const fn = `${convertFullyQualifiedNameToRelativePath(baseKey)}.ts`;\n const source = sources.find(({ fileName }) => fileName === fn)!;\n // replace the (empty) source with a newly-generated one\n const newSource = new SubTypesProcessor(baseKey, source, baseComponent.oneOf).process();\n sources.splice(sources.indexOf(source), 1, newSource);\n\n // mentioned types in the oneOf need to be fixed as well\n baseComponent.oneOf.forEach((schema) => {\n if ('$ref' in schema) {\n const path = schema.$ref;\n Object.entries(components).forEach(([subKey, subComponent]) => {\n if ('anyOf' in subComponent && subKey === path.substring('#/components/schemas/'.length)) {\n subComponent.anyOf?.forEach((s) => {\n if ('properties' in s && '@type' in s.properties! && 'example' in s.properties['@type']) {\n const typeValue = s.properties['@type'].example as string;\n const subFn = `${convertFullyQualifiedNameToRelativePath(subKey)}.ts`;\n const subSource = sources.find(({ fileName }) => fileName === subFn)!;\n // fix the source to replace the @type property name with a quoted string\n const fixedSource = new TypeFixProcessor(subSource, typeValue).process();\n sources.splice(sources.indexOf(subSource), 1, fixedSource);\n\n // fix the model to remove the @type property\n const modelFn = `${convertFullyQualifiedNameToRelativePath(subKey)}Model.ts`;\n const modelSource = sources.find(({ fileName }) => fileName === modelFn)!;\n const fixedModelSource = new ModelFixProcessor(modelSource).process();\n sources.splice(sources.indexOf(modelSource), 1, fixedModelSource);\n }\n });\n }\n });\n }\n });\n\n // remove the union type model file\n const unionFn = `${convertFullyQualifiedNameToRelativePath(baseKey)}Model.ts`;\n const unionSource = sources.find(({ fileName }) => fileName === unionFn)!;\n sources.splice(sources.indexOf(unionSource), 1);\n }\n });\n }\n}\n"],"version":3}
1
+ {"mappings":"AACA,OAAO,oDAAqD;AAC5D,SACE,mBACA,uFAE8C;AAGhD,SAAS,iDAAkD;AAC3D,SAAS,iDAAkD;AAC3D,SAAS,+CAAgD;AAEzD,SAAS,6BAA8B;AAIvC,MAAM,wBAAwB;;;;;;;AAU9B,SAAS,WAAWA,WAAsD;AACxE,KAAI,kBAAkB,UAAU,EAAE;AAChC,SAAO,CAAE;CACV;AAED,QAAO,UAAU,QACb,UAAU,MAAM,OAAO,CAAC,WAA6C,gBAAgB,OAAO,GAC5F,CAAC,SAAU;AAChB;;;;;;;;;AAUD,SAAS,oBACPC,WACAC,2BAC+B;AAC/B,MAAK,WAAW;AACd,SAAO;CACR;AAED,MAAK,MAAM,UAAU,WAAW,UAAU,EAAE;EAC1C,MAAM,WAAW,OAAO,aAAa;AAErC,MAAI,YAAY,UAAU,YAAY,SAAS,MAAM,QAAQ;AAC3D,UAAO,SAAS,KAAK,OAAO,CAAC,iBAAkC,UAAU,SAAS;EACnF;CACF;AAED,QAAO;AACR;;;;;;;;AASD,SAAS,wBACPC,YACAC,MACAF,2BACgC;CAChC,MAAM,SAAS,IAAI;AAEnB,MAAK,QAAQ,CAAC,QAAQ;EACpB,MAAM,gBAAgB,oBAAoB,WAAW,MAAM,0BAA0B;AAErF,MAAI,eAAe,QAAQ;AACzB,UAAO,IAAI,KAAK,cAAc;EAC/B;CACF,EAAC;AAEF,QAAO;AACR;AAED,SAAS,WAAWG,SAAuBC,QAAgBC,eAAoC;CAC7F,MAAM,EAAE,cAAc,2BAA2B,QAAQ,GAAG;CAC5D,MAAM,aAAa,OAAO,IAAI,OAAO;AAErC,MAAK,YAAY;AACf;CACD;CAED,MAAM,SAAS,EAAE,wCAAwC,OAAO,CAAC;CACjE,MAAM,YAAY,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,MAAM;CAGpE,MAAM,cAAc,IAAI,iBAAiB,WAAW,2BAA2B,YAAY,SAAS;AACpG,SAAQ,OAAO,QAAQ,QAAQ,UAAU,EAAE,GAAG,YAAY;CAG1D,MAAM,WAAW,EAAE,wCAAwC,OAAO,CAAC;CACnE,MAAM,cAAc,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,QAAQ;CACxE,MAAM,mBAAmB,IAAI,kBAAkB,aAAa,2BAA2B,SAAS;AAChG,SAAQ,OAAO,QAAQ,QAAQ,YAAY,EAAE,GAAG,iBAAiB;AAClE;AAED,eAAe,MAAM,uBAAuB,OAAO;CAGjD,IAAa,OAAe;AAC1B,SAAO,OAAO,KAAK;CACpB;CAGD,MAAe,QAAQC,SAAuC;EAC5D,MAAM,EAAE,KAAK,SAAS,GAAG;EAEzB,MAAM,aAAa,IAAI,YAAY;AAEnC,OAAK,YAAY;AACf;EACD;AAED,SAAO,QAAQ,WAAW,CAAC,QAAQ,CAAC,CAAC,SAAS,cAAc,KAAK;AAE/D,OACE,WAAW,iBACX,MAAM,QAAQ,cAAc,MAAM,IAClC,cAAc,MAAM,MAAM,CAAC,WAAW,kBAAkB,OAAO,CAAC,EAChE;IAEA,MAAM,4BAA4B,cAAc,eAAe,gBAAgB;IAC/E,MAAM,UAAU,cAAc,MAAM,IAAI,UAAU;IAClD,MAAM,gBAAgB;KACpB,cAAc;KACd,QAAQ,wBAAwB,YAAY,SAAS,0BAA0B;IAChF;IAED,MAAM,MAAM,EAAE,wCAAwC,QAAQ,CAAC;IAC/D,MAAM,SAAS,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,GAAG;IAE9D,MAAM,YAAY,IAAI,kBAAkB,SAAS,QAAQ,cAAc,OAAO,eAAe,SAAS;AACtG,YAAQ,OAAO,QAAQ,QAAQ,OAAO,EAAE,GAAG,UAAU;AAGrD,YAAQ,QAAQ,CAAC,WAAW;AAC1B,gBAAW,SAAS,QAAQ,cAAc;IAC3C,EAAC;IAGF,MAAM,WAAW,EAAE,wCAAwC,QAAQ,CAAC;IACpE,MAAM,cAAc,QAAQ,KAAK,CAAC,EAAE,UAAU,KAAK,aAAa,QAAQ;AACxE,YAAQ,OAAO,QAAQ,QAAQ,YAAY,EAAE,EAAE;GAChD;EACF,EAAC;CACH;AACF","names":["component: Schema","component: Schema | undefined","discriminatorPropertyName: string","components: Components","keys: readonly string[]","sources: SourceFile[]","subKey: string","discriminator: Discriminator","storage: SharedStorage"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/index.ts"],"sourcesContent":["import type { SourceFile } from '@typescript/typescript6';\nimport Plugin from '@vaadin/hilla-generator-core/Plugin.js';\nimport {\n isReferenceSchema,\n convertFullyQualifiedNameToRelativePath,\n type Schema,\n} from '@vaadin/hilla-generator-core/Schema.js';\nimport type { SharedStorage } from '@vaadin/hilla-generator-core/SharedStorage.js';\nimport type { OpenAPIV3 } from 'openapi-types';\nimport { ModelFixProcessor } from './ModelFixProcessor.js';\nimport { SubTypesProcessor } from './SubTypesProcessor.js';\nimport { TypeFixProcessor } from './TypeFixProcessor.js';\nimport type { Discriminator } from './utils.js';\nimport { schemaKey } from './utils.js';\n\n// the property name used by Jackson unless `@JsonTypeInfo` specifies another\n// one: kept as a fallback for OpenAPI documents without a `discriminator`\nconst DEFAULT_DISCRIMINATOR = '@type';\n\ntype Components = Readonly<Record<string, Schema>>;\n\n/**\n * Returns the schemas that hold the properties declared by the type itself: a\n * subtype is a composed schema whose `anyOf` list contains a reference to the\n * supertype and an object schema with the own properties, while a type that has\n * no supertype is a plain object schema.\n */\nfunction ownSchemas(component: Schema): readonly OpenAPIV3.SchemaObject[] {\n if (isReferenceSchema(component)) {\n return [];\n }\n\n return component.anyOf\n ? component.anyOf.filter((schema): schema is OpenAPIV3.SchemaObject => 'properties' in schema)\n : [component];\n}\n\n/**\n * Returns the values that the discriminator property of the type accepts: the\n * Java parser stores them as the `enum` of that property, the own value of the\n * type first, followed by the values of the subtypes below it, which narrow the\n * discriminator further. The property is left alone when the values are\n * missing, as pinning a type to its own value alone would clash with the\n * subtypes below it.\n */\nfunction discriminatorValues(\n component: Schema | undefined,\n discriminatorPropertyName: string,\n): readonly string[] | undefined {\n if (!component) {\n return undefined;\n }\n\n for (const schema of ownSchemas(component)) {\n const property = schema.properties?.[discriminatorPropertyName];\n\n if (property && 'enum' in property && property.enum?.length) {\n return property.enum.filter((value): value is string => typeof value === 'string');\n }\n }\n\n return undefined;\n}\n\n/**\n * Returns the values that the discriminator accepts in each subtype, mapped by\n * schema name. A subtype that is also the supertype of another subtype accepts\n * more than one value: pinning it to its own value would leave the subtype\n * unable to extend it, or to be used as the type parameter of its model, so the\n * union type pins the own value of such a subtype instead.\n */\nfunction findDiscriminatorValues(\n components: Components,\n keys: readonly string[],\n discriminatorPropertyName: string,\n): Map<string, readonly string[]> {\n const values = new Map<string, readonly string[]>();\n\n keys.forEach((key) => {\n const subTypeValues = discriminatorValues(components[key], discriminatorPropertyName);\n\n if (subTypeValues?.length) {\n values.set(key, subTypeValues);\n }\n });\n\n return values;\n}\n\nfunction fixSubType(sources: SourceFile[], subKey: string, discriminator: Discriminator): void {\n const { propertyName: discriminatorPropertyName, values } = discriminator;\n const typeValues = values.get(subKey);\n\n if (!typeValues) {\n return;\n }\n\n const subFn = `${convertFullyQualifiedNameToRelativePath(subKey)}.ts`;\n const subSource = sources.find(({ fileName }) => fileName === subFn)!;\n // fix the source to turn the discriminator property into the string literals\n // that the type accepts\n const fixedSource = new TypeFixProcessor(subSource, discriminatorPropertyName, typeValues).process();\n sources.splice(sources.indexOf(subSource), 1, fixedSource);\n\n // fix the model to remove the discriminator property\n const modelFn = `${convertFullyQualifiedNameToRelativePath(subKey)}Model.ts`;\n const modelSource = sources.find(({ fileName }) => fileName === modelFn)!;\n const fixedModelSource = new ModelFixProcessor(modelSource, discriminatorPropertyName).process();\n sources.splice(sources.indexOf(modelSource), 1, fixedModelSource);\n}\n\nexport default class SubTypesPlugin extends Plugin {\n declare ['constructor']: typeof SubTypesPlugin;\n\n override get path(): string {\n return import.meta.url;\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n override async execute(storage: SharedStorage): Promise<void> {\n const { api, sources } = storage;\n\n const components = api.components?.schemas;\n\n if (!components) {\n return;\n }\n\n Object.entries(components).forEach(([baseKey, baseComponent]) => {\n // search for components with oneOf: those are union types\n if (\n 'oneOf' in baseComponent &&\n Array.isArray(baseComponent.oneOf) &&\n baseComponent.oneOf.every((schema) => isReferenceSchema(schema))\n ) {\n // `@JsonTypeInfo` may use any property name as the discriminator\n const discriminatorPropertyName = baseComponent.discriminator?.propertyName ?? DEFAULT_DISCRIMINATOR;\n const subKeys = baseComponent.oneOf.map(schemaKey);\n const discriminator = {\n propertyName: discriminatorPropertyName,\n values: findDiscriminatorValues(components, subKeys, discriminatorPropertyName),\n };\n\n const fn = `${convertFullyQualifiedNameToRelativePath(baseKey)}.ts`;\n const source = sources.find(({ fileName }) => fileName === fn)!;\n // replace the (empty) source with a newly-generated one\n const newSource = new SubTypesProcessor(baseKey, source, baseComponent.oneOf, discriminator).process();\n sources.splice(sources.indexOf(source), 1, newSource);\n\n // mentioned types in the oneOf need to be fixed as well\n subKeys.forEach((subKey) => {\n fixSubType(sources, subKey, discriminator);\n });\n\n // remove the union type model file\n const unionFn = `${convertFullyQualifiedNameToRelativePath(baseKey)}Model.ts`;\n const unionSource = sources.find(({ fileName }) => fileName === unionFn)!;\n sources.splice(sources.indexOf(unionSource), 1);\n }\n });\n }\n}\n"],"version":3}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/hilla-generator-plugin-subtypes",
3
- "version": "25.3.0-beta1",
3
+ "version": "25.3.0-beta2",
4
4
  "description": "A Hilla TypeScript Generator plugin to support JsonSubTypes",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -50,10 +50,10 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@typescript/typescript6": "6.0.2",
53
- "@vaadin/hilla-generator-core": "25.3.0-beta1",
54
- "@vaadin/hilla-generator-plugin-client": "25.3.0-beta1",
55
- "@vaadin/hilla-generator-plugin-model": "25.3.0-beta1",
56
- "@vaadin/hilla-generator-utils": "25.3.0-beta1",
53
+ "@vaadin/hilla-generator-core": "25.3.0-beta2",
54
+ "@vaadin/hilla-generator-plugin-client": "25.3.0-beta2",
55
+ "@vaadin/hilla-generator-plugin-model": "25.3.0-beta2",
56
+ "@vaadin/hilla-generator-utils": "25.3.0-beta2",
57
57
  "fast-deep-equal": "^3.1.3",
58
58
  "openapi-types": "^12.1.3"
59
59
  }
package/utils.d.ts CHANGED
@@ -1,2 +1,37 @@
1
- import { type PropertyName } from "@typescript/typescript6";
1
+ import ts, { type PropertyName, type TypeNode } from "@typescript/typescript6";
2
+ import type { ReferenceSchema } from "@vaadin/hilla-generator-core/Schema.js";
3
+ /**
4
+ * The discriminator of a union type: the name of the property that holds it,
5
+ * and the values that the property accepts in each subtype, mapped by schema
6
+ * name. The own value of a subtype comes first, followed by the values of the
7
+ * subtypes below it.
8
+ */
9
+ export type Discriminator = Readonly<{
10
+ propertyName: string
11
+ values: ReadonlyMap<string, readonly string[]>
12
+ }>;
2
13
  export declare function propertyNameToString(node: PropertyName): string | null;
14
+ export declare function schemaKey(schema: ReferenceSchema): string;
15
+ /**
16
+ * Creates a property name node, quoting it when it is not a valid identifier:
17
+ * the default discriminator property of Jackson is `@type`.
18
+ */
19
+ export declare function createPropertyName(name: string): PropertyName;
20
+ /**
21
+ * Creates the property signature that restricts the discriminator to the given
22
+ * values, e. g. `"@type": "add"`. A subtype that is also the supertype of
23
+ * another subtype accepts more than one value, as the discriminator is narrowed
24
+ * further down the hierarchy.
25
+ */
26
+ export declare function createDiscriminatorProperty(propertyName: string, typeValues: readonly string[]): ts.PropertySignature;
27
+ /**
28
+ * Creates a type that pins the discriminator of a type accepting more than one
29
+ * value, e. g. `(BaseEvent & { "@type": "base" })`.
30
+ */
31
+ export declare function createDiscriminatedType(type: TypeNode, propertyName: string, typeValue: string): TypeNode;
32
+ /**
33
+ * Removes the named imports that are not referenced anymore, which happens when
34
+ * the discriminator property was the only user of a model type. The default
35
+ * import of a generated model is its entity type, which is always used.
36
+ */
37
+ export declare function removeUnusedImports(statements: readonly ts.Statement[]): readonly ts.Statement[];
package/utils.js CHANGED
@@ -1,8 +1,67 @@
1
1
  import ts from "@typescript/typescript6";
2
+ const SCHEMA_PREFIX = "#/components/schemas/";
3
+ const IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
2
4
  export function propertyNameToString(node) {
3
5
  if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
4
6
  return node.text;
5
7
  }
6
8
  return null;
7
9
  }
10
+ export function schemaKey(schema) {
11
+ return schema.$ref.substring(SCHEMA_PREFIX.length);
12
+ }
13
+ /**
14
+ * Creates a property name node, quoting it when it is not a valid identifier:
15
+ * the default discriminator property of Jackson is `@type`.
16
+ */
17
+ export function createPropertyName(name) {
18
+ return IDENTIFIER_PATTERN.test(name) ? ts.factory.createIdentifier(name) : ts.factory.createStringLiteral(name);
19
+ }
20
+ /**
21
+ * Creates the property signature that restricts the discriminator to the given
22
+ * values, e. g. `"@type": "add"`. A subtype that is also the supertype of
23
+ * another subtype accepts more than one value, as the discriminator is narrowed
24
+ * further down the hierarchy.
25
+ */
26
+ export function createDiscriminatorProperty(propertyName, typeValues) {
27
+ const literals = typeValues.map((typeValue) => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(typeValue)));
28
+ return ts.factory.createPropertySignature(undefined, createPropertyName(propertyName), undefined, literals.length === 1 ? literals[0] : ts.factory.createUnionTypeNode(literals));
29
+ }
30
+ /**
31
+ * Creates a type that pins the discriminator of a type accepting more than one
32
+ * value, e. g. `(BaseEvent & { "@type": "base" })`.
33
+ */
34
+ export function createDiscriminatedType(type, propertyName, typeValue) {
35
+ return ts.factory.createParenthesizedType(ts.factory.createIntersectionTypeNode([type, ts.factory.createTypeLiteralNode([createDiscriminatorProperty(propertyName, [typeValue])])]));
36
+ }
37
+ /**
38
+ * Removes the named imports that are not referenced anymore, which happens when
39
+ * the discriminator property was the only user of a model type. The default
40
+ * import of a generated model is its entity type, which is always used.
41
+ */
42
+ export function removeUnusedImports(statements) {
43
+ const used = new Set();
44
+ function collect(node) {
45
+ if (ts.isIdentifier(node)) {
46
+ used.add(node.text);
47
+ }
48
+ ts.forEachChild(node, collect);
49
+ }
50
+ statements.filter((statement) => !ts.isImportDeclaration(statement)).forEach(collect);
51
+ return statements.map((statement) => {
52
+ if (!ts.isImportDeclaration(statement) || !statement.importClause) {
53
+ return statement;
54
+ }
55
+ const { importClause } = statement;
56
+ const bindings = importClause.namedBindings;
57
+ if (!bindings || !ts.isNamedImports(bindings)) {
58
+ return statement;
59
+ }
60
+ const elements = bindings.elements.filter((element) => used.has(element.name.text));
61
+ if (elements.length === bindings.elements.length) {
62
+ return statement;
63
+ }
64
+ return ts.factory.updateImportDeclaration(statement, statement.modifiers, ts.factory.updateImportClause(importClause, importClause.phaseModifier, importClause.name, ts.factory.updateNamedImports(bindings, elements)), statement.moduleSpecifier, statement.attributes);
65
+ });
66
+ }
8
67
  //# sourceMappingURL=./utils.js.map
package/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"mappings":"AAAA,OAAO,iCAAyD;AAEhE,OAAO,SAAS,qBAAqBA,MAAmC;AACtE,KAAI,GAAG,aAAa,KAAK,IAAI,GAAG,gBAAgB,KAAK,IAAI,GAAG,iBAAiB,KAAK,EAAE;AAClF,SAAO,KAAK;CACb;AACD,QAAO;AACR","names":["node: PropertyName"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/utils.ts"],"sourcesContent":["import ts, { type PropertyName } from '@typescript/typescript6';\n\nexport function propertyNameToString(node: PropertyName): string | null {\n if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {\n return node.text;\n }\n return null;\n}\n"],"version":3}
1
+ {"mappings":"AAAA,OAAO,iCAAwE;AAG/E,MAAM,gBAAgB;AAYtB,MAAM,qBAAqB;AAE3B,OAAO,SAAS,qBAAqBA,MAAmC;AACtE,KAAI,GAAG,aAAa,KAAK,IAAI,GAAG,gBAAgB,KAAK,IAAI,GAAG,iBAAiB,KAAK,EAAE;AAClF,SAAO,KAAK;CACb;AACD,QAAO;AACR;AAED,OAAO,SAAS,UAAUC,QAAiC;AACzD,QAAO,OAAO,KAAK,UAAU,cAAc,OAAO;AACnD;;;;;AAMD,OAAO,SAAS,mBAAmBC,MAA4B;AAC7D,QAAO,mBAAmB,KAAK,KAAK,GAAG,GAAG,QAAQ,iBAAiB,KAAK,GAAG,GAAG,QAAQ,oBAAoB,KAAK;AAChH;;;;;;;AAQD,OAAO,SAAS,4BAA4BC,cAAsBC,YAAqD;CACrH,MAAM,WAAW,WAAW,IAAI,CAAC,cAC/B,GAAG,QAAQ,sBAAsB,GAAG,QAAQ,oBAAoB,UAAU,CAAC,CAC5E;AAED,QAAO,GAAG,QAAQ,wBAChB,WACA,mBAAmB,aAAa,EAChC,WACA,SAAS,WAAW,IAAI,SAAS,KAAK,GAAG,QAAQ,oBAAoB,SAAS,CAC/E;AACF;;;;;AAMD,OAAO,SAAS,wBAAwBC,MAAgBF,cAAsBG,WAA6B;AACzG,QAAO,GAAG,QAAQ,wBAChB,GAAG,QAAQ,2BAA2B,CACpC,MACA,GAAG,QAAQ,sBAAsB,CAAC,4BAA4B,cAAc,CAAC,SAAU,EAAC,AAAC,EAAC,AAC3F,EAAC,CACH;AACF;;;;;;AAOD,OAAO,SAAS,oBAAoBC,YAA8D;CAChG,MAAM,OAAO,IAAI;CAEjB,SAAS,QAAQC,MAAqB;AACpC,MAAI,GAAG,aAAa,KAAK,EAAE;AACzB,QAAK,IAAI,KAAK,KAAK;EACpB;AAED,KAAG,aAAa,MAAM,QAAQ;CAC/B;AAED,YAAW,OAAO,CAAC,eAAe,GAAG,oBAAoB,UAAU,CAAC,CAAC,QAAQ,QAAQ;AAErF,QAAO,WAAW,IAAI,CAAC,cAAc;AACnC,OAAK,GAAG,oBAAoB,UAAU,KAAK,UAAU,cAAc;AACjE,UAAO;EACR;EAED,MAAM,EAAE,cAAc,GAAG;EACzB,MAAM,WAAW,aAAa;AAE9B,OAAK,aAAa,GAAG,eAAe,SAAS,EAAE;AAC7C,UAAO;EACR;EAED,MAAM,WAAW,SAAS,SAAS,OAAO,CAAC,YAAY,KAAK,IAAI,QAAQ,KAAK,KAAK,CAAC;AAEnF,MAAI,SAAS,WAAW,SAAS,SAAS,QAAQ;AAChD,UAAO;EACR;AAED,SAAO,GAAG,QAAQ,wBAChB,WACA,UAAU,WACV,GAAG,QAAQ,mBACT,cACA,aAAa,eACb,aAAa,MACb,GAAG,QAAQ,mBAAmB,UAAU,SAAS,CAClD,EACD,UAAU,iBACV,UAAU,WACX;CACF,EAAC;AACH","names":["node: PropertyName","schema: ReferenceSchema","name: string","propertyName: string","typeValues: readonly string[]","type: TypeNode","typeValue: string","statements: readonly ts.Statement[]","node: ts.Node"],"sources":["/opt/agent/work/649c11185a3798db/packages/ts/generator-plugin-subtypes/src/utils.ts"],"sourcesContent":["import ts, { type PropertyName, type TypeNode } from '@typescript/typescript6';\nimport type { ReferenceSchema } from '@vaadin/hilla-generator-core/Schema.js';\n\nconst SCHEMA_PREFIX = '#/components/schemas/';\n\n/**\n * The discriminator of a union type: the name of the property that holds it,\n * and the values that the property accepts in each subtype, mapped by schema\n * name. The own value of a subtype comes first, followed by the values of the\n * subtypes below it.\n */\nexport type Discriminator = Readonly<{\n propertyName: string;\n values: ReadonlyMap<string, readonly string[]>;\n}>;\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;\n\nexport function propertyNameToString(node: PropertyName): string | null {\n if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {\n return node.text;\n }\n return null;\n}\n\nexport function schemaKey(schema: ReferenceSchema): string {\n return schema.$ref.substring(SCHEMA_PREFIX.length);\n}\n\n/**\n * Creates a property name node, quoting it when it is not a valid identifier:\n * the default discriminator property of Jackson is `@type`.\n */\nexport function createPropertyName(name: string): PropertyName {\n return IDENTIFIER_PATTERN.test(name) ? ts.factory.createIdentifier(name) : ts.factory.createStringLiteral(name);\n}\n\n/**\n * Creates the property signature that restricts the discriminator to the given\n * values, e. g. `\"@type\": \"add\"`. A subtype that is also the supertype of\n * another subtype accepts more than one value, as the discriminator is narrowed\n * further down the hierarchy.\n */\nexport function createDiscriminatorProperty(propertyName: string, typeValues: readonly string[]): ts.PropertySignature {\n const literals = typeValues.map((typeValue) =>\n ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(typeValue)),\n );\n\n return ts.factory.createPropertySignature(\n undefined,\n createPropertyName(propertyName),\n undefined,\n literals.length === 1 ? literals[0] : ts.factory.createUnionTypeNode(literals),\n );\n}\n\n/**\n * Creates a type that pins the discriminator of a type accepting more than one\n * value, e. g. `(BaseEvent & { \"@type\": \"base\" })`.\n */\nexport function createDiscriminatedType(type: TypeNode, propertyName: string, typeValue: string): TypeNode {\n return ts.factory.createParenthesizedType(\n ts.factory.createIntersectionTypeNode([\n type,\n ts.factory.createTypeLiteralNode([createDiscriminatorProperty(propertyName, [typeValue])]),\n ]),\n );\n}\n\n/**\n * Removes the named imports that are not referenced anymore, which happens when\n * the discriminator property was the only user of a model type. The default\n * import of a generated model is its entity type, which is always used.\n */\nexport function removeUnusedImports(statements: readonly ts.Statement[]): readonly ts.Statement[] {\n const used = new Set<string>();\n\n function collect(node: ts.Node): void {\n if (ts.isIdentifier(node)) {\n used.add(node.text);\n }\n\n ts.forEachChild(node, collect);\n }\n\n statements.filter((statement) => !ts.isImportDeclaration(statement)).forEach(collect);\n\n return statements.map((statement) => {\n if (!ts.isImportDeclaration(statement) || !statement.importClause) {\n return statement;\n }\n\n const { importClause } = statement;\n const bindings = importClause.namedBindings;\n\n if (!bindings || !ts.isNamedImports(bindings)) {\n return statement;\n }\n\n const elements = bindings.elements.filter((element) => used.has(element.name.text));\n\n if (elements.length === bindings.elements.length) {\n return statement;\n }\n\n return ts.factory.updateImportDeclaration(\n statement,\n statement.modifiers,\n ts.factory.updateImportClause(\n importClause,\n importClause.phaseModifier,\n importClause.name,\n ts.factory.updateNamedImports(bindings, elements),\n ),\n statement.moduleSpecifier,\n statement.attributes,\n );\n });\n}\n"],"version":3}