mirror of
https://github.com/vale981/tridactyl
synced 2025-03-05 09:31:41 -05:00
Make the generated metadata typed
This commit makes the compiler pass use different classes in order to represent the metadata. This enables adding per-class toString/convert functions. This enables easy type checking and conversion in the `:set` excmd.
This commit is contained in:
parent
abbb51debd
commit
4d0f7c84eb
27 changed files with 631 additions and 299 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -13,5 +13,6 @@ native/native_main
|
||||||
native_main.spec
|
native_main.spec
|
||||||
.wine-pyinstaller
|
.wine-pyinstaller
|
||||||
tags
|
tags
|
||||||
compiler/gen_metadata.js
|
compiler/*.js
|
||||||
|
compiler/**/*.js
|
||||||
src/.metadata.generated.ts
|
src/.metadata.generated.ts
|
||||||
|
|
|
@ -1,95 +1,68 @@
|
||||||
import * as ts from "typescript"
|
import * as ts from "typescript"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
import * as commandLineArgs from "command-line-args"
|
import * as commandLineArgs from "command-line-args"
|
||||||
|
import * as AllTypes from "./types/AllTypes"
|
||||||
|
import * as AllMetadata from "./metadata/AllMetadata"
|
||||||
|
|
||||||
class SimpleType {
|
export function toSimpleType(typeNode) {
|
||||||
name: string
|
switch (typeNode.kind) {
|
||||||
kind: string
|
case ts.SyntaxKind.VoidKeyword:
|
||||||
// Support for generics is mostly done but it might not be needed for now
|
return new AllTypes.VoidType()
|
||||||
// generics: Array<SimpleType>
|
case ts.SyntaxKind.AnyKeyword:
|
||||||
arguments: Array<SimpleType>
|
return new AllTypes.AnyType()
|
||||||
type: SimpleType
|
case ts.SyntaxKind.BooleanKeyword:
|
||||||
|
return new AllTypes.BooleanType()
|
||||||
constructor(typeNode) {
|
case ts.SyntaxKind.NumberKeyword:
|
||||||
switch (typeNode.kind) {
|
return new AllTypes.NumberType()
|
||||||
case ts.SyntaxKind.VoidKeyword:
|
case ts.SyntaxKind.ObjectKeyword:
|
||||||
this.kind = "void"
|
return new AllTypes.ObjectType()
|
||||||
break
|
case ts.SyntaxKind.StringKeyword:
|
||||||
case ts.SyntaxKind.AnyKeyword:
|
return new AllTypes.StringType()
|
||||||
this.kind = "any"
|
case ts.SyntaxKind.Parameter:
|
||||||
break
|
let n = toSimpleType(typeNode.type)
|
||||||
case ts.SyntaxKind.BooleanKeyword:
|
n.name = typeNode.name.original.escapedText
|
||||||
this.kind = "boolean"
|
return n
|
||||||
break
|
case ts.SyntaxKind.TypeReference:
|
||||||
case ts.SyntaxKind.NumberKeyword:
|
let args = typeNode.typeArguments
|
||||||
this.kind = "number"
|
? typeNode.typeArguments.map(t =>
|
||||||
break
|
toSimpleType(typeNode.typeArguments[0]),
|
||||||
case ts.SyntaxKind.ObjectKeyword:
|
)
|
||||||
this.kind = "object"
|
: []
|
||||||
break
|
return new AllTypes.TypeReferenceType(
|
||||||
case ts.SyntaxKind.StringKeyword:
|
typeNode.typeName.escapedText,
|
||||||
this.kind = "string"
|
args,
|
||||||
break
|
)
|
||||||
case ts.SyntaxKind.Parameter:
|
case ts.SyntaxKind.FunctionType:
|
||||||
// 149 is "Parameter". We don't care about that so let's
|
// generics = (typeNode.typeParameters || []).map(p => new AllTypes.SimpleType(p))
|
||||||
// convert its type into a SimpleType and grab what we need from it
|
return new AllTypes.FunctionType(
|
||||||
let ttype = new SimpleType(typeNode.type)
|
typeNode.parameters.map(p => toSimpleType(p)),
|
||||||
this.kind = ttype.kind
|
toSimpleType(typeNode.type),
|
||||||
// this.generics = ttype.generics
|
)
|
||||||
this.arguments = ttype.arguments
|
case ts.SyntaxKind.TypeLiteral:
|
||||||
this.type = ttype.type
|
// This is a type literal. i.e., something like this: { [str: string]: string[] }
|
||||||
this.name = typeNode.name.original.escapedText
|
// Very complicated and perhaps not useful to know about. Let's just say "object" for now
|
||||||
break
|
return new AllTypes.ObjectType()
|
||||||
case ts.SyntaxKind.TypeReference:
|
case ts.SyntaxKind.ArrayType:
|
||||||
// 162 is "TypeReference". Not sure what the rules are here but it seems to be used for generics
|
return new AllTypes.ArrayType(toSimpleType(typeNode.elementType))
|
||||||
this.kind = typeNode.typeName.escapedText
|
case ts.SyntaxKind.TupleType:
|
||||||
if (typeNode.typeArguments) {
|
return new AllTypes.TupleType(
|
||||||
this.arguments = typeNode.typeArguments.map(
|
typeNode.elementTypes.map(t => toSimpleType(t)),
|
||||||
t => new SimpleType(typeNode.typeArguments[0]),
|
)
|
||||||
)
|
case ts.SyntaxKind.UnionType:
|
||||||
}
|
return new AllTypes.UnionType(
|
||||||
break
|
typeNode.types.map(t => toSimpleType(t)),
|
||||||
case ts.SyntaxKind.FunctionType:
|
)
|
||||||
this.kind = "function"
|
break
|
||||||
// Probably don't need generics for now
|
case ts.SyntaxKind.LiteralType:
|
||||||
// this.generics = (typeNode.typeParameters || []).map(p => new SimpleType(p))
|
return new AllTypes.LiteralTypeType(typeNode.literal.text)
|
||||||
this.arguments = typeNode.parameters.map(p => new SimpleType(p))
|
break
|
||||||
this.type = new SimpleType(typeNode.type)
|
default:
|
||||||
break
|
console.log(typeNode)
|
||||||
case ts.SyntaxKind.TypeLiteral:
|
throw new Error(`Unhandled kind (${typeNode.kind}) for ${typeNode}`)
|
||||||
// This is a type literal. i.e., something like this: { [str: string]: string[] }
|
|
||||||
// Very complicated and perhaps not useful to know about. Let's just say "object" for now
|
|
||||||
this.kind = "object"
|
|
||||||
break
|
|
||||||
case ts.SyntaxKind.ArrayType:
|
|
||||||
this.kind = "array"
|
|
||||||
this.type = new SimpleType(typeNode.elementType)
|
|
||||||
break
|
|
||||||
case ts.SyntaxKind.TupleType:
|
|
||||||
this.kind = "tuple"
|
|
||||||
this.arguments = typeNode.elementTypes.map(
|
|
||||||
t => new SimpleType(t),
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case ts.SyntaxKind.UnionType:
|
|
||||||
this.kind = "union"
|
|
||||||
this.arguments = typeNode.types.map(t => new SimpleType(t))
|
|
||||||
break
|
|
||||||
case ts.SyntaxKind.LiteralType:
|
|
||||||
// "LiteralType". I'm not sure what this is. Probably things like type a = "b" | "c"
|
|
||||||
this.kind = "LiteralType"
|
|
||||||
this.name = typeNode.literal.text
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
console.log(typeNode)
|
|
||||||
throw new Error(
|
|
||||||
`Unhandled kind (${typeNode.kind}) for ${typeNode}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True if this is visible outside this file, false otherwise */
|
/** True if node is visible outside its file, false otherwise */
|
||||||
function isNodeExported(node: ts.Node): boolean {
|
function isNodeExported(node: ts.Node): boolean {
|
||||||
return (
|
return (
|
||||||
(ts.getCombinedModifierFlags(<ts.Declaration>node) &
|
(ts.getCombinedModifierFlags(<ts.Declaration>node) &
|
||||||
|
@ -99,57 +72,64 @@ function isNodeExported(node: ts.Node): boolean {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function visit(checker: any, filename: string, node: any, everything: any) {
|
function visit(checker: any, file: AllMetadata.FileMetadata, node: any) {
|
||||||
let symbol = checker.getSymbolAtLocation(node.name)
|
let symbol = checker.getSymbolAtLocation(node.name)
|
||||||
if (symbol && isNodeExported(node)) {
|
if (symbol && isNodeExported(node)) {
|
||||||
// ensure() is very simple, it just creates a key named `name` the value of which is `def` if `name` doesn't exist in `obj`
|
|
||||||
let ensure = (obj, name, def) => {
|
|
||||||
obj[name] = obj[name] || def
|
|
||||||
return obj[name]
|
|
||||||
}
|
|
||||||
// addDoc creates a "doc" key set to an empty array in `obj` if it doesn't exist and then adds documentation from the symbol to it if it isn't already in the array
|
|
||||||
let addDoc = (obj, symbol) => {
|
|
||||||
let doc = ensure(obj, "doc", [])
|
|
||||||
let docstr = ts.displayPartsToString(
|
|
||||||
symbol.getDocumentationComment(),
|
|
||||||
)
|
|
||||||
if (docstr && !doc.includes(docstr)) doc.push(docstr)
|
|
||||||
}
|
|
||||||
// addType sets the `type` attribute of `obj` to the SimpleType of `symbol` if it has one
|
|
||||||
let addType = (obj, symbol) => {
|
|
||||||
let ttype = checker.getTypeOfSymbolAtLocation(
|
|
||||||
symbol,
|
|
||||||
symbol.valueDeclaration!,
|
|
||||||
)
|
|
||||||
if (ttype) {
|
|
||||||
obj["type"] = new SimpleType(checker.typeToTypeNode(ttype))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let nodeName = symbol.escapedName
|
let nodeName = symbol.escapedName
|
||||||
|
|
||||||
let file = ensure(everything, filename, {})
|
|
||||||
|
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
case ts.SyntaxKind.FunctionDeclaration:
|
case ts.SyntaxKind.FunctionDeclaration:
|
||||||
let functions = ensure(file, "functions", {})
|
// Grab the doc, default to empty string
|
||||||
let func = ensure(functions, nodeName, {})
|
let doc =
|
||||||
addDoc(func, symbol)
|
ts.displayPartsToString(symbol.getDocumentationComment()) ||
|
||||||
addType(func, symbol)
|
""
|
||||||
break
|
// 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),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
case ts.SyntaxKind.ClassDeclaration:
|
case ts.SyntaxKind.ClassDeclaration:
|
||||||
let classes = ensure(file, "classes", {})
|
let clazz = file.getClass(nodeName)
|
||||||
let clazz = ensure(classes, nodeName, {})
|
if (!clazz) {
|
||||||
|
clazz = new AllMetadata.ClassMetadata()
|
||||||
|
file.setClass(nodeName, clazz)
|
||||||
|
}
|
||||||
symbol.members.forEach((sym, name, map) => {
|
symbol.members.forEach((sym, name, map) => {
|
||||||
// Can't get doc/type from these special functions
|
// Can't get doc/type from these special functions
|
||||||
// Or at least, it requires work that might not be needed for now
|
// Or at least, it requires work that might not be needed for now
|
||||||
if (["__constructor", "get", "set"].includes(name)) return
|
if (["__constructor", "get", "set"].includes(name)) return
|
||||||
|
|
||||||
let member = ensure(clazz, name, {})
|
// Grab the doc, default to empty string
|
||||||
addDoc(member, sym)
|
let doc =
|
||||||
addType(member, sym)
|
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),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
break
|
return
|
||||||
|
|
||||||
// Other declaration syntaxkinds:
|
// Other declaration syntaxkinds:
|
||||||
// case ts.SyntaxKind.VariableDeclaration:
|
// case ts.SyntaxKind.VariableDeclaration:
|
||||||
// case ts.SyntaxKind.VariableDeclarationList:
|
// case ts.SyntaxKind.VariableDeclarationList:
|
||||||
|
@ -171,7 +151,7 @@ function visit(checker: any, filename: string, node: any, everything: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ts.forEachChild(node, node => visit(checker, filename, node, everything))
|
ts.forEachChild(node, node => visit(checker, file, node))
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateMetadata(
|
function generateMetadata(
|
||||||
|
@ -185,18 +165,32 @@ function generateMetadata(
|
||||||
module: ts.ModuleKind.CommonJS,
|
module: ts.ModuleKind.CommonJS,
|
||||||
})
|
})
|
||||||
|
|
||||||
let everything = {}
|
let metadata = new AllMetadata.ProgramMetadata()
|
||||||
|
|
||||||
for (const sourceFile of program.getSourceFiles()) {
|
for (const sourceFile of program.getSourceFiles()) {
|
||||||
let n = (fileNames as any).find(name => sourceFile.fileName.match(name))
|
let name = (fileNames as any).find(name =>
|
||||||
if (n) visit(program.getTypeChecker(), n, sourceFile, everything)
|
sourceFile.fileName.match(name),
|
||||||
|
)
|
||||||
|
if (name) {
|
||||||
|
let file = metadata.getFile(name)
|
||||||
|
if (!file) {
|
||||||
|
file = new AllMetadata.FileMetadata()
|
||||||
|
metadata.setFile(name, file)
|
||||||
|
}
|
||||||
|
visit(program.getTypeChecker(), file, sourceFile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadataString = `\nexport let everything = ${JSON.stringify(
|
let imports =
|
||||||
everything,
|
`import {${Object.keys(AllTypes).join(
|
||||||
undefined,
|
", ",
|
||||||
4,
|
)}} from "../compiler/types/AllTypes"\n` +
|
||||||
)}\n`
|
`import {${Object.keys(AllMetadata).join(
|
||||||
|
", ",
|
||||||
|
)}} from "../compiler/metadata/AllMetadata"\n`
|
||||||
|
|
||||||
|
let metadataString =
|
||||||
|
imports + `\nexport let everything = ${metadata.toConstructor()}\n`
|
||||||
|
|
||||||
if (themedir) {
|
if (themedir) {
|
||||||
metadataString += `\nexport let staticThemes = ${JSON.stringify(
|
metadataString += `\nexport let staticThemes = ${JSON.stringify(
|
||||||
|
|
4
compiler/metadata/AllMetadata.ts
Normal file
4
compiler/metadata/AllMetadata.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export { SymbolMetadata } from "./SymbolMetadata"
|
||||||
|
export { ClassMetadata } from "./ClassMetadata"
|
||||||
|
export { FileMetadata } from "./FileMetadata"
|
||||||
|
export { ProgramMetadata } from "./ProgramMetadata"
|
33
compiler/metadata/ClassMetadata.ts
Normal file
33
compiler/metadata/ClassMetadata.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { Type } from "../types/AllTypes"
|
||||||
|
import { SymbolMetadata } from "./SymbolMetadata"
|
||||||
|
|
||||||
|
export class ClassMetadata {
|
||||||
|
constructor(
|
||||||
|
public members: Map<string, SymbolMetadata> = new Map<
|
||||||
|
string,
|
||||||
|
SymbolMetadata
|
||||||
|
>(),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
setMember(name: string, s: SymbolMetadata) {
|
||||||
|
this.members.set(name, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
getMember(name: string) {
|
||||||
|
return this.members.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
getMembers() {
|
||||||
|
return this.members.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return (
|
||||||
|
`new ClassMetadata(new Map<string, SymbolMetadata>([` +
|
||||||
|
Array.from(this.members.entries())
|
||||||
|
.map(([n, m]) => `[${JSON.stringify(n)}, ${m.toConstructor()}]`)
|
||||||
|
.join(",\n") +
|
||||||
|
`]))`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
53
compiler/metadata/FileMetadata.ts
Normal file
53
compiler/metadata/FileMetadata.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { SymbolMetadata } from "./SymbolMetadata"
|
||||||
|
import { ClassMetadata } from "./ClassMetadata"
|
||||||
|
|
||||||
|
export class FileMetadata {
|
||||||
|
constructor(
|
||||||
|
public classes: Map<string, ClassMetadata> = new Map<
|
||||||
|
string,
|
||||||
|
ClassMetadata
|
||||||
|
>(),
|
||||||
|
public functions: Map<string, SymbolMetadata> = new Map<
|
||||||
|
string,
|
||||||
|
SymbolMetadata
|
||||||
|
>(),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
setClass(name: string, c: ClassMetadata) {
|
||||||
|
this.classes.set(name, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
getClass(name: string) {
|
||||||
|
return this.classes.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
getClasses() {
|
||||||
|
return Array.from(this.classes.keys())
|
||||||
|
}
|
||||||
|
|
||||||
|
setFunction(name: string, f: SymbolMetadata) {
|
||||||
|
this.functions.set(name, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFunction(name: string) {
|
||||||
|
return this.functions.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFunctions() {
|
||||||
|
return Array.from(this.functions.keys())
|
||||||
|
}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return (
|
||||||
|
`new FileMetadata(new Map<string, ClassMetadata>([` +
|
||||||
|
Array.from(this.classes.entries())
|
||||||
|
.map(([n, c]) => `[${JSON.stringify(n)}, ${c.toConstructor()}]`)
|
||||||
|
.join(",\n") +
|
||||||
|
`]), new Map<string, SymbolMetadata>([` +
|
||||||
|
Array.from(this.functions.entries())
|
||||||
|
.map(([n, f]) => `[${JSON.stringify(n)}, ${f.toConstructor()}]`)
|
||||||
|
.join(",\n") +
|
||||||
|
`]))`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
28
compiler/metadata/ProgramMetadata.ts
Normal file
28
compiler/metadata/ProgramMetadata.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { FileMetadata } from "./FileMetadata"
|
||||||
|
|
||||||
|
export class ProgramMetadata {
|
||||||
|
constructor(
|
||||||
|
public files: Map<string, FileMetadata> = new Map<
|
||||||
|
string,
|
||||||
|
FileMetadata
|
||||||
|
>(),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
setFile(name: string, file: FileMetadata) {
|
||||||
|
this.files.set(name, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFile(name: string) {
|
||||||
|
return this.files.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return (
|
||||||
|
`new ProgramMetadata(new Map<string, FileMetadata>([` +
|
||||||
|
Array.from(this.files.entries())
|
||||||
|
.map(([n, f]) => `[${JSON.stringify(n)}, ${f.toConstructor()}]`)
|
||||||
|
.join(",\n") +
|
||||||
|
`]))`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
11
compiler/metadata/SymbolMetadata.ts
Normal file
11
compiler/metadata/SymbolMetadata.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { Type } from "../types/AllTypes"
|
||||||
|
|
||||||
|
export class SymbolMetadata {
|
||||||
|
constructor(public doc: string, public type: Type) {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return `new SymbolMetadata(${JSON.stringify(
|
||||||
|
this.doc,
|
||||||
|
)}, ${this.type.toConstructor()})`
|
||||||
|
}
|
||||||
|
}
|
13
compiler/types/AllTypes.ts
Normal file
13
compiler/types/AllTypes.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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"
|
19
compiler/types/AnyType.ts
Normal file
19
compiler/types/AnyType.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class AnyType implements Type {
|
||||||
|
kind = "any"
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return "new AnyType()"
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
return argument
|
||||||
|
}
|
||||||
|
}
|
29
compiler/types/ArrayType.ts
Normal file
29
compiler/types/ArrayType.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class ArrayType implements Type {
|
||||||
|
kind = "array"
|
||||||
|
|
||||||
|
constructor(public elemType: Type) {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return `new ArrayType(${this.elemType.toConstructor()})`
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.elemType.toString()}[]`
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
21
compiler/types/BooleanType.ts
Normal file
21
compiler/types/BooleanType.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class BooleanType implements Type {
|
||||||
|
kind = "boolean"
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return "new BooleanType()"
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
if (argument === "true") return true
|
||||||
|
else if (argument === "false") return false
|
||||||
|
throw new Error("Can't convert ${argument} to boolean")
|
||||||
|
}
|
||||||
|
}
|
28
compiler/types/FunctionType.ts
Normal file
28
compiler/types/FunctionType.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class FunctionType implements Type {
|
||||||
|
kind = "function"
|
||||||
|
|
||||||
|
constructor(public args: Type[], public ret: Type) {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return (
|
||||||
|
`new FunctionType([` +
|
||||||
|
// Convert every argument type to its string constructor representation
|
||||||
|
this.args.map(cur => cur.toConstructor()) +
|
||||||
|
`], ${this.ret.toConstructor()})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `(${this.args.map(a => a.toString()).join(", ")}) => ${this.ret.toString()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
// Possible strategies:
|
||||||
|
// - eval()
|
||||||
|
// - window[argument]
|
||||||
|
// - tri.excmds[argument]
|
||||||
|
throw new Error(`Conversion to function not implemented: ${argument}`)
|
||||||
|
}
|
||||||
|
}
|
24
compiler/types/LiteralTypeType.ts
Normal file
24
compiler/types/LiteralTypeType.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class LiteralTypeType implements Type {
|
||||||
|
kind = "LiteralType"
|
||||||
|
|
||||||
|
constructor(public value: string) {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return `new LiteralTypeType(${JSON.stringify(this.value)})`
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return JSON.stringify(this.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
if (argument === this.value) return argument
|
||||||
|
throw new Error(
|
||||||
|
`Argument does not match expected value (${
|
||||||
|
this.value
|
||||||
|
}): ${argument}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
23
compiler/types/NumberType.ts
Normal file
23
compiler/types/NumberType.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class NumberType implements Type {
|
||||||
|
kind = "number"
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return "new NumberType()"
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
let n = parseInt(argument)
|
||||||
|
if (!Number.isNaN(n)) return n
|
||||||
|
n = parseFloat(argument)
|
||||||
|
if (!Number.isNaN(n)) return n
|
||||||
|
throw new Error(`Can't convert to number: ${argument}`)
|
||||||
|
}
|
||||||
|
}
|
23
compiler/types/ObjectType.ts
Normal file
23
compiler/types/ObjectType.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class ObjectType implements Type {
|
||||||
|
kind = "object"
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return "new ObjectType()"
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(argument)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Can't convert to object: ${argument}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
compiler/types/StringType.ts
Normal file
20
compiler/types/StringType.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class StringType implements Type {
|
||||||
|
kind = "string"
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return "new StringType()"
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
if (typeof argument === "string") return argument
|
||||||
|
throw new Error(`Can't convert to string: ${argument}`)
|
||||||
|
}
|
||||||
|
}
|
39
compiler/types/TupleType.ts
Normal file
39
compiler/types/TupleType.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class TupleType implements Type {
|
||||||
|
kind = "tuple"
|
||||||
|
|
||||||
|
constructor(public elemTypes: Type[]) {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return (
|
||||||
|
`new TupleType([` +
|
||||||
|
// Convert every element type to its constructor representation
|
||||||
|
this.elemTypes.map(cur => cur.toConstructor()).join(",\n") +
|
||||||
|
`])`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `[${this.elemTypes.map(e => e.toString()).join(", ")}]`
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
9
compiler/types/Type.ts
Normal file
9
compiler/types/Type.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import * as ts from "typescript"
|
||||||
|
|
||||||
|
export interface Type {
|
||||||
|
kind: string
|
||||||
|
name?: string
|
||||||
|
toConstructor(): string
|
||||||
|
toString(): string
|
||||||
|
convert: (argument: string) => any
|
||||||
|
}
|
22
compiler/types/TypeReferenceType.ts
Normal file
22
compiler/types/TypeReferenceType.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class TypeReferenceType implements Type {
|
||||||
|
constructor(public kind: string, public args: Type[]) {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return (
|
||||||
|
`new TypeReferenceType(${JSON.stringify(this.kind)}, [` +
|
||||||
|
// Turn every type argument into its constructor representation
|
||||||
|
this.args.map(cur => cur.toConstructor()).join(",\n") +
|
||||||
|
`])`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.kind}<${this.args.map(a => a.toString()).join(", ")}>`
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
throw new Error("Conversion of simple type references not implemented.")
|
||||||
|
}
|
||||||
|
}
|
33
compiler/types/UnionType.ts
Normal file
33
compiler/types/UnionType.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class UnionType implements Type {
|
||||||
|
kind = "union"
|
||||||
|
|
||||||
|
constructor(public types: Type[]) {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return (
|
||||||
|
`new UnionType([` +
|
||||||
|
// Convert every type to its string constructor representation
|
||||||
|
this.types.map(cur => cur.toConstructor()).join(",\n") +
|
||||||
|
`])`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.types.map(t => t.toString()).join(" | ")
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
for (let t of this.types) {
|
||||||
|
try {
|
||||||
|
return t.convert(argument)
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Can't convert argument to any of types: ${argument}, ${
|
||||||
|
this.types
|
||||||
|
}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
19
compiler/types/VoidType.ts
Normal file
19
compiler/types/VoidType.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Type } from "./Type"
|
||||||
|
|
||||||
|
export class VoidType implements Type {
|
||||||
|
kind = "void"
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
toConstructor() {
|
||||||
|
return "new VoidType()"
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(argument) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ fi
|
||||||
|
|
||||||
# It's important to generate the metadata before the documentation because
|
# It's important to generate the metadata before the documentation because
|
||||||
# missing imports might break documentation generation on clean builds
|
# missing imports might break documentation generation on clean builds
|
||||||
"$(npm bin)/tsc" compiler/gen_metadata.ts -m commonjs --target es2016 \
|
"$(npm bin)/tsc" compiler/gen_metadata.ts -m commonjs --target es2017 \
|
||||||
&& node compiler/gen_metadata.js \
|
&& node compiler/gen_metadata.js \
|
||||||
--out src/.metadata.generated.ts \
|
--out src/.metadata.generated.ts \
|
||||||
--themeDir src/static/themes \
|
--themeDir src/static/themes \
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import * as Completions from "@src/completions"
|
import * as Completions from "@src/completions"
|
||||||
import { typeToSimpleString } from "@src/lib/metadata"
|
|
||||||
import * as Metadata from "@src/.metadata.generated"
|
import * as Metadata from "@src/.metadata.generated"
|
||||||
import state from "@src/state"
|
import state from "@src/state"
|
||||||
import * as config from "@src/lib/config"
|
import * as config from "@src/lib/config"
|
||||||
|
@ -10,7 +9,6 @@ class ExcmdCompletionOption extends Completions.CompletionOptionHTML
|
||||||
public fuseKeys = []
|
public fuseKeys = []
|
||||||
constructor(
|
constructor(
|
||||||
public value: string,
|
public value: string,
|
||||||
public ttype: string = "",
|
|
||||||
public documentation: string = "",
|
public documentation: string = "",
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
|
@ -22,7 +20,6 @@ class ExcmdCompletionOption extends Completions.CompletionOptionHTML
|
||||||
<td class="documentation">${documentation}</td>
|
<td class="documentation">${documentation}</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
}
|
}
|
||||||
// <td class="type">${ttype}</td>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
|
export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
|
||||||
|
@ -49,37 +46,26 @@ export class ExcmdCompletionSource extends Completions.CompletionSourceFuse {
|
||||||
private async updateOptions(exstr?: string) {
|
private async updateOptions(exstr?: string) {
|
||||||
if (!exstr) exstr = ""
|
if (!exstr) exstr = ""
|
||||||
this.lastExstr = exstr
|
this.lastExstr = exstr
|
||||||
let fns = Metadata.everything["src/excmds.ts"].functions
|
|
||||||
this.options = (await this.scoreOptions(
|
|
||||||
Object.keys(fns).filter(f => f.startsWith(exstr)),
|
|
||||||
)).map(f => {
|
|
||||||
let t = ""
|
|
||||||
if (fns[f].type) t = typeToSimpleString(fns[f].type)
|
|
||||||
return new ExcmdCompletionOption(f, t, fns[f].doc)
|
|
||||||
})
|
|
||||||
|
|
||||||
let exaliases = config.get("exaliases")
|
let excmds = Metadata.everything.getFile("src/excmds.ts")
|
||||||
for (let alias of Object.keys(exaliases).filter(a =>
|
if (!excmds) return
|
||||||
a.startsWith(exstr),
|
let fns = excmds.getFunctions()
|
||||||
)) {
|
|
||||||
|
// Add all excmds that start with exstr and that tridactyl has metadata about to completions
|
||||||
|
this.options = (await this.scoreOptions(
|
||||||
|
fns.filter(f => f.startsWith(exstr)),
|
||||||
|
)).map(f => new ExcmdCompletionOption(f, excmds.getFunction(f).doc))
|
||||||
|
|
||||||
|
// Also add aliases to possible completions
|
||||||
|
let exaliases = Object.keys(config.get("exaliases")).filter(a => a.startsWith(exstr))
|
||||||
|
for (let alias of exaliases) {
|
||||||
let cmd = aliases.expandExstr(alias)
|
let cmd = aliases.expandExstr(alias)
|
||||||
if (fns[cmd]) {
|
let fn = excmds.getFunction(cmd)
|
||||||
this.options = this.options.concat(
|
if (fn) {
|
||||||
new ExcmdCompletionOption(
|
this.options.push(new ExcmdCompletionOption(alias, `Alias for \`${cmd}\`. ${fn.doc}`))
|
||||||
alias,
|
|
||||||
fns[cmd].type ? typeToSimpleString(fns[cmd].type) : "",
|
|
||||||
`Alias for \`${cmd}\`. ${fns[cmd].doc}`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
// This can happen when the alias is a composite command or a command with arguments. We can't display type or doc because we don't know what parameter the alias takes or what it does.
|
// This can happen when the alias is a composite command or a command with arguments. We can't display doc because we don't know what parameter the alias takes or what it does.
|
||||||
this.options = this.options.concat(
|
this.options.push(new ExcmdCompletionOption(alias, `Alias for \`${cmd}\`.`))
|
||||||
new ExcmdCompletionOption(
|
|
||||||
alias,
|
|
||||||
"",
|
|
||||||
`Alias for \`${cmd}\`.`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import * as aliases from "@src/lib/aliases"
|
||||||
import * as config from "@src/lib/config"
|
import * as config from "@src/lib/config"
|
||||||
import state from "@src/state"
|
import state from "@src/state"
|
||||||
import { browserBg } from "@src/lib/webext"
|
import { browserBg } from "@src/lib/webext"
|
||||||
import { typeToString } from "@src/lib/metadata"
|
|
||||||
|
|
||||||
class HelpCompletionOption extends Completions.CompletionOptionHTML implements Completions.CompletionOptionFuse {
|
class HelpCompletionOption extends Completions.CompletionOptionHTML implements Completions.CompletionOptionFuse {
|
||||||
public fuseKeys = []
|
public fuseKeys = []
|
||||||
|
@ -51,33 +50,37 @@ export class HelpCompletionSource extends Completions.CompletionSourceFuse {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let configmd = Metadata.everything["src/lib/config.ts"].classes.default_config
|
let file, default_config, excmds, fns, settings, exaliases, bindings
|
||||||
let fns = Metadata.everything["src/excmds.ts"].functions
|
if (!(file = Metadata.everything.getFile("src/lib/config.ts"))
|
||||||
let settings = config.get()
|
|| !(default_config = file.getClass("default_config"))
|
||||||
let exaliases = settings.exaliases
|
|| !(excmds = Metadata.everything.getFile("src/excmds.ts"))
|
||||||
let bindings = settings.nmaps
|
|| !(fns = excmds.getFunctions())
|
||||||
|
|| !(settings = config.get())
|
||||||
|
|| !(exaliases = settings.exaliases)
|
||||||
|
|| !(bindings = settings.nmaps))
|
||||||
|
return;
|
||||||
|
|
||||||
// Settings completion
|
// Settings completion
|
||||||
this.options = Object.keys(settings)
|
this.options = Object.keys(settings)
|
||||||
.filter(x => x.startsWith(query))
|
.filter(x => x.startsWith(query))
|
||||||
.map(setting => {
|
.map(setting => {
|
||||||
let doc = ""
|
let member, doc = ""
|
||||||
if (configmd[setting]) {
|
if (member = default_config.getMember(setting)) {
|
||||||
doc = configmd[setting].doc.join(" ")
|
doc = member.doc
|
||||||
}
|
}
|
||||||
return new HelpCompletionOption(setting, `Setting. ${doc}`)
|
return new HelpCompletionOption(setting, `Setting. ${doc}`)
|
||||||
})
|
})
|
||||||
// Excmd completion
|
// Excmd completion
|
||||||
.concat(Object.keys(fns)
|
.concat(fns
|
||||||
.filter(fn => fn.startsWith(query))
|
.filter(fn => fn.startsWith(query))
|
||||||
.map(f => new HelpCompletionOption(f, `Excmd. ${fns[f].doc}`))
|
.map(f => new HelpCompletionOption(f, `Excmd. ${excmds.getFunction(f).doc}`))
|
||||||
)
|
)
|
||||||
// Alias completion
|
// Alias completion
|
||||||
.concat(Object.keys(exaliases)
|
.concat(Object.keys(exaliases)
|
||||||
.filter(alias => alias.startsWith(query))
|
.filter(alias => alias.startsWith(query))
|
||||||
.map(alias => {
|
.map(alias => {
|
||||||
let cmd = aliases.expandExstr(alias)
|
let cmd = aliases.expandExstr(alias)
|
||||||
let doc = (fns[cmd] || {}).doc || ""
|
let doc = (excmds.getFunction(cmd) || {}).doc || ""
|
||||||
return new HelpCompletionOption(alias, `Alias for \`${cmd}\`. ${doc}`)
|
return new HelpCompletionOption(alias, `Alias for \`${cmd}\`. ${doc}`)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,6 @@ import * as Completions from "@src/completions"
|
||||||
import * as config from "@src/lib/config"
|
import * as config from "@src/lib/config"
|
||||||
import { browserBg } from "@src/lib/webext"
|
import { browserBg } from "@src/lib/webext"
|
||||||
import * as metadata from "@src/.metadata.generated"
|
import * as metadata from "@src/.metadata.generated"
|
||||||
import { typeToString } from "@src/lib/metadata"
|
|
||||||
|
|
||||||
class SettingsCompletionOption extends Completions.CompletionOptionHTML
|
class SettingsCompletionOption extends Completions.CompletionOptionHTML
|
||||||
implements Completions.CompletionOptionFuse {
|
implements Completions.CompletionOptionFuse {
|
||||||
|
@ -62,18 +61,22 @@ export class SettingsCompletionSource extends Completions.CompletionSourceFuse {
|
||||||
|
|
||||||
options += options ? " " : ""
|
options += options ? " " : ""
|
||||||
|
|
||||||
let configmd =
|
let file, default_config, settings
|
||||||
metadata.everything["src/lib/config.ts"].classes.default_config
|
if (!(file = metadata.everything.getFile("src/lib/config.ts"))
|
||||||
let settings = config.get()
|
|| !(default_config = file.getClass("default_config"))
|
||||||
|
|| !(settings = config.get()))
|
||||||
|
return
|
||||||
|
|
||||||
this.options = Object.keys(settings)
|
this.options = Object.keys(settings)
|
||||||
.filter(x => x.startsWith(query))
|
.filter(x => x.startsWith(query))
|
||||||
.sort()
|
.sort()
|
||||||
.map(setting => {
|
.map(setting => {
|
||||||
|
let md = undefined
|
||||||
let doc = ""
|
let doc = ""
|
||||||
let type = ""
|
let type = ""
|
||||||
if (configmd[setting]) {
|
if (md = default_config.getMember(setting)) {
|
||||||
doc = configmd[setting].doc.join(" ")
|
doc = md.doc
|
||||||
type = typeToString(configmd[setting].type)
|
type = md.type.toString()
|
||||||
}
|
}
|
||||||
return new SettingsCompletionOption(options + setting, {
|
return new SettingsCompletionOption(options + setting, {
|
||||||
name: setting,
|
name: setting,
|
||||||
|
@ -82,7 +85,6 @@ export class SettingsCompletionSource extends Completions.CompletionSourceFuse {
|
||||||
type: type,
|
type: type,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// this.options = [new SettingsCompletionOption("ok", {name: "ok", docs:""})]
|
|
||||||
|
|
||||||
this.updateChain()
|
this.updateChain()
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,6 @@ import Mark from "mark.js"
|
||||||
import * as CSS from "css"
|
import * as CSS from "css"
|
||||||
import * as Perf from "@src/perf"
|
import * as Perf from "@src/perf"
|
||||||
import * as Metadata from "@src/.metadata.generated"
|
import * as Metadata from "@src/.metadata.generated"
|
||||||
import { fitsType, typeToString } from "@src/lib/metadata"
|
|
||||||
|
|
||||||
//#content_helper
|
//#content_helper
|
||||||
// {
|
// {
|
||||||
|
@ -2848,9 +2847,15 @@ function validateSetArgs(key: string, values: string[]) {
|
||||||
throw "Unsupported setting type!"
|
throw "Unsupported setting type!"
|
||||||
}
|
}
|
||||||
|
|
||||||
let md = Metadata.everything["src/lib/config.ts"].classes.default_config[last]
|
let file, default_config, md
|
||||||
if (md) {
|
if ((file = Metadata.everything.getFile("src/lib/config.ts")) && (default_config = file.getClass("default_config")) && (md = default_config.getMember(last))) {
|
||||||
if (md.type && !fitsType(value, md.type)) throw `Given type does not match expected type (given: ${value}, expected: ${typeToString(md.type)})`
|
try {
|
||||||
|
value = md.type.convert(value)
|
||||||
|
} catch (e) {
|
||||||
|
throw `Given value (${value}) does not match or could not be converted to ${md.type.toString()}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warning("Could not fetch setting metadata.")
|
||||||
}
|
}
|
||||||
|
|
||||||
target.push(value)
|
target.push(value)
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
export function fitsType(variable, type) {
|
|
||||||
switch (type.kind) {
|
|
||||||
case "function":
|
|
||||||
return variable instanceof Function
|
|
||||||
case "array":
|
|
||||||
return (
|
|
||||||
variable instanceof Array &&
|
|
||||||
!variable.find(e => !fitsType(e, type.type))
|
|
||||||
)
|
|
||||||
case "Promise":
|
|
||||||
return variable instanceof Promise
|
|
||||||
case "union":
|
|
||||||
for (let t of type.arguments) {
|
|
||||||
if (fitsType(variable, t)) return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case "LiteralType":
|
|
||||||
return variable == type.name
|
|
||||||
case "tuple":
|
|
||||||
if (variable.length != type.arguments.length) return false
|
|
||||||
for (let i = 0; i < variable.length; ++i) {
|
|
||||||
let v = variable[i]
|
|
||||||
let t = type.arguments[i]
|
|
||||||
if (!fitsType(v, t)) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case "any":
|
|
||||||
return true
|
|
||||||
case "object":
|
|
||||||
return true
|
|
||||||
case "boolean":
|
|
||||||
return typeof variable == "number"
|
|
||||||
case "number":
|
|
||||||
return typeof variable == "number"
|
|
||||||
case "string":
|
|
||||||
return typeof variable == "string"
|
|
||||||
case "void":
|
|
||||||
return !variable
|
|
||||||
}
|
|
||||||
throw new Error("Unhandled type!")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the type sitting inside a promise
|
|
||||||
function unwrapPromise(type) {
|
|
||||||
while (type.kind == "Promise") type = type.arguments[0]
|
|
||||||
return type
|
|
||||||
}
|
|
||||||
|
|
||||||
// This turns TYPE into a string, the format of which is quite short in order to be useful for completions
|
|
||||||
export function typeToSimpleString(type): string {
|
|
||||||
let result = ""
|
|
||||||
switch (type.kind) {
|
|
||||||
case "function":
|
|
||||||
result = type.arguments.map(typeToSimpleString).join(", ")
|
|
||||||
let t = unwrapPromise(type.type)
|
|
||||||
if (!["any", "object", "void"].includes(t.kind)) {
|
|
||||||
if (result == "") result = "()"
|
|
||||||
result += "->" + typeToString(t)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
result = type.name || ""
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// This turns TYPE into a string, the format of which is quite close to the one tsc uses to display type strings
|
|
||||||
export function typeToString(type): string {
|
|
||||||
let allTypes = arr => arr.map(typeToString).join(", ")
|
|
||||||
let result = ""
|
|
||||||
switch (type.kind) {
|
|
||||||
case "function":
|
|
||||||
result =
|
|
||||||
"(" +
|
|
||||||
allTypes(type.arguments) +
|
|
||||||
")" +
|
|
||||||
" => " +
|
|
||||||
typeToString(type.type)
|
|
||||||
break
|
|
||||||
case "array":
|
|
||||||
result = typeToString(type.type) + "[]"
|
|
||||||
break
|
|
||||||
case "Promise":
|
|
||||||
result = "Promise<" + allTypes(type.arguments) + ">"
|
|
||||||
break
|
|
||||||
case "union":
|
|
||||||
result = type.arguments.map(typeToString).join(" | ")
|
|
||||||
break
|
|
||||||
case "LiteralType":
|
|
||||||
result = type.name
|
|
||||||
break
|
|
||||||
case "tuple":
|
|
||||||
result = "(" + type.arguments.map(typeToString).join(", ") + ")"
|
|
||||||
case "any":
|
|
||||||
case "object":
|
|
||||||
case "boolean":
|
|
||||||
case "number":
|
|
||||||
case "string":
|
|
||||||
case "void":
|
|
||||||
default:
|
|
||||||
result = type.kind
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = ""
|
|
||||||
// If the type has a name, it could be interesting to add it to the string
|
|
||||||
// representation of the type. However, when the type is a LiteralType, the
|
|
||||||
// name is already in result, so there's no need to add it.
|
|
||||||
if (type.kind != "LiteralType" && type.name) name = type.name + ": "
|
|
||||||
return name + result
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue