Skip to content

Commit

Permalink
feat: support $ref (#1208)
Browse files Browse the repository at this point in the history
  • Loading branch information
hmil committed Apr 7, 2022
1 parent a577157 commit cdd587e
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 9 deletions.
3 changes: 2 additions & 1 deletion src/AnnotationsReader/BasicAnnotationsReader.ts
Expand Up @@ -5,14 +5,15 @@ import { Annotations } from "../Type/AnnotatedType";
import { symbolAtNode } from "../Utils/symbolAtNode";

export class BasicAnnotationsReader implements AnnotationsReader {
private static requiresDollar = new Set<string>(["id", "comment"]);
private static requiresDollar = new Set<string>(["id", "comment", "ref"]);
private static textTags = new Set<string>([
"title",
"description",
"id",

"format",
"pattern",
"ref",

// New since draft-07:
"comment",
Expand Down
15 changes: 10 additions & 5 deletions src/NodeParser/AnnotatedNodeParser.ts
Expand Up @@ -9,6 +9,7 @@ import { ReferenceType } from "../Type/ReferenceType";
import { removeUndefined } from "../Utils/removeUndefined";
import { DefinitionType } from "../Type/DefinitionType";
import { UnionType } from "../Type/UnionType";
import { AnyType } from "../Type/AnyType";

export class AnnotatedNodeParser implements SubNodeParser {
public constructor(protected childNodeParser: SubNodeParser, protected annotationsReader: AnnotationsReader) {}
Expand All @@ -18,15 +19,21 @@ export class AnnotatedNodeParser implements SubNodeParser {
}

public createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType | undefined {
const annotatedNode = this.getAnnotatedNode(node);
let annotations = this.annotationsReader.getAnnotations(annotatedNode);
const nullable = this.getNullable(annotatedNode);

// Short-circuit parsing the underlying type if an explicit ref annotation was passed.
if (annotations && "$ref" in annotations) {
return new AnnotatedType(new AnyType(), annotations, nullable);
}

const baseType = this.childNodeParser.createType(node, context, reference);

if (baseType === undefined) {
return undefined;
}

const annotatedNode = this.getAnnotatedNode(node);
let annotations = this.annotationsReader.getAnnotations(annotatedNode);

// Don't return annotations for lib types such as Exclude.
if (node.getSourceFile().fileName.match(/[/\\]typescript[/\\]lib[/\\]lib\.[^/\\]+\.d\.ts$/i)) {
let specialCase = false;
Expand Down Expand Up @@ -58,8 +65,6 @@ export class AnnotatedNodeParser implements SubNodeParser {
}
}

const nullable = this.getNullable(annotatedNode);

return !annotations && !nullable ? baseType : new AnnotatedType(baseType, annotations || {}, nullable);
}

Expand Down
12 changes: 9 additions & 3 deletions src/Utils/removeUnreachable.ts
Expand Up @@ -2,6 +2,8 @@ import { JSONSchema7Definition } from "json-schema";
import { Definition } from "../Schema/Definition";
import { StringMap } from "./StringMap";

const DEFINITION_OFFSET = "#/definitions/".length;

function addReachable(
definition: Definition | JSONSchema7Definition,
definitions: StringMap<Definition>,
Expand All @@ -12,9 +14,9 @@ function addReachable(
}

if (definition.$ref) {
const typeName = decodeURIComponent(definition.$ref.slice(14));
if (reachable.has(typeName)) {
// we've already processed this definition
const typeName = decodeURIComponent(definition.$ref.slice(DEFINITION_OFFSET));
if (reachable.has(typeName) || !isLocalRef(definition.$ref)) {
// we've already processed this definition, or this definition refers to an external schema
return;
}
reachable.add(typeName);
Expand Down Expand Up @@ -79,3 +81,7 @@ export function removeUnreachable(

return out;
}

function isLocalRef(ref: string) {
return ref.charAt(0) === "#";
}
2 changes: 2 additions & 0 deletions test/valid-data-annotations.test.ts
Expand Up @@ -39,5 +39,7 @@ describe("valid-data-annotations", () => {

it("annotation-readOnly", assertValidSchema("annotation-readOnly", "MyObject", "basic"));

it("annotation-ref", assertValidSchema("annotation-ref", "MyObject", "extended"));

it("annotation-writeOnly", assertValidSchema("annotation-writeOnly", "MyObject", "basic"));
});
23 changes: 23 additions & 0 deletions test/valid-data/annotation-ref/main.ts
@@ -0,0 +1,23 @@

export interface MyObject {
/**
* Nested description
*
* @title Nested title
* @ref http://json-schema.org/draft-07/schema#
*/
nested: MyNestedObject

/**
* MyObject description
*
* @title MyObject title
* @ref http://json-schema.org/draft-07/schema#
*/
myObject: { [key: string]: string };
}

export interface MyNestedObject {
foo: string;
bar: number;
}
25 changes: 25 additions & 0 deletions test/valid-data/annotation-ref/schema.json
@@ -0,0 +1,25 @@
{
"$ref": "#/definitions/MyObject",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"MyObject": {
"additionalProperties": false,
"properties": {
"nested": {
"$ref": "http://json-schema.org/draft-07/schema#",
"title": "Nested title",
"description": "Nested description"
},
"myObject": {
"$ref": "http://json-schema.org/draft-07/schema#",
"title": "MyObject title",
"description": "MyObject description"
}
},
"required": [
"nested", "myObject"
],
"type": "object"
}
}
}

0 comments on commit cdd587e

Please sign in to comment.