Skip to content

Commit 23f79db

Browse files
authored
Merge pull request YousefED#335 from RyanLB/ryanlb/Unique_Names
[Fixes YousefED#331][Fixes YousefED#333] Fix two issues with the --uniqueNames options
2 parents 14cffd2 + dd5a1d2 commit 23f79db

File tree

5 files changed

+122
-13
lines changed

5 files changed

+122
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import "./other";
2+
3+
class SubObject {
4+
is: "SubObject_1";
5+
}
6+
7+
class MyObject {
8+
sub: SubObject;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class SubObject {
2+
is: "SubObject_2";
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"properties": {
4+
"sub": {
5+
"$ref": "#/definitions/SubObject.0eb4e9af"
6+
}
7+
},
8+
"type": "object",
9+
"required": [
10+
"sub"
11+
],
12+
"definitions": {
13+
"SubObject.0eb4e9af": {
14+
"properties": {
15+
"is": {
16+
"enum": [
17+
"SubObject_1"
18+
],
19+
"type": "string"
20+
}
21+
},
22+
"required": [
23+
"is"
24+
],
25+
"type": "object"
26+
}
27+
}
28+
}
29+

test/schema.test.ts

+15-4
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,21 @@ describe("schema", () => {
294294
assertSchema("namespace-deep-2", "RootNamespace.SubNamespace.HelperA");
295295
});
296296

297+
describe("uniqueNames", () => {
298+
assertSchemas("unique-names", "MyObject", {
299+
uniqueNames: true
300+
});
301+
302+
// It should throw an error if there are two definitions for the top-level ref
303+
assertRejection("unique-names", "MyObject", {
304+
uniqueNames: true
305+
});
306+
307+
assertSchema("unique-names-multiple-subdefinitions", "MyObject", {
308+
uniqueNames: true
309+
});
310+
});
311+
297312
describe("other", () => {
298313
assertSchema("array-and-description", "MyObject");
299314

@@ -312,10 +327,6 @@ describe("schema", () => {
312327
excludePrivate: true
313328
});
314329

315-
assertSchemas("unique-names", "MyObject", {
316-
uniqueNames: true
317-
});
318-
319330
assertSchema("builtin-names", "Ext.Foo");
320331

321332
assertSchema("user-symbols", "*");

typescript-json-schema.ts

+66-9
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,42 @@ function makeNullable(def: Definition) {
247247
return def;
248248
}
249249

250+
/**
251+
* Given a Symbol, returns a canonical Definition. That can be either:
252+
* 1) The Symbol's valueDeclaration parameter if defined, or
253+
* 2) The sole entry in the Symbol's declarations array, provided that array has a length of 1.
254+
*
255+
* valueDeclaration is listed as a required parameter in the definition of a Symbol, but I've
256+
* experienced crashes when it's undefined at runtime, which is the reason for this function's
257+
* existence. Not sure if that's a compiler API bug or what.
258+
*/
259+
function getCanonicalDeclaration(sym: ts.Symbol): ts.Declaration {
260+
if (sym.valueDeclaration !== undefined) {
261+
return sym.valueDeclaration;
262+
} else if (sym.declarations.length === 1) {
263+
return sym.declarations[0];
264+
}
265+
266+
throw new Error(`Symbol "${sym.name}" has no valueDeclaration and ${sym.declarations.length} declarations.`);
267+
}
268+
269+
/**
270+
* Given a Symbol, finds the place it was declared and chases parent pointers until we find a
271+
* node where SyntaxKind === SourceFile.
272+
*/
273+
function getSourceFile(sym: ts.Symbol): ts.SourceFile {
274+
let currentDecl: ts.Node = getCanonicalDeclaration(sym);
275+
276+
while (currentDecl.kind !== ts.SyntaxKind.SourceFile) {
277+
if (currentDecl.parent === undefined) {
278+
throw new Error(`Unable to locate source file for declaration "${sym.name}".`);
279+
}
280+
currentDecl = currentDecl.parent;
281+
}
282+
283+
return currentDecl as ts.SourceFile;
284+
}
285+
250286
/**
251287
* JSDoc keywords that should be used to annotate the JSON schema.
252288
*
@@ -950,16 +986,30 @@ export class JsonSchemaGenerator {
950986

951987
let fullTypeName = "";
952988
if (asTypeAliasRef) {
953-
fullTypeName = this.makeTypeNameUnique(
954-
typ,
955-
this.tc.getFullyQualifiedName(
956-
reffedType!.getFlags() & ts.SymbolFlags.Alias ?
957-
this.tc.getAliasedSymbol(reffedType!) :
958-
reffedType!
959-
).replace(REGEX_FILE_NAME_OR_SPACE, "")
960-
);
989+
const typeName = this.tc.getFullyQualifiedName(
990+
reffedType!.getFlags() & ts.SymbolFlags.Alias ?
991+
this.tc.getAliasedSymbol(reffedType!) :
992+
reffedType!
993+
).replace(REGEX_FILE_NAME_OR_SPACE, "");
994+
if (this.args.uniqueNames) {
995+
const sourceFile = getSourceFile(reffedType!);
996+
const relativePath = path.relative(process.cwd(), sourceFile.fileName);
997+
fullTypeName = `${typeName}.${generateHashOfNode(getCanonicalDeclaration(reffedType!), relativePath)}`;
998+
} else {
999+
fullTypeName = this.makeTypeNameUnique(
1000+
typ,
1001+
typeName
1002+
);
1003+
}
9611004
} else if (asRef) {
962-
fullTypeName = this.getTypeName(typ);
1005+
if (this.args.uniqueNames) {
1006+
const sym = typ.symbol;
1007+
const sourceFile = getSourceFile(sym);
1008+
const relativePath = path.relative(process.cwd(), sourceFile.fileName);
1009+
fullTypeName = `${this.getTypeName(typ)}.${generateHashOfNode(getCanonicalDeclaration(sym), relativePath)}`;
1010+
} else {
1011+
fullTypeName = this.getTypeName(typ);
1012+
}
9631013
}
9641014

9651015
if (asRef) {
@@ -1236,6 +1286,13 @@ export function generateSchema(program: ts.Program, fullTypeName: string, args:
12361286

12371287
if (fullTypeName === "*") { // All types in file(s)
12381288
return generator.getSchemaForSymbols(generator.getMainFileSymbols(program, onlyIncludeFiles));
1289+
} else if (args.uniqueNames) { // Find the hashed type name to use as the root object
1290+
const matchingSymbols = generator.getSymbols(fullTypeName);
1291+
if (matchingSymbols.length === 1) {
1292+
return generator.getSchemaForSymbol(matchingSymbols[0].name);
1293+
} else {
1294+
throw new Error(`${matchingSymbols.length} definitions found for requested type "${fullTypeName}".`);
1295+
}
12391296
} else { // Use specific type as root object
12401297
return generator.getSchemaForSymbol(fullTypeName);
12411298
}

0 commit comments

Comments
 (0)