mirror of
https://github.com/vale981/tridactyl
synced 2025-03-05 09:31:41 -05:00
Improve generated metadata types for objects
Before this commit, the compiler pass that generated metadata for settings didn't generate informetion precise enough to be used to validate settings that existed in objects (e.g. `logging.cmdline`). This resulted in no typechecking being done for these settings (e.g. `:set logging.cmdline 1` would not throw any errors). This commit fixes that.
This commit is contained in:
parent
d80fa64b03
commit
b5da7705e9
4 changed files with 67 additions and 27 deletions
|
@ -23,6 +23,14 @@ export function toSimpleType(typeNode) {
|
|||
n.name = typeNode.name.original.escapedText
|
||||
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]),
|
||||
|
@ -39,9 +47,16 @@ export function toSimpleType(typeNode) {
|
|||
toSimpleType(typeNode.type),
|
||||
)
|
||||
case ts.SyntaxKind.TypeLiteral:
|
||||
// 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
|
||||
return new AllTypes.ObjectType()
|
||||
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:
|
||||
|
@ -181,7 +196,8 @@ function generateMetadata(
|
|||
}
|
||||
}
|
||||
|
||||
let imports =
|
||||
// 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` +
|
||||
|
|
|
@ -3,16 +3,36 @@ import { Type } from "./Type"
|
|||
export class ObjectType implements Type {
|
||||
kind = "object"
|
||||
|
||||
constructor() {}
|
||||
// Note: a map that has an empty key ("") uses the corresponding type as default type
|
||||
constructor(public members: Map<string, Type> = new Map<string, Type>()) {}
|
||||
|
||||
toConstructor() {
|
||||
return "new ObjectType()"
|
||||
return `new ObjectType(new Map<string, Type>([` +
|
||||
Array.from(this.members.entries()).map(([n, m]) => `[${JSON.stringify(n)}, ${m.toConstructor()}]`)
|
||||
.join(", ") +
|
||||
`]))`
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.kind
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
convert(argument) {
|
||||
try {
|
||||
return JSON.parse(argument)
|
||||
|
|
|
@ -1079,7 +1079,7 @@ export function viewsource(url = "") {
|
|||
}
|
||||
|
||||
/**
|
||||
* Go to the homepages you have set with `set homepages [url1] [url2]`.
|
||||
* Go to the homepages you have set with `set homepages ["url1", "url2"]`.
|
||||
*
|
||||
* @param all
|
||||
* - if "true", opens all homepages in new tabs
|
||||
|
@ -2835,10 +2835,20 @@ export function searchsetkeyword(keyword: string, url: string) {
|
|||
*/
|
||||
function validateSetArgs(key: string, values: string[]) {
|
||||
const target: any[] = key.split(".")
|
||||
const currentValue = config.get(...target)
|
||||
const last = target[target.length - 1]
|
||||
|
||||
let value: string | string[] = values
|
||||
let value, file, default_config, md
|
||||
if ((file = Metadata.everything.getFile("src/lib/config.ts")) && (default_config = file.getClass("default_config")) && (md = default_config.getMember(target[0]))) {
|
||||
const strval = values.join(" ")
|
||||
// Note: the conversion will throw if strval can't be converted to the right type
|
||||
if (md.type.kind == "object")
|
||||
value = md.type.convertMember(target.slice(1), strval)
|
||||
else
|
||||
value = md.type.convert(strval)
|
||||
} else {
|
||||
// If we don't have metadata, fall back to the old way
|
||||
logger.warning("Could not fetch setting metadata. Falling back to type of current value.")
|
||||
const currentValue = config.get(...target)
|
||||
if (Array.isArray(currentValue)) {
|
||||
// Do nothing
|
||||
} else if (currentValue === undefined || typeof currentValue === "string") {
|
||||
|
@ -2846,16 +2856,6 @@ function validateSetArgs(key: string, values: string[]) {
|
|||
} else {
|
||||
throw "Unsupported setting type!"
|
||||
}
|
||||
|
||||
let file, default_config, md
|
||||
if ((file = Metadata.everything.getFile("src/lib/config.ts")) && (default_config = file.getClass("default_config")) && (md = default_config.getMember(last))) {
|
||||
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)
|
||||
|
|
|
@ -379,7 +379,7 @@ class default_config {
|
|||
/**
|
||||
* Whether to use the keytranslatemap in various maps.
|
||||
*/
|
||||
keytranslatemodes = {
|
||||
keytranslatemodes: {[key:string]: "true" | "false"} = {
|
||||
nmaps: "true",
|
||||
imaps: "false",
|
||||
inputmaps: "false",
|
||||
|
@ -517,9 +517,9 @@ class default_config {
|
|||
storageloc: "sync" | "local" = "sync"
|
||||
|
||||
/**
|
||||
* Pages opened with `gH`.
|
||||
* Pages opened with `gH`. In order to set this value, use `:set homepages ["example.org", "example.net", "example.com"]` and so on.
|
||||
*/
|
||||
homepages = []
|
||||
homepages: string[] = []
|
||||
|
||||
/**
|
||||
* Characters to use in hint mode.
|
||||
|
@ -644,6 +644,10 @@ class default_config {
|
|||
state: "warning",
|
||||
styling: "warning",
|
||||
}
|
||||
|
||||
/**
|
||||
* Pages on which the command line should not be inserted. Set these values with `:set noiframeon ["url1", "url2"]`.
|
||||
*/
|
||||
noiframeon: string[] = []
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue