Skip to content

Commit 07e0e6d

Browse files
authored
fix: unresolved reference (#8)
close #7
1 parent 4bd3aa3 commit 07e0e6d

File tree

12 files changed

+113
-41
lines changed

12 files changed

+113
-41
lines changed

src/Converter/v3/Context.ts

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as Path from "path";
33
import ts from "typescript";
44

55
import * as TypeScriptCodeGenerator from "../../CodeGenerator";
6-
import { DevelopmentError, NotFoundReference } from "../../Exception";
6+
import { DevelopmentError } from "../../Exception";
77
import { Store } from "./store";
88
import * as ToTypeNode from "./toTypeNode";
99

@@ -24,54 +24,67 @@ const generatePath = (entryPoint: string, currentPoint: string, referencePath: s
2424
};
2525
};
2626

27-
const generateName = (store: Store.Type, base: string, pathArray: string[]): string => {
27+
const calculateReferencePath = (store: Store.Type, base: string, pathArray: string[]): ToTypeNode.ResolveReferencePath => {
2828
let names: string[] = [];
29+
let unresolvedPaths: string[] = [];
2930
pathArray.reduce((previous, lastPath, index) => {
3031
const current = Path.join(previous, lastPath);
3132
// ディレクトリが深い場合は相対パスが`..`を繰り返す可能性があり、
3233
// その場合はすでに登録されたnamesを削除する
3334
if (lastPath === ".." && names.length > 0) {
3435
names = names.slice(0, names.length - 1);
3536
}
36-
const isLast = index === pathArray.length - 1;
37-
if (isLast) {
37+
const isFinalPath = index === pathArray.length - 1;
38+
if (isFinalPath) {
3839
const statement = store.getStatement(current, "interface");
3940
const statement2 = store.getStatement(current, "typeAlias");
4041
const statement3 = store.getStatement(current, "namespace");
4142
if (statement) {
4243
names.push(statement.name);
44+
return current;
4345
} else if (statement2) {
4446
names.push(statement2.name);
47+
return current;
4548
} else if (statement3) {
4649
names.push(statement3.name);
50+
return current;
51+
} else {
52+
unresolvedPaths.push(lastPath);
4753
}
4854
} else {
4955
const statement = store.getStatement(current, "namespace");
5056
if (statement) {
51-
names.push(statement.value.name.text);
57+
unresolvedPaths = unresolvedPaths.slice(0, unresolvedPaths.length - 1);
58+
names.push(statement.name);
59+
} else {
60+
unresolvedPaths.push(lastPath);
5261
}
5362
}
5463
return current;
5564
}, base);
5665
if (names.length === 0) {
5766
throw new DevelopmentError("Local Reference Error \n" + JSON.stringify({ pathArray, names, base }, null, 2));
5867
}
59-
return names.join(".");
68+
return {
69+
name: names.join("."),
70+
maybeResolvedName: names.concat(unresolvedPaths).join("."),
71+
unresolvedPaths,
72+
};
6073
};
6174

6275
export const create = (entryPoint: string, store: Store.Type, factory: TypeScriptCodeGenerator.Factory.Type): ToTypeNode.Context => {
63-
const getReferenceName: ToTypeNode.Context["getReferenceName"] = (currentPoint, referencePath): string => {
76+
const resolveReferencePath: ToTypeNode.Context["resolveReferencePath"] = (currentPoint, referencePath) => {
6477
const { pathArray, base } = generatePath(entryPoint, currentPoint, referencePath);
65-
return generateName(store, base, pathArray);
78+
return calculateReferencePath(store, base, pathArray);
6679
};
67-
const setReferenceHandler: ToTypeNode.Context["setReferenceHandler"] = reference => {
80+
const setReferenceHandler: ToTypeNode.Context["setReferenceHandler"] = (currentPoint, reference) => {
6881
if (store.hasStatement(reference.path, ["interface", "typeAlias"])) {
6982
return;
7083
}
7184
if (reference.type === "remote") {
7285
const typeNode = ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
7386
setReferenceHandler,
74-
getReferenceName,
87+
resolveReferencePath,
7588
});
7689
if (ts.isTypeLiteralNode(typeNode)) {
7790
store.addStatement(reference.path, {
@@ -89,7 +102,7 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
89102
name: reference.name,
90103
type: ToTypeNode.convert(entryPoint, reference.referencePoint, factory, reference.data, {
91104
setReferenceHandler,
92-
getReferenceName,
105+
resolveReferencePath,
93106
}),
94107
});
95108
store.addStatement(reference.path, {
@@ -99,10 +112,22 @@ export const create = (entryPoint: string, store: Store.Type, factory: TypeScrip
99112
});
100113
}
101114
} else if (reference.type === "local") {
102-
if (!store.hasStatement(reference.path, ["namespace", "interface", "typeAlias"])) {
103-
throw new NotFoundReference(`The schema ${reference.name} is undefined in "${reference.path}".`);
115+
if (!store.isAfterDefined(reference.path)) {
116+
const { maybeResolvedName } = resolveReferencePath(currentPoint, reference.path);
117+
const value = factory.TypeAliasDeclaration.create({
118+
export: true,
119+
name: reference.name,
120+
type: factory.TypeReferenceNode.create({
121+
name: maybeResolvedName,
122+
}),
123+
});
124+
store.addStatement(reference.path, {
125+
name: reference.name,
126+
type: "typeAlias",
127+
value,
128+
});
104129
}
105130
}
106131
};
107-
return { setReferenceHandler: setReferenceHandler, getReferenceName };
132+
return { setReferenceHandler: setReferenceHandler, resolveReferencePath: resolveReferencePath };
108133
};

src/Converter/v3/components/Header.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,20 @@ export const generatePropertySignature = (
3333
if (Guard.isReference(header)) {
3434
const reference = Reference.generate<OpenApi.Header>(entryPoint, currentPoint, header);
3535
if (reference.type === "local") {
36-
context.setReferenceHandler(reference);
36+
context.setReferenceHandler(currentPoint, reference);
3737
return factory.PropertySignature.create({
3838
name,
3939
optional: false,
4040
type: factory.TypeReferenceNode.create({
41-
name: context.getReferenceName(currentPoint, reference.path),
41+
name: context.resolveReferencePath(currentPoint, reference.path).name,
4242
}),
4343
});
4444
}
4545
return factory.PropertySignature.create({
4646
name,
4747
optional: false,
4848
type: factory.TypeReferenceNode.create({
49-
name: context.getReferenceName(currentPoint, reference.path),
49+
name: context.resolveReferencePath(currentPoint, reference.path).name,
5050
}),
5151
});
5252
}

src/Converter/v3/components/Operation.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ export const generateNamespace = (
7272
if (Guard.isReference(operation.requestBody)) {
7373
const reference = Reference.generate<OpenApi.RequestBody>(entryPoint, currentPoint, operation.requestBody);
7474
if (reference.type === "local") {
75-
context.setReferenceHandler(reference);
75+
context.setReferenceHandler(currentPoint, reference);
7676
// TODO (not-use) 追加する必要がある(このメソッドを使わない可能性あり)
77-
factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, reference.path) });
77+
factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, reference.path).name });
7878
} else if (reference.type === "remote" && reference.componentName) {
7979
const contentPath = path.join(reference.path, "Content"); // requestBodyはNamespaceを形成するため
8080
const name = "Content";
@@ -83,7 +83,7 @@ export const generateNamespace = (
8383
name: name,
8484
value: RequestBody.generateInterface(entryPoint, reference.referencePoint, factory, name, reference.data, context),
8585
});
86-
const typeAliasName = context.getReferenceName(currentPoint, contentPath);
86+
const typeAliasName = context.resolveReferencePath(currentPoint, contentPath).name;
8787
store.addStatement(`${basePath}/RequestBody`, {
8888
type: "typeAlias",
8989
name: typeAliasName,
@@ -129,13 +129,13 @@ export const generateStatements = (
129129
if (Guard.isReference(operation.requestBody)) {
130130
const reference = Reference.generate<OpenApi.RequestBody>(entryPoint, currentPoint, operation.requestBody);
131131
if (reference.type === "local") {
132-
context.setReferenceHandler(reference);
132+
context.setReferenceHandler(currentPoint, reference);
133133
statements.push(
134134
factory.TypeAliasDeclaration.create({
135135
export: true,
136136
name: Name.requestBodyName(operationId),
137137
type: factory.TypeReferenceNode.create({
138-
name: context.getReferenceName(currentPoint, `${reference.path}`) + "." + Name.ComponentChild.Content, // TODO Contextから作成?
138+
name: context.resolveReferencePath(currentPoint, `${reference.path}`) + "." + Name.ComponentChild.Content, // TODO Contextから作成?
139139
}),
140140
}),
141141
);
@@ -151,7 +151,7 @@ export const generateStatements = (
151151
factory.TypeAliasDeclaration.create({
152152
export: true,
153153
name: requestBodyName,
154-
type: factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, contentPath) }),
154+
type: factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, contentPath).name }),
155155
}),
156156
);
157157

