|
| 1 | +import fs from "node:fs"; |
| 2 | +import nodeTypeInfo from "tree-sitter-java-orchard/src/node-types.json" with { type: "json" }; |
| 3 | + |
| 4 | +fs.writeFileSync( |
| 5 | + "src/node-types.ts", |
| 6 | + `interface Point { |
| 7 | + index: number; |
| 8 | + row: number; |
| 9 | + column: number; |
| 10 | +} |
| 11 | +
|
| 12 | +interface SyntaxNodeBase { |
| 13 | + value: string; |
| 14 | + start: Point; |
| 15 | + end: Point; |
| 16 | + comments?: CommentNode[]; |
| 17 | +} |
| 18 | +
|
| 19 | +interface NamedNodeBase extends SyntaxNodeBase { |
| 20 | + isNamed: true; |
| 21 | + fieldName: string | null; |
| 22 | + children: SyntaxNode[]; |
| 23 | + namedChildren: NamedNode[]; |
| 24 | +} |
| 25 | +
|
| 26 | +export interface UnnamedNode<T extends UnnamedType = UnnamedType> extends SyntaxNodeBase { |
| 27 | + type: T; |
| 28 | + isNamed: false; |
| 29 | + fieldName: string | null; |
| 30 | +} |
| 31 | +
|
| 32 | +export interface CommentNode extends SyntaxNodeBase { |
| 33 | + type: SyntaxType.BlockComment | SyntaxType.LineComment; |
| 34 | + leading: boolean; |
| 35 | + trailing: boolean; |
| 36 | + printed: boolean; |
| 37 | + enclosingNode?: SyntaxNode; |
| 38 | + precedingNode?: SyntaxNode; |
| 39 | + followingNode?: SyntaxNode; |
| 40 | +} |
| 41 | +
|
| 42 | +type PickNamedType<Node, T extends SyntaxType> = Node extends { type: T; isNamed: true } ? Node : never; |
| 43 | +
|
| 44 | +export type NamedNode<T extends NamedType = NamedType> = PickNamedType<SyntaxNode, T>; |
| 45 | +
|
| 46 | +export const enum SyntaxType { |
| 47 | + ERROR = "ERROR", |
| 48 | +${nodeTypeInfo |
| 49 | + .filter(({ named, subtypes }) => named && !subtypes?.length) |
| 50 | + .map( |
| 51 | + ({ type }) => |
| 52 | + ` ${getSyntaxKindFromString(type)} = ${JSON.stringify(type)},` |
| 53 | + ) |
| 54 | + .join("\n")} |
| 55 | +}; |
| 56 | +
|
| 57 | +export type NamedType = Exclude<SyntaxType, SyntaxType.BlockComment | SyntaxType.LineComment>; |
| 58 | +
|
| 59 | +export type UnnamedType = ${nodeTypeInfo |
| 60 | + .filter(({ named }) => !named) |
| 61 | + .map(({ type }) => JSON.stringify(type)) |
| 62 | + .join(" | ")}; |
| 63 | +
|
| 64 | +export type TypeString = NamedType | UnnamedType; |
| 65 | +
|
| 66 | +export type SyntaxNode = ErrorNode | ${nodeTypeInfo |
| 67 | + .filter(({ type }) => !type.endsWith("_comment")) |
| 68 | + .map(getTypeExprFromRef) |
| 69 | + .join(" | ")}; |
| 70 | +
|
| 71 | +export interface ErrorNode extends NamedNodeBase { |
| 72 | + type: SyntaxType.ERROR; |
| 73 | +} |
| 74 | +
|
| 75 | +${nodeTypeInfo |
| 76 | + .filter(({ named }) => named) |
| 77 | + .map(({ type, subtypes, fields }) => |
| 78 | + subtypes?.length |
| 79 | + ? `export type ${getTypeNameFromString(type)} = ${subtypes.map(getTypeExprFromRef).join(" | ")};` |
| 80 | + : `export interface ${getTypeNameFromString(type)} extends NamedNodeBase { |
| 81 | + type: SyntaxType.${getSyntaxKindFromString(type)}; |
| 82 | +${ |
| 83 | + fields && Object.keys(fields).length |
| 84 | + ? `${Object.entries(fields as unknown as Record<string, NodeTypeChildren>) |
| 85 | + .map(([field, children]) => { |
| 86 | + let fieldName = `${field}Node`; |
| 87 | + let type = children.types.length |
| 88 | + ? children.types.map(t => getTypeExprFromRef(t)).join(" | ") |
| 89 | + : "UnnamedNode"; |
| 90 | + if (children.multiple) { |
| 91 | + if (children.types.length > 1) { |
| 92 | + type = `(${type})`; |
| 93 | + } |
| 94 | + type += "[]"; |
| 95 | + fieldName += "s"; |
| 96 | + } |
| 97 | + const opt = children.required || children.multiple ? "" : "?"; |
| 98 | + return ` ${fieldName}${opt}: ${type};`; |
| 99 | + }) |
| 100 | + .join("\n")} |
| 101 | +}` |
| 102 | + : "}" |
| 103 | +}` |
| 104 | + ) |
| 105 | + .join("\n\n")} |
| 106 | +` |
| 107 | +); |
| 108 | + |
| 109 | +interface NodeTypeRef { |
| 110 | + type: string; |
| 111 | + named: boolean; |
| 112 | + isError?: boolean; |
| 113 | +} |
| 114 | + |
| 115 | +interface NodeTypeChildren { |
| 116 | + multiple: boolean; |
| 117 | + required: boolean; |
| 118 | + types: NodeTypeRef[]; |
| 119 | +} |
| 120 | + |
| 121 | +function isIdentifier(str: string) { |
| 122 | + return /^[a-z$_][a-z0-9$_]*$/i.test(str); |
| 123 | +} |
| 124 | + |
| 125 | +function mangleNameToIdentifier(str: string) { |
| 126 | + let sb = "$"; |
| 127 | + for (let i = 0; i < str.length; ++i) { |
| 128 | + const char = str.charAt(i); |
| 129 | + if (/[a-z0-9_]/i.test(char)) { |
| 130 | + sb += char; |
| 131 | + } else { |
| 132 | + sb += "$" + str.charCodeAt(i) + "$"; |
| 133 | + } |
| 134 | + } |
| 135 | + return sb; |
| 136 | +} |
| 137 | + |
| 138 | +function toCapitalCase(str: string) { |
| 139 | + return str |
| 140 | + .replace(/^[a-z]/, t => t.toUpperCase()) |
| 141 | + .replace(/_[a-zA-Z]/g, t => t.substring(1).toUpperCase()); |
| 142 | +} |
| 143 | + |
| 144 | +function getTypePrefixFromString(str: string) { |
| 145 | + return isIdentifier(str) ? toCapitalCase(str) : mangleNameToIdentifier(str); |
| 146 | +} |
| 147 | + |
| 148 | +function getTypeNameFromString(str: string) { |
| 149 | + return getTypePrefixFromString(str) + "Node"; |
| 150 | +} |
| 151 | + |
| 152 | +function getSyntaxKindFromString(str: string) { |
| 153 | + return getTypePrefixFromString(str); |
| 154 | +} |
| 155 | + |
| 156 | +function getTypeExprFromRef({ type, named }: { type: string; named: boolean }) { |
| 157 | + return named |
| 158 | + ? getTypeNameFromString(type) |
| 159 | + : `UnnamedNode<${JSON.stringify(type)}>`; |
| 160 | +} |
0 commit comments