Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6da0765
Use transform to retrieve JSDoc
JairusSW Oct 28, 2024
cf452e4
display docs in generated schema
JairusSW Oct 31, 2024
dedaa19
setup go transform
JairusSW Nov 1, 2024
5f97d33
use proper GraphQL doc comments
JairusSW Nov 4, 2024
14ff9f3
ignore invalid comments and add fields to docs
JairusSW Nov 7, 2024
e430910
extract comments from ast
JairusSW Nov 11, 2024
7d4f49f
remove "fmt"
JairusSW Nov 11, 2024
cfef4d1
clean up and finish
JairusSW Nov 11, 2024
710969f
revert http example
JairusSW Nov 11, 2024
57d6d8a
Merge branch 'main' into jairus/hyp-2411-extract-doc-comments-from-fu…
mattjohnsonpint Nov 12, 2024
bdfd62a
lint/fmt
mattjohnsonpint Nov 12, 2024
0af6644
Merge branch 'main' into jairus/hyp-2411-extract-doc-comments-from-fu…
mattjohnsonpint Nov 13, 2024
d6b85a8
keep graphql type definitions separate from metadata
JairusSW Nov 14, 2024
726966d
clean up
JairusSW Nov 14, 2024
847faa3
format and fix version
JairusSW Nov 16, 2024
c9eb054
only include valid docs in preprocess
JairusSW Nov 21, 2024
500c724
Merge branch 'main' into jairus/hyp-2411-extract-doc-comments-from-fu…
mattjohnsonpint Nov 21, 2024
c1d56ba
Fix struct/field docs extraction
mattjohnsonpint Nov 22, 2024
40aacd4
Fix function doc extraction
mattjohnsonpint Nov 22, 2024
3c9dab7
Merge branch 'main' into jairus/hyp-2411-extract-doc-comments-from-fu…
mattjohnsonpint Nov 22, 2024
1041c92
Update schemagen
mattjohnsonpint Nov 22, 2024
b4ceee5
remove visitor dependency
JairusSW Nov 22, 2024
f54cc35
lint
JairusSW Nov 22, 2024
ce29863
Merge branch 'main' into jairus/hyp-2411-extract-doc-comments-from-fu…
mattjohnsonpint Nov 23, 2024
7f77e80
Adjust comments in simple examples
mattjohnsonpint Nov 23, 2024
8861e7a
Update CHANGELOG.md
mattjohnsonpint Nov 23, 2024
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
5 changes: 5 additions & 0 deletions lib/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@ type Metadata struct {
Types TypeMap `json:"types,omitempty"`
}

type Docs struct {
Description string `json:"description"`;
}

type Function struct {
Name string `json:"-"`
Parameters []*Parameter `json:"parameters,omitempty"`
Results []*Result `json:"results,omitempty"`
Docs *Docs `json:"docs,omitempty"`
}