src/Converter/v3/components/Parameter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ export const generatePropertySignature = (
4343
if (Guard.isReference(parameter)) {
4444
const reference = Reference.generate<OpenApi.Parameter>(entryPoint, currentPoint, parameter);
4545
if (reference.type === "local") {
46-
context.setReferenceHandler(reference);
46+
context.setReferenceHandler(currentPoint, reference);
4747
return factory.PropertySignature.create({
4848
name: reference.name,
4949
optional: false,
5050
type: factory.TypeReferenceNode.create({
51-
name: context.getReferenceName(currentPoint, reference.path),
51+
name: context.resolveReferencePath(currentPoint, reference.path).name,
5252
}),
5353
});
5454
}

src/Converter/v3/components/Parameters.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export const generateNamespace = (
4747
export: true,
4848
name: name,
4949
type: factory.TypeReferenceNode.create({
50-
name: context.getReferenceName(currentPoint, reference.path),
50+
name: context.resolveReferencePath(currentPoint, reference.path).name,
5151
}),
5252
}),
5353
});

src/Converter/v3/components/Response.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const generateReferenceNamespace = (
5959
context: ToTypeNode.Context,
6060
): void => {
6161
const basePath = `${parentPath}/${nameWithStatusCode}`;
62-
const referenceNamespaceName = context.getReferenceName(currentPoint, responseReference.path);
62+
const referenceNamespaceName = context.resolveReferencePath(currentPoint, responseReference.path).name;
6363
store.addStatement(basePath, {
6464
type: "namespace",
6565
name: nameWithStatusCode,

src/Converter/v3/components/Responses.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const generateNamespaceWithStatusCode = (
8585
if (Guard.isReference(response)) {
8686
const reference = Reference.generate<OpenApi.Response>(entryPoint, currentPoint, response);
8787
if (reference.type === "local") {
88-
context.setReferenceHandler(reference);
88+
context.setReferenceHandler(currentPoint, reference);
8989
Response.generateReferenceNamespace(entryPoint, currentPoint, store, factory, basePath, nameWithStatusCode, reference, context);
9090
} else if (reference.componentName) {
9191
// reference先に定義を作成
@@ -125,13 +125,14 @@ export const generateInterfacesWithStatusCode = (
125125
if (Guard.isReference(response)) {
126126
const reference = Reference.generate<OpenApi.Response>(entryPoint, currentPoint, response);
127127
if (reference.type === "local") {
128-
context.setReferenceHandler(reference);
128+
context.setReferenceHandler(currentPoint, reference);
129+
const name = context.resolveReferencePath(currentPoint, `${reference.path}/Content`).maybeResolvedName;
129130
statements.push(
130131
factory.TypeAliasDeclaration.create({
131132
export: true,
132133
name: Name.responseName(operationId, statusCode),
133134
type: factory.TypeReferenceNode.create({
134-
name: context.getReferenceName(currentPoint, `${reference.path}/Content`),
135+
name: name,
135136
}),
136137
}),
137138
);

src/Converter/v3/components/Schemas.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ export const generateNamespace = (
3232
if (Guard.isReference(schema)) {
3333
const reference = Reference.generate<OpenApi.Schema>(entryPoint, currentPoint, schema);
3434
if (reference.type === "local") {
35-
const referenceName = context.getReferenceName(currentPoint, reference.path);
35+
const { maybeResolvedName } = context.resolveReferencePath(currentPoint, reference.path);
3636
store.addStatement(`${basePath}/${name}`, {
3737
type: "typeAlias",
3838
name: name,
3939
value: factory.TypeAliasDeclaration.create({
4040
export: true,
4141
name: name,
4242
type: factory.TypeReferenceNode.create({
43-
name: referenceName,
43+
name: maybeResolvedName,
4444
}),
4545
}),
4646
});
@@ -58,7 +58,7 @@ export const generateNamespace = (
5858
name: name,
5959
comment: reference.data.description,
6060
type: factory.TypeReferenceNode.create({
61-
name: context.getReferenceName(currentPoint, reference.path),
61+
name: context.resolveReferencePath(currentPoint, reference.path).name,
6262
}),
6363
}),
6464
});

src/Converter/v3/store/Store.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface Type {
3737
dumpOperationState: (filename: string) => void;
3838
getNoReferenceOperationState: () => Operation.State;
3939
getPathItem: (localPath: string) => OpenApi.PathItem;
40+
isAfterDefined: (referencePath: string) => boolean;
4041
}
4142

4243
export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): Type => {
@@ -79,8 +80,12 @@ export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): T
7980
};
8081

8182
const hasStatement = (path: string, types: Def.Statement<A, B, C>["type"][]): boolean => {
82-
const targetPath = relative("components", path);
83-
return types.some(type => !!PropAccess.get(state.components, type, targetPath));
83+
const alreadyRegistered = types.some(type => !!getStatement(path, type));
84+
return alreadyRegistered;
85+
};
86+
87+
const isAfterDefined = (referencePath: string) => {
88+
return !!Dot.get(state.document, referencePath.replace(/\//g, "."));
8489
};
8590

8691
const addStatement = (path: string, statement: Def.Statement<A, B, C>): void => {
@@ -191,5 +196,6 @@ export const create = (factory: Factory.Type, rootDocument: OpenApi.Document): T
191196
addAdditionalStatement,
192197
dumpOperationState,
193198
getPathItem,
199+
isAfterDefined,
194200
};
195201
};

src/Converter/v3/toTypeNode.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@ import * as Guard from "./Guard";
99
import { OpenApi } from "./types";
1010
import { ObjectSchemaWithAdditionalProperties } from "./types";
1111

12+
export interface ResolveReferencePath {
13+
name: string;
14+
maybeResolvedName: string;
15+
unresolvedPaths: string[];
16+
}
17+
1218
export interface Context {
13-
setReferenceHandler: (reference: Reference.Type<OpenApi.Schema | OpenApi.JSONSchemaDefinition>) => void;
14-
getReferenceName: (currentPoint: string, referencePath: string) => string;
19+
setReferenceHandler: (currentPoint: string, reference: Reference.Type<OpenApi.Schema | OpenApi.JSONSchemaDefinition>) => void;
20+
resolveReferencePath: (currentPoint: string, referencePath: string) => ResolveReferencePath;
1521
}
1622

1723
export type Convert = (
@@ -84,15 +90,15 @@ export const convert: Convert = (
8490
const reference = Reference.generate<OpenApi.Schema | OpenApi.JSONSchemaDefinition>(entryPoint, currentPoint, schema);
8591
if (reference.type === "local") {
8692
// Type Aliasを作成 (or すでにある場合は作成しない)
87-
context.setReferenceHandler(reference);
88-
return factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, reference.path) });
93+
context.setReferenceHandler(currentPoint, reference);
94+
return factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, reference.path).maybeResolvedName });
8995
}
9096
// サポートしているディレクトリに対して存在する場合
9197
if (reference.componentName) {
9298
// Type AliasもしくはInterfaceを作成
93-
context.setReferenceHandler(reference);
99+
context.setReferenceHandler(currentPoint, reference);
94100
// Aliasを貼る
95-
return factory.TypeReferenceNode.create({ name: context.getReferenceName(currentPoint, reference.path) });
101+
return factory.TypeReferenceNode.create({ name: context.resolveReferencePath(currentPoint, reference.path).name });
96102
}
97103
// サポートしていないディレクトリに存在する場合、直接Interface、もしくはTypeAliasを作成
98104
return convert(entryPoint, reference.referencePoint, factory, reference.data, context, { parent: schema });

test/__tests__/__snapshots__/snapshot-test.ts.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ export namespace Schemas {
6464
}
6565
export type LocalRefOneOfType = Schemas.StringType | Schemas.NumberType | Schemas.ObjectHasPropertiesType | Schemas.LocalRefObjectProperties;
6666
export type LocalRefAllOfType = Schemas.StringType & Schemas.NumberType & Schemas.ObjectHasPropertiesType & Schemas.LocalRefObjectProperties;
67+
export type LocalReferenceBeforeResolvedSchema1 = Schemas.UnresolvedTarget1;
68+
export type UnresolvedTarget1 = boolean;
69+
export type LocalReferenceBeforeResolvedSchema2 = Schemas.UnresolvedTarget2;
70+
export type UnresolvedTarget2 = Schemas.UnresolvedTarget3;
71+
export type UnresolvedTarget3 = number;
72+
export interface LocalReferenceBeforeResolvedSchema3 {
73+
unresolvedTarget4?: Schemas.UnresolvedTarget4;
74+
}
75+
export interface UnresolvedTarget4 {
76+
unresolvedTarget5?: Schemas.UnresolvedTarget5;
77+
}
78+
export type UnresolvedTarget5 = string;
6779
export type RemoteString = string;
6880
export type RemoteRefString = Schemas.RemoteString;
6981
export namespace Level1 {

test/api.test.domain/index.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,28 @@ components:
150150
- $ref: "#/components/schemas/NumberType"
151151
- $ref: "#/components/schemas/ObjectHasPropertiesType"
152152
- $ref: "#/components/schemas/LocalRefObjectProperties"
153+
LocalReferenceBeforeResolvedSchema1:
154+
$ref: "#/components/schemas/UnresolvedTarget1"
155+
UnresolvedTarget1:
156+
type: boolean
157+
LocalReferenceBeforeResolvedSchema2:
158+
$ref: "#/components/schemas/UnresolvedTarget2"
159+
UnresolvedTarget2:
160+
$ref: "#/components/schemas/UnresolvedTarget3"
161+
UnresolvedTarget3:
162+
type: number
163+
LocalReferenceBeforeResolvedSchema3:
164+
type: object
165+
properties:
166+
unresolvedTarget4:
167+
$ref: "#/components/schemas/UnresolvedTarget4"
168+
UnresolvedTarget4:
169+
type: object
170+
properties:
171+
unresolvedTarget5:
172+
$ref: "#/components/schemas/UnresolvedTarget5"
173+
UnresolvedTarget5:
174+
type: string
153175
RemoteRefString:
154176
$ref: "./components/schemas/RemoteString.yml"
155177
RemoteRefBoolean:

0 commit comments

Comments
 (0)