Skip to content
This repository has been archived by the owner on Nov 4, 2023. It is now read-only.

Commit

Permalink
fix: class processing
Browse files Browse the repository at this point in the history
  • Loading branch information
cdaringe committed Jan 17, 2021
1 parent eb534ef commit 94e9803
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 50 deletions.
22 changes: 18 additions & 4 deletions src/generate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Bluebird from "bluebird";
import { promises as fs } from "fs";
import { Cls, printer } from "./ir/ir";
import { scrapeClassPage } from "./scrape/classes";
import { scrapeConcepts } from "./scrape/concepts";
import { scrapeDefines } from "./scrape/defines";
import { ofUrl } from "./scrape/dom";
import { printer } from "./ir/ir";
import { promises as fs } from "fs";

export const produce = async ({
urls,
Expand All @@ -14,8 +14,21 @@ export const produce = async ({
};
}) => {
const classLinks = await enumerateClassUrlsFromUrl(urls.apiRoot);
const classSchemas = await getClassesFromUrl(classLinks).then((pageSchemas) =>
pageSchemas.flat()
const nonDedupedclassSchemas = await getClassesFromUrl(
classLinks
).then((pageSchemas) => pageSchemas.flat());
const classSchemas = Object.values(
nonDedupedclassSchemas.reduce((byName, curr) => {
if (byName[curr.name]) {
if (JSON.stringify(curr) !== JSON.stringify(byName[curr.name])) {
throw new Error(
`class ${curr.name} varies depending from where it is parsed! :|`
);
}
}
byName[curr.name] = curr;
return byName;
}, {} as Record<string, Cls>)
);
const [defines, concepts] = await Promise.all([
getDefinesFromUrl(urls.apiRoot),
Expand All @@ -25,6 +38,7 @@ export const produce = async ({
`/** @noSelfInFile */`,
printer.print(defines),
...concepts.map(printer.print),
...classSchemas.map(printer.print),
].join("\n");
// const definitions = [
// ...classSchemas.map(({ schema, className }) => ({
Expand Down
12 changes: 7 additions & 5 deletions src/ir/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export type Type =
| Any
| And
| Collection
| Cls
| Field
| Function
| Intf
Expand Down Expand Up @@ -128,10 +129,12 @@ export type Property = {
} & IRCommon<"property">;
export const property = factory<Property>("property");

export type Struct = {
type StructLike = {
name: string;
members: ClassMember[];
} & IRCommon<"struct">;
members: Property[];
};

export type Struct = StructLike & IRCommon<"struct">;

export const struct = factory<Struct>("struct");

Expand All @@ -142,10 +145,9 @@ export type Intf = {
} & IRCommon<"interface">;
export const intf = factory<Intf>("interface");

export type ClassMember = Property | Function;
export type Cls = {
inherits?: Sym[];
} & Omit<Struct, "__type"> &
} & StructLike &
IRCommon<"class">;
export const cls = factory<Cls>("class");

Expand Down
20 changes: 20 additions & 0 deletions src/ir/of-lua.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
any,
arr,
bool,
fn,
map,
nil,
num,
optional,
param,
str,
sym,
Type,
Expand Down Expand Up @@ -76,6 +78,24 @@ export const ofLua = (ltype_: string): Type => {
if (unmodeled.includes(ltype)) {
return any({});
}
/**
* @todo
* docs sometimes yield just the word `function` with no type details :/.
* sometimes it does have mediocre type details. let's improve it
*/
if (ltype === "function" || ltype.match(/function\([^)]*\)/)) {
return fn({
name: "fn",
parameters: [
param({
name: "args",
isVariadic: true,
type: any({}),
}),
],
return: any({}),
});
}
if (!definitionTypes.includes(ltype)) {
definitionTypes.push(ltype.trim());
console.warn(`:: Adding type ${ltype}`);
Expand Down
4 changes: 3 additions & 1 deletion src/ir/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const printInner = (t: Type): string => {
.join(" ")} }`;
if (!t.isRoot) return inner;
return `interface ${t.name} \n${inner} `;
case "class":
case "struct":
return `interface ${t.name} { \n ${t.members
.map((member) => {
Expand All @@ -48,8 +49,9 @@ export const printInner = (t: Type): string => {
return `${t.text}`;
case "union":
return `(${t.members.map(print).join(" | ")})`;
case "number":
case "nil":
return "null";
case "number":
case "string":
case "boolean":
case "any":
Expand Down
80 changes: 48 additions & 32 deletions src/scrape/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { asMarkdown, asUrlCorrectedMarkdown } from "../batteries/markdown";
import { PageMeta } from "../interfaces";
import {
any,
ClassMember,
Cls,
cls,
field,
Expand All @@ -16,6 +15,7 @@ import {
optional,
param,
Param,
Property,
property,
sym,
Type,
Expand Down Expand Up @@ -86,7 +86,8 @@ export const getParamFromRow = (el: IElement): Param => {
const [typeStr, description] = r1.split(":").map(trim);
return param({
description,
name,
// handle TS keyword edge case - keywords in typings are not permitted as key name
name: name === "function" ? "fn" : name,
type: ofLua(typeStr),
});
} else {
Expand All @@ -113,8 +114,15 @@ export const parseParam = (el: IElement, i: number): Param => {
nameAndOptional = "";
}
let [name] = nameAndOptional.split("(optional)").map(trim);
// case: "Table with the following fields: <desc>\n\n<ul>..."
if (name.startsWith("Table with")) {
name = "tbl";
}
const isOptional = name !== nameAndOptional;
const members = Array.from(fieldsUl.children).reduce((acc, el, j) => {
// @todo consider supporting `optional?` properties for "Additional entity-specific parameters" entries
// these entities do not have great narrowing properties, ready to be parsed from the HTML
if (el.textContent.startsWith("Additional")) return acc;
const prm = parseParam(el, j);
acc[prm.name] = field({ type: prm.type, description: prm.description });
return acc;
Expand All @@ -129,7 +137,10 @@ export const parseParam = (el: IElement, i: number): Param => {
return getParamFromRow(el);
};

export const parseImplEl = (implEl: IElement, description: string) => {
export const parseMemberFnFromImplEl = (
implEl: IElement,
description: string
) => {
const id = implEl.id;
if (!id) throw new Error("unable to find id");
const name = id.split(".")[id.split(".").length - 1];
Expand Down Expand Up @@ -187,16 +198,22 @@ export const parseImplEl = (implEl: IElement, description: string) => {
}
const returnType = ofLua(returnTypeText.trim());
const isReturnDescriptionPermittingNil = returnDescription.match("or nil");
return fn({
description,
return property({
name,
parameters: params,
return: isReturningNil
? nil({ description: "" })
: isReturnDescriptionPermittingNil
? union({ members: [returnType, nil({ description: "" })], description })
: returnType,
returnDescription,
description,
type: fn({
name,
parameters: params,
return: isReturningNil
? nil({ description: "" })
: isReturnDescriptionPermittingNil
? union({
members: [returnType, nil({ description: "" })],
description,
})
: returnType,
returnDescription,
}),
});
};

Expand All @@ -213,19 +230,22 @@ const parseMemberFunction = (
const implAnchor = query(el, "a", "missing impl anchor");
// fast parse when there are no args
if (el.textContent.includes("()")) {
return fn({
description,
return property({
name: implAnchor.textContent,
parameters: [],
return: el.textContent.includes("→")
? ofLua(
query(
el,
".return-type",
"unable to find return type"
).textContent.trim()
)
: nil({ description: "" }),
description,
type: fn({
name: implAnchor.textContent,
parameters: [],
return: el.textContent.includes("→")
? ofLua(
query(
el,
".return-type",
"unable to find return type"
).textContent.trim()
)
: nil({ description: "" }),
}),
});
}
// slow parse when no arg case fails
Expand All @@ -235,7 +255,7 @@ const parseMemberFunction = (
if (!implEl) {
throw new Error("missing impl el");
}
return parseImplEl(implEl, description);
return parseMemberFnFromImplEl(implEl, description);
};

const parseMemberAttr = (
Expand Down Expand Up @@ -271,7 +291,7 @@ const parseMemberRow = (
document: Document,
row: IElement,
pageMeta: PageMeta
): ClassMember => {
): Property => {
const [signatureEl, descriptionEl] = row.children;
if (!signatureEl) throw new Error("unable to find member signature el");
const description = asUrlCorrectedMarkdown(
Expand Down Expand Up @@ -314,12 +334,8 @@ export const ofEl = (document: Document, el: IElement, pageMeta: PageMeta) => {
it.classList.contains("brief-members")
);
if (!membersRootEl) throw new Error(`unable to find class member root el`);

const members = parseMemberRows(
document,
queryAll(membersRootEl, "tr"),
pageMeta
);
const memberRows = queryAll(membersRootEl, "tr");
const members = parseMemberRows(document, memberRows, pageMeta);
return cls({
name,
description: getDescription(
Expand Down
4 changes: 0 additions & 4 deletions src/scrape/concepts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ export const scrapeConcept = (el: IElement) => {
name,
description,
members: fields.map((field) => {
if (testIsType(field.type, fn)) {
// @todo - did we just throw away a bunch of info? do we actually expect this case?
return field.type;
}
return property(field);
}),
});
Expand Down
8 changes: 4 additions & 4 deletions test/class-method.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import ava, { TestInterface } from "ava";
import { parseImplEl } from "../src/scrape/classes";
import { parseMemberFnFromImplEl } from "../src/scrape/classes";
import { getFixture } from "./fixture/snippets";

const test = ava as TestInterface<{}>;

test("parse method params - set_controller", async (t) => {
const { el } = await getFixture("class-params-table-arg-set_controller.html");
const parsed = parseImplEl(el, "test-description");
const parsed = parseMemberFnFromImplEl(el, "test-description");
t.snapshot(parsed);
});

test("parse method params - create_local_flying_text", async (t) => {
const { el } = await getFixture(
"class-params-table-arg-create_local_flying_text.html"
);
const parsed = parseImplEl(el, "test-description");
const parsed = parseMemberFnFromImplEl(el, "test-description");
t.snapshot(parsed);
});

test("parse method params - set_allows_action", async (t) => {
const { el } = await getFixture(
"class-params-mismatched-params-set_allows_action.html"
);
const parsed = parseImplEl(el, "test-description");
const parsed = parseMemberFnFromImplEl(el, "test-description");
t.snapshot(parsed);
});

0 comments on commit 94e9803

Please sign in to comment.