type TypeDefinition struct {
Expand Down
136 changes: 32 additions & 104 deletions sdk/assemblyscript/examples/http/assembly/index.ts
Original file line number Diff line number Diff line change
@@ -1,108 +1,36 @@
/*
* This example is part of the Modus project, licensed under the Apache License 2.0.
* You may modify and use this example in accordance with the license.
* See the LICENSE file that accompanied this code for further details.
/**
* This is some documentation
*/
// This is a single line comment
@json
export function foo(player: Player): void {}

import { http } from "@hypermode/modus-sdk-as";
import { Quote, Image, Issue } from "./classes";

// This function makes a simple HTTP GET request to example.com,
// and returns the HTML text of the response.
export function getExampleHtml(): string {
const response = http.fetch("https://example.com/");
return response.text();
}

// This function makes a request to an API that returns data in JSON format,
// and returns an object representing the data.
// It also demonstrates how to check the HTTP response status.
export function getRandomQuote(): Quote {
const request = new http.Request("https://zenquotes.io/api/random");

const response = http.fetch(request);
if (!response.ok) {
throw new Error(
`Failed to fetch quote. Received: ${response.status} ${response.statusText}`,
);
}

// The API returns an array of quotes, but we only want the first one.
return response.json<Quote[]>()[0];
}

// This function makes a request to an API that returns an image, and returns the image data.
export function getRandomImage(width: i32, height: i32): Image {
const url = `https://picsum.photos/${width}/${height}`;
const response = http.fetch(url);
const contentType = response.headers.get("Content-Type")!;

return {
contentType,
data: response.body,
};
/**
* This is a class that represents a Three Dimensional Vector
*/
@json
class Vec3 {
x: f32 = 0.0;
y: f32 = 0.0;
z: f32 = 0.0;
}

/*
This function demonstrates a more complex HTTP call.
It makes a POST request to the GitHub API to create an issue.

See https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#create-an-issue

To use it, you must add a GitHub personal access token to your secrets.
Create a fine-grained token at https://github.com/settings/tokens?type=beta with access
to write issues to the repository you want to use, then add it to the appropriate secret
store for your environment. (See the modus documentation for details.)

NOTE: Do not pass the Authorization header in code when creating the request.
That would be a security risk, as the token could be exposed in the source code repository.

Instead, configure the headers in the modus.json manifest as follows:

"hosts": {
"github": {
"baseUrl": "https://api.github.com/",
"headers": {
"Authorization": "Bearer {{AUTH_TOKEN}}"
}
}
}

The Modus runtime will retrieve the token from your secrets and add it to the request.
*/
export function createGithubIssue(
owner: string,
repo: string,
title: string,
body: string,
): Issue {
// The URL for creating an issue in a GitHub repository.
const url = `https://api.github.com/repos/${owner}/${repo}/issues`;

// Create a new request with the URL, method, and headers.
const request = new http.Request(url, {
method: "POST",
headers: http.Headers.from([
// Do not pass an Authorization header here. See note above.
["Accept", "application/vnd.github+json"],
["X-GitHub-Api-Version", "2022-11-28"],
["Content-Type", "application/json"],
]),

// The request body will be sent as JSON from the Issue object passed here.
body: http.Content.from(<Issue>{ title, body }),
} as http.RequestOptions);

// Send the request and check the response status.
// NOTE: If you are using a private repository, and you get a 404 error, that could
// be an authentication issue. Make sure you have created a token as described above.
const response = http.fetch(request);
if (!response.ok) {
throw new Error(
`Failed to create issue. Received: ${response.status} ${response.statusText}`,
);
}

// The response will contain the issue data, including the URL of the issue on GitHub.
return response.json<Issue>();
}
/**
* This class represents a player in a fictitious game
*/
@json
class Player {
@alias("first name")
firstName!: string;
lastName!: string;
/**
* This is some docs describing lastActive
*/
lastActive!: i32[];
// This is some single line docs describing age
@omitif("this.age < 18")
age!: i32;
@omitnull()
pos!: Vec3 | null;
isVerified!: boolean;
}
97 changes: 94 additions & 3 deletions sdk/assemblyscript/src/transform/src/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import binaryen from "assemblyscript/lib/binaryen.js";
import {
ArrayLiteralExpression,
Class,
CommentKind,
CommentNode,
ElementKind,
Expression,
FloatLiteralExpression,
Expand All @@ -22,9 +24,11 @@ import {
NodeKind,
Program,
Property,
Range,
StringLiteralExpression,
} from "assemblyscript/dist/assemblyscript.js";
import {
Docs,
FunctionSignature,
JsonLiteral,
Parameter,
Expand All @@ -33,16 +37,23 @@ import {
typeMap,
} from "./types.js";
import ModusTransform from "./index.js";
import { Visitor } from "./visitor.js";

export class Extractor {
binaryen: typeof binaryen;
module: binaryen.Module;
program: Program;
transform: ModusTransform;

constructor(transform: ModusTransform, module: binaryen.Module) {
this.program = transform.program;
constructor(transform: ModusTransform) {
this.binaryen = transform.binaryen;
}

initHook(program: Program): void {
this.program = program;
}

compileHook(module: binaryen.Module): void {
this.module = module;
}

Expand Down Expand Up @@ -157,6 +168,7 @@ export class Extractor {
.map((f) => ({
name: f.name,
type: f.type.toString(),
docs: null
}));
}

Expand Down Expand Up @@ -216,9 +228,88 @@ export class Extractor {
});
}

return new FunctionSignature(e.name, params, [
const signature = new FunctionSignature(e.name, params, [
{ type: f.signature.returnType.toString() },
]);

signature.docs = this.getDocsFromComment(signature);
return signature;
}
private getDocsFromComment(signature: FunctionSignature) {
const visitor = new Visitor();

visitor.visitFunctionDeclaration = (node: FunctionDeclaration) => {
const source = node.range.source;
// Exported/Imported name may differ from real defined name
// TODO: Track renaming and aliasing of function identifiers

if (node.name.text == signature.name) {
const nodeIndex = source.statements.indexOf(node);
const prevNode = source.statements[Math.max(nodeIndex - 1, 0)];

const start = nodeIndex > 0 ? prevNode.range.start : 0;
const end = node.range.start;

const newRange = new Range(start, end);
newRange.source = source;
const commentNodes = this.parseComments(newRange);
if (!commentNodes.length) return;
console.log("Start: " + start);
console.log("End: " + end);
console.log("Text: " + source.text.slice(start, end));
console.log("Comment: ", commentNodes);
return Docs.from(commentNodes);
}
}

visitor.visit(this.program.sources);
return null;
}
private parseComments(range: Range): CommentNode[] {
const nodes: CommentNode[] = [];
const text = range.source.text.slice(range.start, range.end).trim();

let commentKind: CommentKind;

if (text.startsWith("//")) {
commentKind = text.startsWith("///") ? CommentKind.Triple : CommentKind.Line;

const end = range.source.text.indexOf("\n", range.start + 1);
if (end === -1) return [];
range.start = range.source.text.indexOf("//", range.start);
const newRange = new Range(range.start, end);
newRange.source = range.source;
const node = new CommentNode(commentKind, newRange.source.text.slice(newRange.start, newRange.end), newRange);

nodes.push(node);

if (end < range.end) {
const newRange = new Range(end, range.end);
newRange.source = range.source;
nodes.push(...this.parseComments(newRange));
}
} else if (text.startsWith("/*")) {
commentKind = CommentKind.Block;
const end = range.source.text.indexOf("*/", range.start) + 2;
if (end === 1) return [];

range.start = range.source.text.indexOf("/**", range.start);
const newRange = new Range(range.start, end);
newRange.source = range.source;
const node = new CommentNode(commentKind, newRange.source.text.slice(newRange.start, newRange.end), newRange);

nodes.push(node);

if (end < range.end) {
const newRange = new Range(end, range.end);
newRange.source = range.source;
nodes.push(...this.parseComments(newRange));
}
} else {
return [];
}

return nodes;
}
}

Expand Down
10 changes: 8 additions & 2 deletions sdk/assemblyscript/src/transform/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ import { Transform } from "assemblyscript/dist/transform.js";
import { Metadata } from "./metadata.js";
import { Extractor } from "./extractor.js";
import binaryen from "assemblyscript/lib/binaryen.js";
import { Program } from "types:assemblyscript/src/program";

export default class ModusTransform extends Transform {
private extractor = new Extractor(this);
afterInitialize(program: Program): void | Promise<void> {
this.extractor.initHook(program);
}
afterCompile(module: binaryen.Module) {
const extractor = new Extractor(this, module);
const info = extractor.getProgramInfo();
this.extractor.compileHook(module);

const info = this.extractor.getProgramInfo();

const m = Metadata.generate();
m.addExportFn(info.exportFns);
Expand Down
27 changes: 25 additions & 2 deletions sdk/assemblyscript/src/transform/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { CommentKind, CommentNode } from "assemblyscript/dist/assemblyscript.js";
import { getTypeName } from "./extractor.js";

export class ProgramInfo {
Expand All @@ -20,12 +21,28 @@ export class Result {
public type: string;
}

export class Docs {
constructor(public description: string) { }
static from(nodes: CommentNode[]): Docs | null {
for (const node of nodes.reverse()) {
if (node.commentKind != CommentKind.Block || !node.text.startsWith("/**")) continue;
const lines = node.text.split("\n").filter(v => v.trim().startsWith("*") && v.trim().length > 1);
const description = lines[0].replace("*", "").trim();
// const returnTypeDesc
// const paramDesc
return new Docs(description);
}
return null;
}
}

export class FunctionSignature {
constructor(
public name: string,
public parameters: Parameter[],
public results: Result[],
) {}
public docs: Docs | null = null
) { }

toString() {
let params = "";
Expand Down Expand Up @@ -56,6 +73,10 @@ export class FunctionSignature {
output["results"] = this.results;
}

if (this.docs) {
output["docs"] = this.docs;
}

return output;
}
}
Expand All @@ -65,7 +86,8 @@ export class TypeDefinition {
public name: string,
public id: number,
public fields?: Field[],
) {}
public docs: Docs | null = null
) { }

toString() {
const name = getTypeName(this.name);
Expand Down Expand Up @@ -108,6 +130,7 @@ export interface Parameter {
interface Field {
name: string;
type: string;
docs: Docs | null;
}

export const typeMap = new Map<string, string>([
Expand Down
Loading