Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion factory/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function createParser(program: ts.Program, config: CompletedConfig, augme
.addNodeParser(new ObjectTypeNodeParser())
.addNodeParser(new AsExpressionNodeParser(chainNodeParser))
.addNodeParser(new SatisfiesNodeParser(chainNodeParser))
.addNodeParser(withJsDoc(new ParameterParser(chainNodeParser)))
.addNodeParser(withJsDoc(new ParameterParser(chainNodeParser, typeChecker)))
.addNodeParser(new StringLiteralNodeParser())
.addNodeParser(new StringTemplateLiteralNodeParser(chainNodeParser))
.addNodeParser(new IntrinsicNodeParser())
Expand Down
27 changes: 25 additions & 2 deletions src/NodeParser/FunctionNodeParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,34 @@
// If it's missing a questionToken but has an initializer we can consider the property as not required
const required = node.parameters[index].questionToken ? false : !node.parameters[index].initializer;

return new ObjectProperty(node.parameters[index].name.getText(), parameterType, required);
return new ObjectProperty(getParameterName(node.parameters[index].name, index), parameterType, required);
}),
false,
);
}

function getParameterName(node: ts.BindingName, index: number) {
if (node.parent) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (node.parent) {
if (node.parent && node.pos !== -1) {

Programatically created nodes don't have text information.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get why the condition node.pos !== -1 should be added. node.parent should not be sufficient?

return node.getText();
}

// for parameter type in inferred function type
if (ts.isIdentifier(node)) {
/**
* function foo(name: string) {}
* ^^^^
*/
return node.escapedText as string;
}

// otherwise, for BindingPattern
/**
* function foo([a, b, c]: number[]) {}
* ^^^^^^^^^
*/
return `_${index}`;

Check warning on line 92 in src/NodeParser/FunctionNodeParser.ts

View check run for this annotation

Codecov / codecov/patch

src/NodeParser/FunctionNodeParser.ts#L92

Added line #L92 was not covered by tests
}

export function getTypeName(
node:
| ts.FunctionTypeNode
Expand All @@ -80,7 +102,8 @@
): string | undefined {
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isFunctionTypeNode(node)) {
const parent = node.parent;
if (ts.isVariableDeclaration(parent)) {
// there is no parent for inferred function type node.
if (parent && ts.isVariableDeclaration(parent)) {
return parent.name.getText();
}
}
Expand Down
24 changes: 20 additions & 4 deletions src/NodeParser/ParameterParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,30 @@
import type { Context } from "../NodeParser.js";
import type { SubNodeParser } from "../SubNodeParser.js";
import type { BaseType } from "../Type/BaseType.js";
import { UnknownType } from "../Type/UnknownType.js";

export class ParameterParser implements SubNodeParser {
constructor(protected childNodeParser: NodeParser) {}
constructor(
protected childNodeParser: NodeParser,
protected checker: ts.TypeChecker,
) {}

public supportsNode(node: ts.ParameterDeclaration): boolean {
public supportsNode(node: ts.Node): boolean {
return node.kind === ts.SyntaxKind.Parameter;
}
public createType(node: ts.FunctionTypeNode, context: Context): BaseType {
return this.childNodeParser.createType(node.type, context);
public createType(node: ts.ParameterDeclaration, context: Context): BaseType {
// If the parameter type is declared, use it directly to retain the location information.
if (node.type) {
return this.childNodeParser.createType(node.type, context);
}

// otherwise, use inferred type
const paramType = this.checker.getTypeAtLocation(node.name);
const typeNode = this.checker.typeToTypeNode(paramType, node.parent, ts.NodeBuilderFlags.NoTruncation);
if (typeNode) {
return this.childNodeParser.createType(typeNode, context);
}

return new UnknownType(true);

Check warning on line 30 in src/NodeParser/ParameterParser.ts

View check run for this annotation

Codecov / codecov/patch

src/NodeParser/ParameterParser.ts#L30

Added line #L30 was not covered by tests
}
}
6 changes: 5 additions & 1 deletion src/Type/FunctionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export class FunctionType extends BaseType {
super();

if (node) {
this.comment = `(${node.parameters.map((p) => p.getFullText()).join(",")}) =>${node.type?.getFullText()}`;
if (node.parent) {
this.comment = `(${node.parameters.map((p) => p.getFullText()).join(",")}) =>${node.type?.getFullText()}`;
} else {
this.comment = "Function";
}
}
}

Expand Down
1 change: 1 addition & 0 deletions test/valid-data-other.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe("valid-data-other", () => {
it("exported-enums-union", assertValidSchema("exported-enums-union", "MyObject"));

it("function-parameters-default-value", assertValidSchema("function-parameters-default-value", "myFunction"));
it("function-parameters-infer-type", assertValidSchema("function-parameters-infer-type", "myFunction"));
it("function-parameters-declaration", assertValidSchema("function-parameters-declaration", "myFunction"));
it("function-parameters-jsdoc", assertValidSchema("function-parameters-jsdoc", "myFunction", { jsDoc: "basic" }));
it("function-parameters-optional", assertValidSchema("function-parameters-optional", "myFunction"));
Expand Down
16 changes: 16 additions & 0 deletions test/valid-data/function-parameters-infer-type/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { myObj, MyClass, myArray, mFunction } from "./module";

export const myFunction = (
str = "something",
num = 123,
bool = true,
[a, b, c] = [1, 2, 3],
obj = { a: 1, b: 2 },
func = (a: number, b: number) => a + b,
object = myObj,
func1 = mFunction,
clas = new MyClass(),
arr = myArray,
) => {
return "whatever";
};
15 changes: 15 additions & 0 deletions test/valid-data/function-parameters-infer-type/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function mFunction(input = "something") {
return "whatever";
}

export class MyClass {
someField = "something";
}

export const myObj = {
str: "str",
num: 123,
func: () => "whatever",
};

export const myArray = ["str", 123, () => "whatever"];
139 changes: 139 additions & 0 deletions test/valid-data/function-parameters-infer-type/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{
"$ref": "#/definitions/myFunction",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"MyClass": {
"additionalProperties": false,
"properties": {
"someField": {
"type": "string"
}
},
"required": [
"someField"
],
"type": "object"
},
"myFunction": {
"$comment": "(\n str = \"something\",\n num = 123,\n bool = true,\n [a, b, c] = [1, 2, 3],\n obj = { a: 1, b: 2 },\n func = (a: number, b: number) => a + b,\n object = myObj,\n func1 = mFunction,\n clas = new MyClass(),\n arr = myArray) =>undefined",
"properties": {
"namedArgs": {
"additionalProperties": false,
"properties": {
"[a, b, c]": {
"items": {
"type": "number"
},
"maxItems": 3,
"minItems": 3,
"type": "array"
},
"arr": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"$comment": "Function"
}
]
},
"type": "array"
},
"bool": {
"type": "boolean"
},
"clas": {
"$ref": "#/definitions/MyClass"
},
"func": {
"$comment": "Function",
"properties": {
"namedArgs": {
"additionalProperties": false,
"properties": {
"a": {
"type": "number"
},
"b": {
"type": "number"
}
},
"required": [
"a",
"b"
],
"type": "object"
}
},
"type": "object"
},
"func1": {
"$comment": "Function",
"properties": {
"namedArgs": {
"additionalProperties": false,
"properties": {
"input": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
},
"num": {
"type": "number"
},
"obj": {
"additionalProperties": false,
"properties": {
"a": {
"type": "number"
},
"b": {
"type": "number"
}
},
"required": [
"a",
"b"
],
"type": "object"
},
"object": {
"additionalProperties": false,
"properties": {
"func": {
"$comment": "Function"
},
"num": {
"type": "number"
},
"str": {
"type": "string"
}
},
"required": [
"str",
"num",
"func"
],
"type": "object"
},
"str": {
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}
}
}
Loading