diff --git a/.gitignore b/.gitignore index e6f5d988..b1b12e47 100644 --- a/.gitignore +++ b/.gitignore @@ -10,9 +10,8 @@ native/native_main native_main.spec .wine-pyinstaller tags -compiler/*.js -compiler/**/*.js .*.generated.ts +.*.generated.json .tmp/ .DS_Store .build_cache/ diff --git a/compiler/gen_metadata.ts b/compiler/gen_metadata.ts deleted file mode 100644 index ac2e6169..00000000 --- a/compiler/gen_metadata.ts +++ /dev/null @@ -1,274 +0,0 @@ -import * as ts from "typescript" -import * as fs from "fs" -import * as commandLineArgs from "command-line-args" -import * as AllTypes from "./types/AllTypes" -import * as AllMetadata from "./metadata/AllMetadata" - -export function toSimpleType(typeNode) { - switch (typeNode.kind) { - case ts.SyntaxKind.VoidKeyword: - return new AllTypes.VoidType() - // IndexedAccessTypes are things like `fn(x: T, y: Class[T])` - // This doesn't seem to be easy to deal with so let's kludge it for now - case ts.SyntaxKind.IndexedAccessType: - // Unknown is just like any, but slightly stricter - case ts.SyntaxKind.UnknownKeyword: - case ts.SyntaxKind.AnyKeyword: - return new AllTypes.AnyType() - case ts.SyntaxKind.BooleanKeyword: - return new AllTypes.BooleanType() - case ts.SyntaxKind.NumberKeyword: - return new AllTypes.NumberType() - case ts.SyntaxKind.ObjectKeyword: - return new AllTypes.ObjectType() - case ts.SyntaxKind.StringKeyword: - return new AllTypes.StringType() - case ts.SyntaxKind.Parameter: - let n = toSimpleType(typeNode.type) - n.name = typeNode.name.original.escapedText - n.isDotDotDot = !!typeNode.dotDotDotToken - n.isQuestion = !!typeNode.questionToken - return n - case ts.SyntaxKind.TypeReference: - if (!typeNode.typeArguments) { - // If there are no typeArguments, this is not a parametric type and we can return the type directly - try { - return toSimpleType( - typeNode.typeName.symbol.declarations[0].type, - ) - } catch (e) { - // Fall back to what you'd do with typeArguments - } - } - let args = typeNode.typeArguments - ? typeNode.typeArguments.map(t => - toSimpleType(typeNode.typeArguments[0]), - ) - : [] - return new AllTypes.TypeReferenceType( - typeNode.typeName.escapedText, - args, - ) - case ts.SyntaxKind.FunctionType: - // generics = (typeNode.typeParameters || []).map(p => new AllTypes.SimpleType(p)) - return new AllTypes.FunctionType( - typeNode.parameters.map(p => toSimpleType(p)), - toSimpleType(typeNode.type), - ) - case ts.SyntaxKind.TypeLiteral: - let members = typeNode.members - .map(member => { - if (member.kind == ts.SyntaxKind.IndexSignature) { - // Something like this: { [str: string]: string[] } - return ["", toSimpleType(member.type)] - } - // Very fun feature: when you have an object literal with >20 members, typescript will decide to replace some of them with a "... X more ..." node that obviously doesn't have a corresponding symbol, hence this check and the filter after the map - if (member.name.symbol) - return [ - member.name.symbol.escapedName, - toSimpleType(member.type), - ] - }) - .filter(m => m) - return new AllTypes.ObjectType(new Map(members)) - case ts.SyntaxKind.ArrayType: - return new AllTypes.ArrayType(toSimpleType(typeNode.elementType)) - case ts.SyntaxKind.TupleType: - return new AllTypes.TupleType( - typeNode.elementTypes.map(t => toSimpleType(t)), - ) - case ts.SyntaxKind.UnionType: - return new AllTypes.UnionType( - typeNode.types.map(t => toSimpleType(t)), - ) - break - case ts.SyntaxKind.LiteralType: - return new AllTypes.LiteralTypeType(typeNode.literal.text) - break - default: - console.log(typeNode) - throw new Error(`Unhandled kind (${typeNode.kind}) for ${typeNode}`) - } -} - -/** True if node is visible outside its file, false otherwise */ -function isNodeExported(node: ts.Node): boolean { - return ( - (ts.getCombinedModifierFlags(node) & - ts.ModifierFlags.Export) !== - 0 || - (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) - ) -} - -/** True if node is marked as @hidden in its documentation */ -function isNodeHidden(sourceFile, node): boolean { - return ( - sourceFile && - node.jsDoc && - !!node.jsDoc.find( - doc => - sourceFile.text.slice(doc.pos, doc.end).search("@hidden") != -1, - ) - ) -} - -function visit( - checker: any, - sourceFile: any, - file: AllMetadata.FileMetadata, - node: any, -) { - let symbol = checker.getSymbolAtLocation(node.name) - if (symbol && isNodeExported(node)) { - let nodeName = symbol.escapedName - - switch (node.kind) { - case ts.SyntaxKind.FunctionDeclaration: - // Grab the doc, default to empty string - let doc = - ts.displayPartsToString(symbol.getDocumentationComment()) || - "" - // Grab the type - let ttype = checker.getTypeOfSymbolAtLocation( - symbol, - symbol.valueDeclaration!, - ) - // If the function has a type, try to convert it, if it doesn't, default to any - let t = ttype - ? toSimpleType(checker.typeToTypeNode(ttype)) - : new AllTypes.AnyType() - file.setFunction( - nodeName, - new AllMetadata.SymbolMetadata( - doc, - t, - isNodeHidden(sourceFile, node), - ), - ) - return - - case ts.SyntaxKind.ClassDeclaration: - let clazz = file.getClass(nodeName) - if (!clazz) { - clazz = new AllMetadata.ClassMetadata() - file.setClass(nodeName, clazz) - } - symbol.members.forEach((sym, name, map) => { - // Can't get doc/type from these special functions - // Or at least, it requires work that might not be needed for now - if (["__constructor", "get", "set"].includes(name)) return - - // Grab the doc, default to empty string - let doc = - ts.displayPartsToString( - sym.getDocumentationComment(), - ) || "" - // Grab the type - let ttype = checker.getTypeOfSymbolAtLocation( - sym, - sym.valueDeclaration!, - ) - // If the function has a type, try to convert it, if it doesn't, default to any - let t = ttype - ? toSimpleType(checker.typeToTypeNode(ttype)) - : new AllTypes.AnyType() - clazz.setMember( - name, - new AllMetadata.SymbolMetadata( - doc, - t, - isNodeHidden(sourceFile, node), - ), - ) - }) - return - - // Other declaration syntaxkinds: - // case ts.SyntaxKind.VariableDeclaration: - // case ts.SyntaxKind.VariableDeclarationList: - // case ts.SyntaxKind.PropertyDeclaration: - // case ts.SyntaxKind.MethodDeclaration: - // case ts.SyntaxKind.EndOfDeclarationMarker: - // case ts.SyntaxKind.MergeDeclarationMarker: - // case ts.SyntaxKind.MissingDeclaration: - // case ts.SyntaxKind.ClassExpression: - // case ts.SyntaxKind.InterfaceDeclaration: - // case ts.SyntaxKind.TypeAliasDeclaration: - // case ts.SyntaxKind.EnumDeclaration: - // case ts.SyntaxKind.ModuleDeclaration: - // case ts.SyntaxKind.ImportEqualsDeclaration: - // case ts.SyntaxKind.ImportDeclaration: - // case ts.SyntaxKind.NamespaceExportDeclaration: - // case ts.SyntaxKind.ExportDeclaration: - // case ts.SyntaxKind.Constructor: - } - } - - ts.forEachChild(node, node => visit(checker, sourceFile, file, node)) -} - -function generateMetadata( - out: string, - themedir: string, - fileNames: string[], -): void { - /* Parse Tridactyl */ - let program = ts.createProgram(fileNames, { - target: ts.ScriptTarget.ES5, - module: ts.ModuleKind.CommonJS, - }) - - let metadata = new AllMetadata.ProgramMetadata() - - for (const sourceFile of program.getSourceFiles()) { - let name = (fileNames as any).find(name => - sourceFile.fileName.match(name), - ) - if (name) { - let file = metadata.getFile(name) - if (!file) { - file = new AllMetadata.FileMetadata() - metadata.setFile(name, file) - } - visit(program.getTypeChecker(), sourceFile, file, sourceFile) - } - } - - // We need to specify Type itself because it won't exist in AllTypes.js since it's an interface - let imports = - `import { Type } from "../compiler/types/AllTypes"\n` + - `import {${Object.keys(AllTypes).join( - ", ", - )}} from "../compiler/types/AllTypes"\n` + - `import {${Object.keys(AllMetadata).join( - ", ", - )}} from "../compiler/metadata/AllMetadata"\n` - - let metadataString = - imports + `\nexport let everything = ${metadata.toConstructor()}\n` - - if (themedir) { - metadataString += `\nexport let staticThemes = ${JSON.stringify( - fs.readdirSync(themedir), - )}\n` - } - - // print out the doc - fs.writeFileSync(out, metadataString) - - return -} - -let opts = commandLineArgs([ - { name: "out", type: String }, - { name: "themeDir", type: String }, - { name: "src", type: String, multiple: true, defaultOption: true }, -]) - -if (!opts.out || opts.src.length < 1) - throw new Error( - "Argument syntax: --out outfile [--src] file1.ts [file2.ts ...]", - ) - -generateMetadata(opts.out, opts.themeDir, opts.src) diff --git a/compiler/metadata/AllMetadata.ts b/compiler/metadata/AllMetadata.ts deleted file mode 100644 index dc8d931e..00000000 --- a/compiler/metadata/AllMetadata.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { SymbolMetadata } from "./SymbolMetadata" -export { ClassMetadata } from "./ClassMetadata" -export { FileMetadata } from "./FileMetadata" -export { ProgramMetadata } from "./ProgramMetadata" diff --git a/compiler/metadata/ClassMetadata.ts b/compiler/metadata/ClassMetadata.ts deleted file mode 100644 index 1c25967b..00000000 --- a/compiler/metadata/ClassMetadata.ts +++ /dev/null @@ -1,33 +0,0 @@ - -import { SymbolMetadata } from "./SymbolMetadata" - -export class ClassMetadata { - constructor( - public members: Map = new Map< - string, - SymbolMetadata - >(), - ) {} - - public setMember(name: string, s: SymbolMetadata) { - this.members.set(name, s) - } - - public getMember(name: string) { - return this.members.get(name) - } - - public getMembers() { - return this.members.keys() - } - - public toConstructor() { - return ( - `new ClassMetadata(new Map([` + - Array.from(this.members.entries()) - .map(([n, m]) => `[${JSON.stringify(n)}, ${m.toConstructor()}]`) - .join(",\n") + - `]))` - ) - } -} diff --git a/compiler/metadata/FileMetadata.ts b/compiler/metadata/FileMetadata.ts deleted file mode 100644 index a1c61185..00000000 --- a/compiler/metadata/FileMetadata.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ClassMetadata } from "./ClassMetadata" -import { SymbolMetadata } from "./SymbolMetadata" - -export class FileMetadata { - constructor( - public classes: Map = new Map< - string, - ClassMetadata - >(), - public functions: Map = new Map< - string, - SymbolMetadata - >(), - ) {} - - public setClass(name: string, c: ClassMetadata) { - this.classes.set(name, c) - } - - public getClass(name: string) { - return this.classes.get(name) - } - - public getClasses() { - return Array.from(this.classes.keys()) - } - - public setFunction(name: string, f: SymbolMetadata) { - this.functions.set(name, f) - } - - public getFunction(name: string) { - return this.functions.get(name) - } - - public getFunctions() { - return Array.from(this.functions.entries()) - } - - public getFunctionNames() { - return Array.from(this.functions.keys()) - } - - public toConstructor() { - return ( - `new FileMetadata(new Map([` + - Array.from(this.classes.entries()) - .map(([n, c]) => `[${JSON.stringify(n)}, ${c.toConstructor()}]`) - .join(",\n") + - `]), new Map([` + - Array.from(this.functions.entries()) - .map(([n, f]) => `[${JSON.stringify(n)}, ${f.toConstructor()}]`) - .join(",\n") + - `]))` - ) - } -} diff --git a/compiler/metadata/ProgramMetadata.ts b/compiler/metadata/ProgramMetadata.ts deleted file mode 100644 index 2b8dfbdc..00000000 --- a/compiler/metadata/ProgramMetadata.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { FileMetadata } from "./FileMetadata" - -export class ProgramMetadata { - constructor( - public files: Map = new Map< - string, - FileMetadata - >(), - ) {} - - public setFile(name: string, file: FileMetadata) { - this.files.set(name, file) - } - - public getFile(name: string) { - return this.files.get(name) - } - - public toConstructor() { - return ( - `new ProgramMetadata(new Map([` + - Array.from(this.files.entries()) - .map(([n, f]) => `[${JSON.stringify(n)}, ${f.toConstructor()}]`) - .join(",\n") + - `]))` - ) - } -} diff --git a/compiler/metadata/SymbolMetadata.ts b/compiler/metadata/SymbolMetadata.ts deleted file mode 100644 index 9a93e42d..00000000 --- a/compiler/metadata/SymbolMetadata.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Type } from "../types/AllTypes" - -export class SymbolMetadata { - constructor(public doc: string, public type: Type, public hidden = false) {} - - public toConstructor() { - return `new SymbolMetadata(${JSON.stringify( - this.doc, - )}, ${this.type.toConstructor()}, ${this.hidden})` - } -} diff --git a/compiler/types/AllTypes.ts b/compiler/types/AllTypes.ts deleted file mode 100644 index d35d1fbc..00000000 --- a/compiler/types/AllTypes.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { Type } from "./Type" -export { AnyType } from "./AnyType" -export { BooleanType } from "./BooleanType" -export { FunctionType } from "./FunctionType" -export { NumberType } from "./NumberType" -export { ObjectType } from "./ObjectType" -export { StringType } from "./StringType" -export { TypeReferenceType } from "./TypeReferenceType" -export { VoidType } from "./VoidType" -export { ArrayType } from "./ArrayType" -export { LiteralTypeType } from "./LiteralTypeType" -export { TupleType } from "./TupleType" -export { UnionType } from "./UnionType" diff --git a/compiler/types/AnyType.ts b/compiler/types/AnyType.ts deleted file mode 100644 index 769d704f..00000000 --- a/compiler/types/AnyType.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Type } from "./Type" - -export class AnyType implements Type { - public kind = "any" - - constructor(public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new AnyType(${!this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return this.kind - } - - public convert(argument) { - return argument - } -} diff --git a/compiler/types/ArrayType.ts b/compiler/types/ArrayType.ts deleted file mode 100644 index a44d7b71..00000000 --- a/compiler/types/ArrayType.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Type } from "./Type" - -export class ArrayType implements Type { - public kind = "array" - - constructor(public elemType: Type, public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new ArrayType(${this.elemType.toConstructor()}, ${this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return `${this.elemType.toString()}[]` - } - - public convert(argument) { - if (!Array.isArray(argument)) { - try { - argument = JSON.parse(argument) - } catch (e) { - throw new Error(`Can't convert ${argument} to array:`) - } - if (!Array.isArray(argument)) { - throw new Error(`Can't convert ${argument} to array:`) - } - } - return argument.map(v => this.elemType.convert(v)) - } -} diff --git a/compiler/types/BooleanType.ts b/compiler/types/BooleanType.ts deleted file mode 100644 index 5de34eb7..00000000 --- a/compiler/types/BooleanType.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Type } from "./Type" - -export class BooleanType implements Type { - public kind = "boolean" - - constructor(public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new BooleanType(${this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return this.kind - } - - public convert(argument) { - if (argument === "true") { - return true - } else if (argument === "false") { - return false - } - throw new Error("Can't convert ${argument} to boolean") - } -} diff --git a/compiler/types/FunctionType.ts b/compiler/types/FunctionType.ts deleted file mode 100644 index 274f701e..00000000 --- a/compiler/types/FunctionType.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Type } from "./Type" - -export class FunctionType implements Type { - public kind = "function" - - constructor(public args: Type[], public ret: Type, public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return ( - `new FunctionType([` + - // Convert every argument type to its string constructor representation - this.args.map(cur => cur.toConstructor()) + - `], ${this.ret.toConstructor()}, ${this.isDotDotDot}, ${this.isQuestion})` - ) - } - - public toString() { - return `(${this.args.map(a => a.toString()).join(", ")}) => ${this.ret.toString()}` - } - - public convert(argument) { - // Possible strategies: - // - eval() - // - window[argument] - // - tri.excmds[argument] - throw new Error(`Conversion to function not implemented: ${argument}`) - } -} diff --git a/compiler/types/LiteralTypeType.ts b/compiler/types/LiteralTypeType.ts deleted file mode 100644 index 73790c33..00000000 --- a/compiler/types/LiteralTypeType.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Type } from "./Type" - -export class LiteralTypeType implements Type { - public kind = "LiteralType" - - constructor(public value: string, public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new LiteralTypeType(${JSON.stringify(this.value)}, ${this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return JSON.stringify(this.value) - } - - public convert(argument) { - if (argument === this.value) { - return argument - } - throw new Error( - `Argument does not match expected value (${ - this.value - }): ${argument}`, - ) - } -} diff --git a/compiler/types/NumberType.ts b/compiler/types/NumberType.ts deleted file mode 100644 index 575afe89..00000000 --- a/compiler/types/NumberType.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Type } from "./Type" - -export class NumberType implements Type { - public kind = "number" - - public constructor(public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new NumberType(${this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return this.kind - } - - public convert(argument) { - const n = parseFloat(argument) - if (!Number.isNaN(n)) { - return n - } - throw new Error(`Can't convert to number: ${argument}`) - } -} diff --git a/compiler/types/ObjectType.ts b/compiler/types/ObjectType.ts deleted file mode 100644 index 1695a8c5..00000000 --- a/compiler/types/ObjectType.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Type } from "./Type" - -export class ObjectType implements Type { - public kind = "object" - - // Note: a map that has an empty key ("") uses the corresponding type as default type - constructor(public members: Map = new Map(), public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new ObjectType(new Map([` + - Array.from(this.members.entries()).map(([n, m]) => `[${JSON.stringify(n)}, ${m.toConstructor()}]`) - .join(", ") + - `]), ${this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return this.kind - } - - public convertMember(memberName: string[], memberValue: string) { - let type = this.members.get(memberName[0]) - if (!type) { - // No type, try to get the default type - type = this.members.get("") - if (!type) { - // No info for this member and no default type, anything goes - return memberValue - } - } - if (type.kind === "object") { - return (type as ObjectType).convertMember(memberName.slice(1), memberValue) - } - return type.convert(memberValue) - } - - public convert(argument) { - try { - return JSON.parse(argument) - } catch (e) { - throw new Error(`Can't convert to object: ${argument}`) - } - } -} diff --git a/compiler/types/StringType.ts b/compiler/types/StringType.ts deleted file mode 100644 index 5fce51e5..00000000 --- a/compiler/types/StringType.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Type } from "./Type" - -export class StringType implements Type { - public kind = "string" - - constructor(public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new StringType(${this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return this.kind - } - - public convert(argument) { - if (typeof argument === "string") { - return argument - } - throw new Error(`Can't convert to string: ${argument}`) - } -} diff --git a/compiler/types/TupleType.ts b/compiler/types/TupleType.ts deleted file mode 100644 index 29f6096f..00000000 --- a/compiler/types/TupleType.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Type } from "./Type" - -export class TupleType implements Type { - public kind = "tuple" - - constructor(public elemTypes: Type[], public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return ( - `new TupleType([` + - // Convert every element type to its constructor representation - this.elemTypes.map(cur => cur.toConstructor()).join(",\n") + - `], ${this.isDotDotDot}, ${this.isQuestion})` - ) - } - - public toString() { - return `[${this.elemTypes.map(e => e.toString()).join(", ")}]` - } - - public convert(argument) { - if (!Array.isArray(argument)) { - try { - argument = JSON.parse(argument) - } catch (e) { - throw new Error(`Can't convert to tuple: ${argument}`) - } - if (!Array.isArray(argument)) { - throw new Error(`Can't convert to tuple: ${argument}`) - } - } - if (argument.length !== this.elemTypes.length) { - throw new Error( - `Error converting tuple: number of elements and type mismatch ${argument}`, - ) - } - return argument.map((v, i) => this.elemTypes[i].convert(v)) - } -} diff --git a/compiler/types/Type.ts b/compiler/types/Type.ts deleted file mode 100644 index ecfa7792..00000000 --- a/compiler/types/Type.ts +++ /dev/null @@ -1,12 +0,0 @@ - -export interface Type { - // Only available on argument types - name?: string - isDotDotDot?: boolean - isQuestion?: boolean - // available everywhere - kind: string - toConstructor(): string - toString(): string - convert(argument: string): any -} diff --git a/compiler/types/TypeReferenceType.ts b/compiler/types/TypeReferenceType.ts deleted file mode 100644 index 12bc6671..00000000 --- a/compiler/types/TypeReferenceType.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Type } from "./Type" - -export class TypeReferenceType implements Type { - public constructor(public kind: string, public args: Type[], public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return ( - `new TypeReferenceType(${JSON.stringify(this.kind)}, [` + - // Turn every type argument into its constructor representation - this.args.map(cur => cur.toConstructor()).join(",\n") + - `], ${this.isDotDotDot}, ${this.isQuestion})` - ) - } - - public toString() { - return `${this.kind}<${this.args.map(a => a.toString()).join(", ")}>` - } - - public convert(argument) { - throw new Error("Conversion of simple type references not implemented.") - } -} diff --git a/compiler/types/UnionType.ts b/compiler/types/UnionType.ts deleted file mode 100644 index cce7d27b..00000000 --- a/compiler/types/UnionType.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Type } from "./Type" - -export class UnionType implements Type { - public kind = "union" - - constructor(public types: Type[], public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return ( - `new UnionType([` + - // Convert every type to its string constructor representation - this.types.map(cur => cur.toConstructor()).join(",\n") + - `], ${this.isDotDotDot}, ${this.isQuestion})` - ) - } - - public toString() { - return this.types.map(t => t.toString()).join(" | ") - } - - public convert(argument) { - for (const t of this.types) { - try { - return t.convert(argument) - } catch (e) {} - } - throw new Error(`Can't convert "${argument}" to any of: ${this.types}`) - } -} diff --git a/compiler/types/VoidType.ts b/compiler/types/VoidType.ts deleted file mode 100644 index 6d03a1da..00000000 --- a/compiler/types/VoidType.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Type } from "./Type" - -export class VoidType implements Type { - public kind = "void" - - constructor(public isDotDotDot = false, public isQuestion = false) {} - - public toConstructor() { - return `new VoidType(${this.isDotDotDot}, ${this.isQuestion})` - } - - public toString() { - return this.kind - } - - public convert(argument) { - return null - } -} diff --git a/scripts/build.sh b/scripts/build.sh index 3d7b6d4c..da544a03 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -57,13 +57,15 @@ if [ "$QUICK_BUILD" != "1" ]; then "$(yarn bin)/nearleyc" src/grammars/bracketexpr.ne > \ src/grammars/.bracketexpr.generated.ts - # It's important to generate the metadata before the documentation because - # missing imports might break documentation generation on clean builds - "$(yarn bin)/tsc" compiler/gen_metadata.ts -m commonjs --target es2017 \ - && node compiler/gen_metadata.js \ - --out src/.metadata.generated.ts \ - --themeDir src/static/themes \ - src/excmds.ts src/lib/config.ts + # Generate runtime metadata via typedoc. src/lib/metadata.ts loads the + # JSON and exposes the ProgramMetadata/FileMetadata/Type surface used at + # runtime; the .metadata.generated.ts shim below routes the public + # @src/.metadata.generated import path to that loader. + "$(yarn bin)/typedoc" --json src/.metadata.generated.json --mode file \ + --exclude 'src/.excmds_*.generated.ts' --ignoreCompilerErrors \ + src/excmds.ts src/lib/config.ts + node -e "var fs=require('fs');fs.writeFileSync('src/.themes.generated.json',JSON.stringify(fs.readdirSync('src/static/themes')))" + printf 'export * from "./lib/metadata"\n' > src/.metadata.generated.ts scripts/newtab.md.sh scripts/make_tutorial.sh diff --git a/src/completions/Apropos.ts b/src/completions/Apropos.ts index 4989b849..baaaae06 100644 --- a/src/completions/Apropos.ts +++ b/src/completions/Apropos.ts @@ -1,13 +1,24 @@ import * as Completions from "@src/completions" -import * as Metadata from "@src/.metadata.generated" +import { + excmdsFunctions, + defaultConfigMembers, + getDoc, + memberDoc, +} from "@src/.metadata.generated" import * as aliases from "@src/lib/aliases" import * as config from "@src/lib/config" -class AproposCompletionOption extends Completions.CompletionOptionHTML - implements Completions.CompletionOptionFuse { +class AproposCompletionOption + extends Completions.CompletionOptionHTML + implements Completions.CompletionOptionFuse +{ public fuseKeys = [] - constructor(public name: string, doc: string, flag: string) { + constructor( + public name: string, + doc: string, + flag: string, + ) { super() this.value = `${flag} ${name}` this.html = html` @@ -42,20 +53,13 @@ export class AproposCompletionSource extends Completions.CompletionSourceFuse { return } - const file = Metadata.everything.getFile("src/lib/config.ts") - const default_config = file.getClass("default_config") - const excmds = Metadata.everything.getFile("src/excmds.ts") - const fns = excmds.getFunctions() const settings = config.get() const exaliases = settings.exaliases const bindings = settings.nmaps - if ( - fns === undefined || - exaliases === undefined || - bindings === undefined - ) { + if (exaliases === undefined || bindings === undefined) { return } + const fns = Object.entries(excmdsFunctions) const flags = { "-a": (options, query) => @@ -65,16 +69,14 @@ export class AproposCompletionSource extends Completions.CompletionSourceFuse { ( alias + aliases.expandExstr(alias) + - excmds.getFunction(aliases.expandExstr(alias)) + excmdsFunctions[aliases.expandExstr(alias)] ) .toLowerCase() .includes(query), ) .map(alias => { const cmd = aliases.expandExstr(alias) - const doc = - (excmds.getFunction(cmd) || ({} as any)).doc || - "" + const doc = getDoc(excmdsFunctions[cmd]) return new AproposCompletionOption( alias, `Alias for \`${cmd}\`. ${doc}`, @@ -102,16 +104,14 @@ export class AproposCompletionSource extends Completions.CompletionSourceFuse { "-e": (options, query) => options.concat( fns - .filter( - ([name, fn]) => - !fn.hidden && - (name + fn.doc).toLowerCase().includes(query), + .filter(([name, fn]) => + (name + getDoc(fn)).toLowerCase().includes(query), ) .map( ([name, fn]) => new AproposCompletionOption( name, - `Excmd. ${fn.doc}`, + `Excmd. ${getDoc(fn)}`, "-e", ), ), @@ -120,16 +120,12 @@ export class AproposCompletionSource extends Completions.CompletionSourceFuse { options.concat( Object.keys(settings) .filter(x => - (x + default_config.getMember(x)?.doc) + (x + memberDoc(defaultConfigMembers[x])) .toLowerCase() .includes(query), ) .map(setting => { - const member = default_config.getMember(setting) - let doc = "" - if (member !== undefined) { - doc = member.doc - } + const doc = memberDoc(defaultConfigMembers[setting]) return new AproposCompletionOption( setting, `Setting. ${doc}`, diff --git a/src/completions/Composite.ts b/src/completions/Composite.ts index 1ddd999d..66d8b573 100644 --- a/src/completions/Composite.ts +++ b/src/completions/Composite.ts @@ -1,6 +1,6 @@ import * as Completions from "@src/completions" import * as ExcmdCompletions from "@src/completions/Excmd" -import * as Metadata from "@src/.metadata.generated" +import { excmdsFunctions, getDoc } from "@src/.metadata.generated" import * as config from "@src/lib/config" import * as aliases from "@src/lib/aliases" @@ -65,21 +65,15 @@ export class CompositeCompletionSource extends Completions.CompletionSourceFuse return } - const excmds = Metadata.everything.getFile("src/excmds.ts") - if (!excmds) return - const fns = excmds.getFunctions() - // Add all excmds that start with exstr and that tridactyl has metadata about to completions this.options = this.scoreOptions( - fns - .filter( - ([name, fn]) => !fn.hidden && name.startsWith(end_exstr), - ) + Object.entries(excmdsFunctions) + .filter(([name]) => name.startsWith(end_exstr)) .map( ([name, fn]) => new ExcmdCompletions.ExcmdCompletionOption( name, - fn.doc, + getDoc(fn), ), ), ) @@ -90,12 +84,12 @@ export class CompositeCompletionSource extends Completions.CompletionSourceFuse ) for (const alias of exaliases) { const cmd = aliases.expandExstr(alias) - const fn = excmds.getFunction(cmd) + const fn = excmdsFunctions[cmd] if (fn) { this.options.push( new ExcmdCompletions.ExcmdCompletionOption( alias, - `Alias for \`${cmd}\`. ${fn.doc}`, + `Alias for \`${cmd}\`. ${getDoc(fn)}`, ), ) } else { diff --git a/src/completions/Excmd.ts b/src/completions/Excmd.ts index ada12fa7..4c4ef752 100644 --- a/src/completions/Excmd.ts +++ b/src/completions/Excmd.ts @@ -1,12 +1,17 @@ import * as Completions from "@src/completions" -import * as Metadata from "@src/.metadata.generated" +import { excmdsFunctions, getDoc } from "@src/.metadata.generated" import * as config from "@src/lib/config" import * as aliases from "@src/lib/aliases" -export class ExcmdCompletionOption extends Completions.CompletionOptionHTML - implements Completions.CompletionOptionFuse { +export class ExcmdCompletionOption + extends Completions.CompletionOptionHTML + implements Completions.CompletionOptionFuse +{ public fuseKeys = [] - constructor(public value: string, public documentation: string = "") { + constructor( + public value: string, + public documentation: string = "", + ) { super() this.fuseKeys.push(this.value) @@ -58,15 +63,13 @@ export class ExcmdCompletionSource extends Completions.CompletionSourceFuse { private async updateOptions(exstr = "") { this.lastExstr = exstr - const excmds = Metadata.everything.getFile("src/excmds.ts") - if (!excmds) return - const fns = excmds.getFunctions() - // Add all excmds that start with exstr and that tridactyl has metadata about to completions this.options = this.scoreOptions( - fns - .filter(([name, fn]) => !fn.hidden && name.startsWith(exstr)) - .map(([name, fn]) => new ExcmdCompletionOption(name, fn.doc)), + Object.entries(excmdsFunctions) + .filter(([name]) => name.startsWith(exstr)) + .map( + ([name, fn]) => new ExcmdCompletionOption(name, getDoc(fn)), + ), ) // Also narrow down aliases map to possible completions @@ -80,13 +83,13 @@ export class ExcmdCompletionSource extends Completions.CompletionSourceFuse { for (const alias of Object.keys(exaliases)) { const cmd = aliases.expandExstr(alias, exaliases) - const fn = excmds.getFunction(cmd) + const fn = excmdsFunctions[cmd] if (fn) { this.options.push( new ExcmdCompletionOption( alias, - `Alias for \`${cmd}\`. ${fn.doc}`, + `Alias for \`${cmd}\`. ${getDoc(fn)}`, ), ) } else { @@ -100,12 +103,11 @@ export class ExcmdCompletionSource extends Completions.CompletionSourceFuse { // Add partial matched funcs like: 'conf' ~= 'viewconfig' const seen = new Set(this.options.map(o => o.value)) const partial_options = this.scoreOptions( - fns - .filter( - ([name, fn]) => - !fn.hidden && name.includes(exstr) && !seen.has(name), - ) - .map(([name, fn]) => new ExcmdCompletionOption(name, fn.doc)), + Object.entries(excmdsFunctions) + .filter(([name]) => name.includes(exstr) && !seen.has(name)) + .map( + ([name, fn]) => new ExcmdCompletionOption(name, getDoc(fn)), + ), ) this.options = this.options.concat(partial_options) diff --git a/src/completions/Help.ts b/src/completions/Help.ts index 59d398fe..928b860e 100644 --- a/src/completions/Help.ts +++ b/src/completions/Help.ts @@ -1,13 +1,24 @@ import * as Completions from "@src/completions" -import * as Metadata from "@src/.metadata.generated" +import { + excmdsFunctions, + defaultConfigMembers, + getDoc, + memberDoc, +} from "@src/.metadata.generated" import * as aliases from "@src/lib/aliases" import * as config from "@src/lib/config" -class HelpCompletionOption extends Completions.CompletionOptionHTML - implements Completions.CompletionOptionFuse { +class HelpCompletionOption + extends Completions.CompletionOptionHTML + implements Completions.CompletionOptionFuse +{ public fuseKeys = [] - constructor(public name: string, doc: string, flag: string) { + constructor( + public name: string, + doc: string, + flag: string, + ) { super() this.value = `${flag} ${name}` this.html = html` @@ -42,20 +53,13 @@ export class HelpCompletionSource extends Completions.CompletionSourceFuse { return } - const file = Metadata.everything.getFile("src/lib/config.ts") - const default_config = file.getClass("default_config") - const excmds = Metadata.everything.getFile("src/excmds.ts") - const fns = excmds.getFunctions() const settings = config.get() const exaliases = settings.exaliases const bindings = settings.nmaps - if ( - fns === undefined || - exaliases === undefined || - bindings === undefined - ) { + if (exaliases === undefined || bindings === undefined) { return } + const fns = Object.entries(excmdsFunctions) const flags = { "-a": (options, query) => @@ -64,9 +68,7 @@ export class HelpCompletionSource extends Completions.CompletionSourceFuse { .filter(alias => alias.startsWith(query)) .map(alias => { const cmd = aliases.expandExstr(alias) - const doc = - (excmds.getFunction(cmd) || ({} as any)).doc || - "" + const doc = getDoc(excmdsFunctions[cmd]) return new HelpCompletionOption( alias, `Alias for \`${cmd}\`. ${doc}`, @@ -90,15 +92,12 @@ export class HelpCompletionSource extends Completions.CompletionSourceFuse { "-e": (options, query) => options.concat( fns - .filter( - ([name, fn]) => - !fn.hidden && name.startsWith(query), - ) + .filter(([name]) => name.startsWith(query)) .map( ([name, fn]) => new HelpCompletionOption( name, - `Excmd. ${fn.doc}`, + `Excmd. ${getDoc(fn)}`, "-e", ), ), @@ -108,11 +107,7 @@ export class HelpCompletionSource extends Completions.CompletionSourceFuse { Object.keys(settings) .filter(x => x.startsWith(query)) .map(setting => { - const member = default_config.getMember(setting) - let doc = "" - if (member !== undefined) { - doc = member.doc - } + const doc = memberDoc(defaultConfigMembers[setting]) return new HelpCompletionOption( setting, `Setting. ${doc}`, diff --git a/src/completions/Settings.ts b/src/completions/Settings.ts index 96c77c0c..653bf1f8 100644 --- a/src/completions/Settings.ts +++ b/src/completions/Settings.ts @@ -1,10 +1,16 @@ import * as Completions from "@src/completions" import * as config from "@src/lib/config" -import * as metadata from "@src/.metadata.generated" +import { + defaultConfigMembers, + memberDoc, + memberType, + typeToString, +} from "@src/.metadata.generated" class SettingsCompletionOption extends Completions.CompletionOptionHTML - implements Completions.CompletionOptionFuse { + implements Completions.CompletionOptionFuse +{ public fuseKeys = [] constructor( @@ -66,11 +72,9 @@ export class SettingsCompletionSource extends Completions.CompletionSourceFuse { options += options ? " " : "" - const file = metadata.everything.getFile("src/lib/config.ts") - const default_config = file.getClass("default_config") const settings = config.get() - if (default_config === undefined || settings === undefined) { + if (settings === undefined) { return } @@ -78,18 +82,12 @@ export class SettingsCompletionSource extends Completions.CompletionSourceFuse { .filter(x => x.startsWith(query)) .sort() .map(setting => { - const md = default_config.getMember(setting) - let doc = "" - let type = "" - if (md !== undefined) { - doc = md.doc - type = md.type.toString() - } + const md = defaultConfigMembers[setting] return new SettingsCompletionOption(options + setting, { name: setting, value: JSON.stringify(settings[setting]), - doc, - type, + doc: memberDoc(md), + type: md ? typeToString(memberType(md)) : "", }) }) diff --git a/src/excmds.ts b/src/excmds.ts index 4272673e..75812712 100644 --- a/src/excmds.ts +++ b/src/excmds.ts @@ -86,8 +86,7 @@ import * as Logging from "@src/lib/logging" import { AutoContain } from "@src/lib/autocontainers" import * as CSS from "css" import * as Perf from "@src/perf" -import * as Metadata from "@src/.metadata.generated" -import { ObjectType } from "../compiler/types/ObjectType" +import { staticThemes, defaultConfigMembers, memberType, typeKind, convert, convertMember } from "@src/.metadata.generated" import * as Native from "@src/lib/native" import * as TTS from "@src/lib/text_to_speech" import * as excmd_parser from "@src/parsers/exmode" @@ -544,7 +543,7 @@ export async function colourscheme(...args: string[]) { const themename = option._[0] // If this is a builtin theme, no need to bother with slow stuff - if (!Metadata.staticThemes.includes(themename)) { + if (!staticThemes.includes(themename)) { if (themename.search("\\.") >= 0) throw new Error(`Theme name should not contain any dots! (given name: ${themename}).`) if (url) { if (themename === undefined) throw new Error(`You must provide a theme name!`) @@ -1427,6 +1426,7 @@ export function scrollto(a: number | string, b: number | "x" | "y" = "y") { /** @hidden */ //#content_helper let lineHeight = null + /** Scrolls the document of its first scrollable child element by n lines. * * The height of a line is defined by the site's CSS. If Tridactyl can't get it, it'll default to 22 pixels. @@ -4591,16 +4591,15 @@ function validateSetArgs(key: string, values: string[]) { const target: any[] = key.split(".") let value - const file = Metadata.everything.getFile("src/lib/config.ts") - const default_config = file.getClass("default_config") - const md = default_config.getMember(target[0]) + const md = defaultConfigMembers[target[0]] if (md !== undefined) { const strval = values.join(" ") + const t = memberType(md) // Note: the conversion will throw if strval can't be converted to the right type - if (md.type.kind === "object" && target.length > 1) { - value = (md.type as ObjectType).convertMember(target.slice(1), strval) + if (typeKind(t) === "object" && target.length > 1) { + value = convertMember(t, target.slice(1), strval) } else { - value = md.type.convert(strval) + value = convert(t, strval) } } else { // If we don't have metadata, fall back to the old way diff --git a/src/lib/metadata.ts b/src/lib/metadata.ts new file mode 100644 index 00000000..3315e6b5 --- /dev/null +++ b/src/lib/metadata.ts @@ -0,0 +1,320 @@ +/** + * Runtime metadata over `typedoc --json` output for src/excmds.ts and + * src/lib/config.ts. Exposes plain-object indexes (keyed by symbol name) plus + * a small set of free helpers that operate on raw typedoc nodes. + * + * @hidden symbols are dropped by typedoc itself before they reach this loader, + * so consumers never see them. + */ + +import metadataJson from "../.metadata.generated.json" +import staticThemesJson from "../.themes.generated.json" + +export const staticThemes: string[] = staticThemesJson + +// ============================================================================= +// Indexes — built once at module load by walking the typedoc tree. +// ============================================================================= + +type Node = any + +const fileBuckets: Record< + string, + { functions: Record; classes: Record } +> = { + "src/excmds.ts": { functions: {}, classes: {} }, + "src/lib/config.ts": { functions: {}, classes: {} }, +} + +function inTargetFile(node: Node): string | undefined { + for (const src of node.sources || []) { + if (fileBuckets[src.fileName]) return src.fileName + } + return undefined +} + +function walk(node: Node) { + if (!node || typeof node !== "object") return + if (Array.isArray(node)) { + for (const v of node) walk(v) + return + } + if (node.kindString === "Function") { + const f = inTargetFile(node) + if (f) fileBuckets[f].functions[node.name] = node + } else if (node.kindString === "Class") { + const f = inTargetFile(node) + if (f) fileBuckets[f].classes[node.name] = node + } + if (node.children) walk(node.children) +} +walk(metadataJson) + +export const excmdsFunctions: Record = + fileBuckets["src/excmds.ts"].functions + +const defaultConfig: Node | undefined = + fileBuckets["src/lib/config.ts"].classes["default_config"] + +export const defaultConfigMembers: Record = (() => { + const out: Record = {} + for (const ch of defaultConfig?.children || []) out[ch.name] = ch + return out +})() + +// ============================================================================= +// Doc / type access on function and class-member nodes. +// ============================================================================= + +function readComment(c: Node | undefined): string { + if (!c) return "" + let s: string = c.shortText || "" + if (c.text) s += (s ? "\n\n" : "") + c.text + return s.replace(/\n+$/, "") +} + +export function getDoc(node: Node | undefined): string { + if (!node) return "" + return ( + readComment(node.signatures?.[0]?.comment) || readComment(node.comment) + ) +} + +export function memberDoc(node: Node | undefined): string { + if (!node) return "" + if (node.kindString === "Accessor") { + const sig = (node.getSignature || [])[0] || (node.setSignature || [])[0] + if (sig?.comment) return readComment(sig.comment) + } + return readComment(node.comment) +} + +/** + * Returns a typedoc type node for a class member, synthesising an empty + * reflection for `Object literal` fields (which have no annotated type) so + * downstream helpers can treat them uniformly as objects. + */ +export function memberType(node: Node | undefined): Node | undefined { + if (!node) return undefined + if (node.type) return node.type + if (node.kindString === "Object literal") { + return { type: "reflection", declaration: { children: [] } } + } + if (node.kindString === "Accessor") { + const sig = (node.getSignature || [])[0] || (node.setSignature || [])[0] + return sig?.type + } + return undefined +} + +/** Parameter list for a function node — each entry has `name`, `type`, `flags`. */ +export function paramTypes(fnNode: Node | undefined): Node[] { + return fnNode?.signatures?.[0]?.parameters || [] +} + +// ============================================================================= +// Type coercion + stringification over typedoc type nodes. +// ============================================================================= + +function intrinsicName(t) { + switch (t.name) { + case "string": + case "number": + case "boolean": + case "object": + return t.name + case "void": + case "undefined": + case "never": + return "void" + default: + return "any" + } +} + +/** Normalised kind: "string" | "number" | "boolean" | "object" | "array" | "void" | "any" | ... */ +export function typeKind(t: Node | undefined): string { + if (!t) return "any" + switch (t.type) { + case "intrinsic": + return intrinsicName(t) + case "array": + case "tuple": + case "union": + return t.type + case "reflection": + return t.declaration?.signatures?.length ? "function" : "object" + case "stringLiteral": + case "numberLiteral": + case "booleanLiteral": + return "LiteralType" + case "reference": + return t.name || "reference" + default: + return "any" + } +} + +export function typeToString(t: Node | undefined): string { + if (!t) return "any" + switch (t.type) { + case "intrinsic": + return t.name + case "array": + return `${typeToString(t.elementType)}[]` + case "tuple": + return `[${(t.elements || []).map(typeToString).join(", ")}]` + case "union": + return (t.types || []).map(typeToString).join(" | ") + case "stringLiteral": + case "numberLiteral": + case "booleanLiteral": + return JSON.stringify(t.value) + case "reference": { + const args = (t.typeArguments || []).map(typeToString) + return args.length + ? `${t.name}<${args.join(", ")}>` + : t.name || "reference" + } + case "reflection": { + const decl = t.declaration || {} + if (decl.signatures?.length) { + const sig = decl.signatures[0] + const args = (sig.parameters || []).map((p: Node) => + typeToString(p.type), + ) + return `(${args.join(", ")}) => ${typeToString(sig.type)}` + } + return "object" + } + default: + return "any" + } +} + +function convertIntrinsic(t) { + switch (t.name) { + case "string": + if (typeof value === "string") return value + throw new Error(`Can't convert to string: ${value}`) + case "number": { + const n = parseFloat(value) + if (!Number.isNaN(n)) return n + throw new Error(`Can't convert to number: ${value}`) + } + case "boolean": + if (value === "true") return true + if (value === "false") return false + throw new Error(`Can't convert ${value} to boolean`) + case "object": + try { + return JSON.parse(value) + } catch { + throw new Error(`Can't convert to object: ${value}`) + } + case "void": + case "undefined": + case "never": + return null + default: + return value + } +} + +export function convert(t: Node | undefined, value: any): any { + if (!t) return value + switch (t.type) { + case "intrinsic": + return convertIntrinsic(t) + case "array": { + let arr = value + if (!Array.isArray(arr)) { + try { + arr = JSON.parse(arr) + } catch { + throw new Error(`Can't convert ${value} to array:`) + } + if (!Array.isArray(arr)) + throw new Error(`Can't convert ${value} to array:`) + } + return arr.map((v: any) => convert(t.elementType, v)) + } + case "tuple": { + let arr = value + if (!Array.isArray(arr)) { + try { + arr = JSON.parse(arr) + } catch { + throw new Error(`Can't convert to tuple: ${value}`) + } + if (!Array.isArray(arr)) + throw new Error(`Can't convert to tuple: ${value}`) + } + const elems = t.elements || [] + if (arr.length !== elems.length) { + throw new Error( + `Error converting tuple: number of elements and type mismatch ${value}`, + ) + } + return arr.map((v: any, i: number) => convert(elems[i], v)) + } + case "union": + for (const u of t.types || []) { + try { + return convert(u, value) + } catch {} + } + throw new Error( + `Can't convert "${value}" to any of: ${(t.types || []).map(typeToString).join(", ")}`, + ) + case "stringLiteral": + case "numberLiteral": + case "booleanLiteral": + if (value === t.value) return value + throw new Error( + `Argument does not match expected value (${t.value}): ${value}`, + ) + case "reflection": { + const decl = t.declaration || {} + if (decl.signatures?.length) { + throw new Error( + `Conversion to function not implemented: ${value}`, + ) + } + try { + return JSON.parse(value) + } catch { + throw new Error(`Can't convert to object: ${value}`) + } + } + case "reference": + throw new Error( + "Conversion of simple type references not implemented.", + ) + default: + return value + } +} + +/** Walk `path` through an object-type reflection, coercing `value` against the matched leaf. */ +export function convertMember( + t: Node | undefined, + path: string[], + value: any, +): any { + const decl = t?.type === "reflection" ? t.declaration : undefined + const named: Record = {} + let indexSig: Node | undefined + for (const ch of decl?.children || []) { + if (ch.name && ch.type) named[ch.name] = ch.type + } + const idx = decl?.indexSignature + const idxArr = Array.isArray(idx) ? idx : idx ? [idx] : [] + for (const sig of idxArr) if (sig?.type) indexSig = sig.type + + const sub = named[path[0]] ?? indexSig + if (!sub) return value + if (typeKind(sub) === "object") + return convertMember(sub, path.slice(1), value) + return convert(sub, value) +} diff --git a/src/parsers/exmode.ts b/src/parsers/exmode.ts index 07d7f512..446ece67 100644 --- a/src/parsers/exmode.ts +++ b/src/parsers/exmode.ts @@ -1,26 +1,20 @@ /** Ex Mode (AKA cmd mode) */ -import { FunctionType } from "../../compiler/types/AllTypes" -import { everything as metadata } from "@src/.metadata.generated" +import { excmdsFunctions, paramTypes, convert } from "@src/.metadata.generated" import * as aliases from "@src/lib/aliases" import * as Logging from "@src/lib/logging" const logger = new Logging.Logger("exmode") -function convertArgs(types, argv) { +function convertArgs(params, argv) { const typedArgs = [] - for ( - let itypes = 0, iargv = 0; - itypes < types.length && iargv < argv.length; - ++itypes && ++iargv - ) { - const curType = types[itypes] - const curArg = argv[iargv] + for (let i = 0, j = 0; i < params.length && j < argv.length; ++i && ++j) { + const p = params[i] // Special casing arrays because that's why the previous arg conversion code did - if (curType.isDotDotDot || curType.kind === "array") { - return typedArgs.concat(curType.convert(argv.slice(iargv))) + if (p.flags?.isRest || p.type?.type === "array") { + return typedArgs.concat(convert(p.type, argv.slice(j))) } - typedArgs.push(curType.convert(curArg)) + typedArgs.push(convert(p.type, argv[j])) } return typedArgs } @@ -47,18 +41,13 @@ export function parser(exstr: string, all_excmds: any): any[] { // Convert arguments, but only for ex commands let converted_args if (namespce == "" && args.length > 0) { - let types - try { - types = (metadata.getFile("src/excmds.ts").getFunction(funcName) - .type as FunctionType).args - } catch (e) { + const fn = excmdsFunctions[funcName] + if (!fn) { // user defined functions? - types = null converted_args = args - } - if (types !== null) { + } else { try { - converted_args = convertArgs(types, args) + converted_args = convertArgs(paramTypes(fn), args) } catch (e) { logger.error("Error executing or parsing:", exstr, e) throw e diff --git a/tsconfig.json b/tsconfig.json index 75fd0ef5..5b40604f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "moduleResolution": "node", "module": "es2020", "esModuleInterop": true, + "resolveJsonModule": true, "noImplicitAny": false, "noEmitOnError": true, "outDir": "build/tsc-out",