Skip to content

Commit

Permalink
Merge pull request #47 from codefori/feature/generated_docs
Browse files Browse the repository at this point in the history
Feature/generated docs
  • Loading branch information
worksofliam authored Nov 23, 2024
2 parents 820daf7 + 3650b37 commit 49f0d95
Show file tree
Hide file tree
Showing 8 changed files with 1,093 additions and 75 deletions.
11 changes: 9 additions & 2 deletions .astro/astro/content.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,19 @@ declare module 'astro:content' {
collection: "docs";
data: InferEntrySchema<"docs">
} & { render(): Render[".mdx"] };
"dev/api.mdx": {
id: "dev/api.mdx";
"dev/api.md": {
id: "dev/api.md";
slug: "dev/api";
body: string;
collection: "docs";
data: InferEntrySchema<"docs">
} & { render(): Render[".md"] };
"dev/examples.mdx": {
id: "dev/examples.mdx";
slug: "dev/examples";
body: string;
collection: "docs";
data: InferEntrySchema<"docs">
} & { render(): Render[".mdx"] };
"dev/getting_started.mdx": {
id: "dev/getting_started.mdx";
Expand Down
42 changes: 41 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"build": "npm run devdocs && astro check && astro build",
"preview": "astro preview",
"astro": "astro"
"astro": "astro",
"devdocs": "npx tsx src/api/gen.ts"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/starlight": "^0.28.6",
"@halcyontech/vscode-ibmi-types": "^2.13.5",
"astro": "^4.16.9",
"sharp": "^0.32.5",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"typescript-parser": "^2.6.1"
}
}
213 changes: 213 additions & 0 deletions src/api/gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { ClassDeclaration, DefaultDeclaration, File, FunctionDeclaration, InterfaceDeclaration, TypescriptParser, VariableDeclaration } from "typescript-parser";
import type {Declaration} from "typescript-parser";
import path from "path";
import { stat, writeFile, readFile } from "fs/promises";

const exists = (path: string): Promise<boolean> => {
return new Promise((resolve) => {
stat(path).then(() => resolve(true)).catch(() => resolve(false));
});
}

const htmlEscape = (str: string): string => {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

const linkType = (type: string|undefined): string => {
if (!type) {
return 'void';
}

type = type.trim();

if (type.includes(`\n`)) {
type = type.split(`\n`).map(t => t.trim()).join(` `);
}

const jsTypes = ['string', 'number', 'boolean', 'object', 'any', 'void', `Date`, `Function`, `undefined`, `Uint8Array`];
if (jsTypes.includes(type)) {
return type;
}

if (type.includes(`.`) || type.includes(`=>`) || type.includes(`"`) || type.includes(`{`)) {
return htmlEscape(type);
}

if (type.startsWith(`Promise<`)) {
return `Promise&lt;${linkType(type.slice(8, -1))}&gt;`;
}

if (type.startsWith(`Array<`)) {
return `Array&lt;${linkType(type.slice(6, -1))}&gt;`;
}

if (type.startsWith(`Partial<`)) {
return `Partial&lt;${linkType(type.slice(8, -1))}&gt;`;
}

if (type.endsWith(`<T>`)) {
return `${linkType(type.slice(0, -3))}&lt;T&gt;`;
}

if (type.endsWith(`[]`)) {
return `${linkType(type.slice(0, -2))}[]`;
}

if (type.includes(`|`)) {
return type.split(`|`).map(t => linkType(t)).join(` | `);
}

return `<a href="#${type.toLowerCase()}">${htmlEscape(type)}</a>`;
}

const PRIVATE = 0;
let parsedFiles: string[] = [];
let sections: { [key: string]: string } = {};
const parser = new TypescriptParser();

// or a filepath
const HEADER_MDX = 'src/api/header.mdx';
const WORKSPACE = 'node_modules/@halcyontech/vscode-ibmi-types/';

const parseFile = async (tsPath: string) => {
if (parsedFiles.includes(tsPath)) {
return [];
}

console.log(`Parsing ${tsPath}`);

const parsed = await parser.parseFile(tsPath, WORKSPACE);

parsedFiles.push(tsPath);

const baseName = path.basename(tsPath, '.d.ts');
console.log(baseName);

let interfaces: string[] = [];
let functions: string[] = [];
let classes: string[] = [];
let variables: string[] = [];

function handleDeclatation(symbol: Declaration) {
if (symbol instanceof InterfaceDeclaration) {
interfaces.push(`### ${symbol.name}`, ``, `#### Properties`, ``);
for (const property of symbol.properties) {
interfaces.push(`- ${property.name}${property.isOptional ? `?` : ``}: ${linkType(property.type)}`);
}
interfaces.push(``);

} else if (symbol instanceof ClassDeclaration) {
classes.push(`### ${symbol.name}`, ``);

const staticProps = symbol.properties.filter(p => p.isStatic && p.visibility !== PRIVATE);
const staticMethods = symbol.methods.filter(m => m.isStatic && m.visibility !== PRIVATE);

if (staticProps.length > 0 || staticMethods.length > 0) {
classes.push(`#### Static`, ``);
for (const prop of staticProps) {
classes.push(`- ${prop.name}: ${linkType(prop.type)}`);
}
for (const prop of staticMethods) {
classes.push(`- ${prop.isAsync ? `async ` : ``}**${prop.name}**(${prop.parameters.map(p => `${p.name}: ${linkType(p.type)}`).join(', ')}): ${linkType(prop.type)}`);
}
classes.push(``);
}

const ctor = symbol.ctor;

if (ctor) {
classes.push(`#### Constructor`, ``);
classes.push(`- ${symbol.name}(${ctor.parameters.map(p => `${p.name}: ${linkType(p.type)}`).join(', ')}): ${symbol.name}`);
classes.push(``);
}

const instanceProps = symbol.properties.filter(p => !p.isStatic && p.visibility !== PRIVATE);
const instanceMethods = symbol.methods.filter(m => !m.isStatic && m.visibility !== PRIVATE);

if (instanceProps.length > 0) {
classes.push(`#### Properties`, ``);
for (const prop of instanceProps) {
classes.push(`* ${prop.name}${prop.isOptional ? `?` : ``}: ${linkType(prop.type)}`);
}
classes.push(``);
}

if (instanceMethods.length > 0) {
classes.push(`#### Methods`, ``);
for (const prop of instanceMethods) {
classes.push(`* ${prop.isAsync ? `async ` : ``}**${prop.name}**(${prop.parameters.map(p => `${p.name}: ${linkType(p.type)}`).join(', ')}): ${prop.type}`);
}
classes.push(``);
}

} else if (symbol instanceof FunctionDeclaration) {
functions.push(`### ${symbol.name}()`, ``);
functions.push(`- ${symbol.name}(${symbol.parameters.map(p => `${p.name}: ${linkType(p.type)}`).join(', ')}): ${linkType(symbol.type)}`);
functions.push(``);

} else if (symbol instanceof VariableDeclaration) {
variables.push(`- ${symbol.name}: ${linkType(symbol.type)}`);

} else if (symbol instanceof DefaultDeclaration) {

} else {
console.log(symbol);
}
}

if (parsed.declarations.length > 0) {
for (const variable of parsed.declarations) {
handleDeclatation(variable);
}

if (variables.length > 0) {
variables = [`### Variables`, ``, ...variables, ``];
}

if (classes.length > 0 || interfaces.length > 0 || functions.length > 0 || variables.length > 0) {
sections[baseName] = [...classes, ...functions, ...variables, ...interfaces, ``].join('\n');
}
}

const imports = parsed.imports;
let subLines: string[] = [];
for (const importDetail of imports) {
if (importDetail.libraryName.startsWith('.')) {
const dPath = path.join(parsed.filePath, `..`, importDetail.libraryName + `.d.ts`);
if (await exists(dPath)) {
// console.log(`Found ${dPath}`);
await parseFile(dPath);
}
}
}
}

await parseFile('node_modules/@halcyontech/vscode-ibmi-types/typings.d.ts');

const topMost = [`Instance`, `IBMi`, `IBMiContent`, `component`, `manager`, `Filter`];
const hidden = [`CustomUI`, `Storage`, `Configuration`];

let allLines: string[] = [];

if (await exists(HEADER_MDX)) {
const header = await readFile(HEADER_MDX, 'utf8');
allLines.push(header, ``, `---`, ``);
}

for (const file of topMost) {
if (sections[file]) {
allLines.push(`## ${file}`, sections[file]);
}
}

for (const file of Object.keys(sections)) {
if (!topMost.includes(file) && !hidden.includes(file)) {
allLines.push(`## ${file}`, sections[file], ``, `---`, ``);
}
}

Object.keys(sections).map(k => sections[k]).flat();

console.log(Object.keys(sections));

await writeFile('src/content/docs/dev/api.md', allLines.join('\n'));
68 changes: 68 additions & 0 deletions src/api/header.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: API
sidebar:
order: 3
---

It is possible to write VS Code extensions that are based on Code for IBM i. That means your extension can use the connection that the user creates in your extension. This is not an extension tutorial, but an intro on how to access the APIs available within Code for IBM i.

For example, you might be a vendor that produces lists or HTML that you'd like to be accessible from within Visual Studio Code.

# Exports

As well as the basic VS Code command API, you can get access to the Code for IBM i API with the VS Code `getExtension` API.

```ts
const { instance } = vscode.extensions.getExtension(`halcyontechltd.code-for-ibmi`).exports;
```

## Typings

We provide TS type definitions to make using the Code for IBM i API easier. They can be installed via `npm`:

```bash title="terminal"
npm i @halcyontech/vscode-ibmi-types
```

It can then be imported and used in combination with `getExtension`:

```ts
import type { CodeForIBMi } from '@halcyontech/vscode-ibmi-types';

//...

const ext = vscode.extensions.getExtension<CodeForIBMi>('halcyontechltd.code-for-ibmi');
```


**As Code for IBM i updates, the API may change. It is recommended you always keep the types packaged updated as the extension updates, incase the API interfaces change. We plan to make the VS Code command API interfaces stable so they will not break as often after they have been released.**

## Example import

This example can be used as a simple way to access the Code for IBM i instance.

```ts
import { CodeForIBMi } from "@halcyontech/vscode-ibmi-types";
import Instance from "@halcyontech/vscode-ibmi-types/api/Instance";
import { Extension, extensions } from "vscode";

let baseExtension: Extension<CodeForIBMi>|undefined;

/**
* This should be used on your extension activation.
*/
export function loadBase(): CodeForIBMi|undefined {
if (!baseExtension) {
baseExtension = (extensions ? extensions.getExtension(`halcyontechltd.code-for-ibmi`) : undefined);
}

return (baseExtension && baseExtension.isActive && baseExtension.exports ? baseExtension.exports : undefined);
}

/**
* Used when you want to fetch the extension 'instance' (the connection)
*/
export function getInstance(): Instance|undefined {
return (baseExtension && baseExtension.isActive && baseExtension.exports ? baseExtension.exports.instance : undefined);
}
```
Loading

0 comments on commit 49f0d95

Please sign in to comment.