UNPKG

@maniascript/api

Version:

Maniascript API generator

262 lines (261 loc) 10.1 kB
import { DocLexer } from '../antlr/DocLexer.js'; import { generateFromParseResult, parse } from './doc-generator.js'; class APIVisitorError extends Error { } function isDefined(value, message) { if (value === null || value === undefined) { throw new APIVisitorError(message); } return value; } function getCtxDocumentation(ctx, tokens) { if (ctx.start !== null) { const docCommentTokens = tokens.getHiddenTokensToLeft(ctx.start.tokenIndex, DocLexer.channelNames.indexOf('DOC_COMMENT')); if (docCommentTokens !== undefined && docCommentTokens.length > 0) { const docToken = docCommentTokens[docCommentTokens.length - 1]; const doc = docToken.text; // Check that the doc block is not empty if (doc !== undefined && doc.slice(3, -2).trim() !== '') { const result = parse(doc); if (!result.success) { let errorList = ''; for (const error of result.errors) { errorList += `${error.line.toString()}:${error.column.toString()} - ${error.message}\n`; } throw new APIVisitorError(`Failed to parse documentation comment (${docToken.line.toString()}:${docToken.column.toString()}):\n${errorList}`); } let parsedDocumentation = generateFromParseResult(result); if (Object.keys(parsedDocumentation).length === 0) { parsedDocumentation = undefined; } return { rawDocumentation: doc, parsedDocumentation }; } } } return {}; } class APIVisitor { tokens; constructor(tokens) { this.tokens = tokens; } visitTypeLiteral(ctx) { return { category: 'literal', name: isDefined(ctx.getChild(0)?.getText(), 'Variable name is undefined') }; } visitTypeClass(ctx) { return { category: 'class', name: ctx.IDENTIFIER().getText() }; } visitTypeEnum(ctx) { return { category: 'enum', namespace: isDefined(ctx.IDENTIFIER(0)?.getText(), 'Enum namespace is undefined'), name: isDefined(ctx.IDENTIFIER(1)?.getText(), 'Enum name is undefined') }; } visitTypeArrayValue(ctx) { const typeLiteral = ctx.typeLiteral(); if (typeLiteral !== null) { return this.visitTypeLiteral(typeLiteral); } else { const typeClass = ctx.typeClass(); if (typeClass !== null) { return this.visitTypeClass(typeClass); } else { const typeEnum = ctx.typeEnum(); if (typeEnum !== null) { return this.visitTypeEnum(typeEnum); } else { throw new Error('Unknown array type'); } } } } visitTypeArray(ctx) { const type = { category: 'array' }; const newType = ctx.type(); if (newType !== null) { type.type = this.visitType(newType); } else { const oldType = ctx.typeArrayValue(); if (oldType !== null) { type.type = this.visitTypeArrayValue(oldType); } else { throw new APIVisitorError('Unknown array type'); } } return type; } visitType(ctx) { let type; const typeLiteral = ctx.typeLiteral(); if (typeLiteral !== null) { type = this.visitTypeLiteral(typeLiteral); } else { const typeClass = ctx.typeClass(); if (typeClass !== null) { type = this.visitTypeClass(typeClass); } else { const typeEnum = ctx.typeEnum(); if (typeEnum !== null) { type = this.visitTypeEnum(typeEnum); } else { const typeArray = ctx.typeArray(); if (typeArray !== null) { type = this.visitTypeArray(typeArray); } else { throw new APIVisitorError('Unknown type'); } } } } type.isValueConst = (ctx._isValueConst !== undefined) ? true : undefined; type.isPointer = (ctx._isPointer !== undefined) ? true : undefined; type.isPointerConst = (ctx._isPointerConst !== undefined) ? true : undefined; return type; } visitVariableDeclaration(ctx) { const documentation = getCtxDocumentation(ctx, this.tokens); return { rawDocumentation: documentation.rawDocumentation, documentation: documentation.parsedDocumentation, type: this.visitType(isDefined(ctx._variableType, 'Variable type is undefined')), isConst: (ctx._isConst !== undefined) ? true : undefined }; } visitFunctionParameter(ctx) { return { type: this.visitType(isDefined(ctx._parameterType, 'Function parameter type is undefined')), name: isDefined(ctx._name?.text, 'Function paramter name is undefined') }; } visitFunctionDeclaration(ctx) { const documentation = getCtxDocumentation(ctx, this.tokens); const functionDeclaration = { rawDocumentation: documentation.rawDocumentation, documentation: documentation.parsedDocumentation, type: this.visitType(isDefined(ctx._functionType, 'Function type is undefined')), parameters: [] }; const parameters = ctx.functionParameterList()?._parameters; if (parameters !== undefined) { for (const parameter of parameters) { functionDeclaration.parameters.push(this.visitFunctionParameter(parameter)); } } return functionDeclaration; } visitEnumDeclaration(ctx) { const values = []; for (const value of ctx.enumValueList()._values) { if (value._name?.text !== undefined) { values.push(value._name.text); } } const documentation = getCtxDocumentation(ctx, this.tokens); return { rawDocumentation: documentation.rawDocumentation, documentation: documentation.parsedDocumentation, values }; } visitMemberDeclarationList(ctx) { const content = {}; for (const enumDeclaration of ctx.enumDeclaration()) { if (enumDeclaration._name?.text !== undefined) { if (content.enums === undefined) { content.enums = {}; } content.enums[enumDeclaration._name.text] = this.visitEnumDeclaration(enumDeclaration); } } for (const functionDeclaration of ctx.functionDeclaration()) { if (functionDeclaration._name?.text !== undefined) { if (content.functions === undefined) { content.functions = {}; } if (functionDeclaration._name.text in content.functions) { content.functions[functionDeclaration._name.text].push(this.visitFunctionDeclaration(functionDeclaration)); } else { content.functions[functionDeclaration._name.text] = [this.visitFunctionDeclaration(functionDeclaration)]; } } } for (const variableDeclaration of ctx.variableDeclaration()) { if (variableDeclaration._name?.text !== undefined) { if (content.variables === undefined) { content.variables = {}; } content.variables[variableDeclaration._name.text] = this.visitVariableDeclaration(variableDeclaration); } } return content; } visitClassDeclaration(api, ctx) { if (ctx._name?.text !== undefined) { api.classNames.push(ctx._name.text); const classContent = {}; if (ctx._content !== undefined) { Object.assign(classContent, this.visitMemberDeclarationList(ctx._content)); } if (ctx._parent?.text !== undefined) { Object.assign(classContent, { parent: ctx._parent.text }); } const documentation = getCtxDocumentation(ctx, this.tokens); classContent.rawDocumentation = documentation.rawDocumentation; classContent.documentation = documentation.parsedDocumentation; api.classes[ctx._name.text] = classContent; } return api; } visitNamespaceDeclaration(api, ctx) { if (ctx._name?.text !== undefined) { api.namespaceNames.push(ctx._name.text); const classContent = {}; if (ctx._content !== undefined) { Object.assign(classContent, this.visitMemberDeclarationList(ctx._content)); } const documentation = getCtxDocumentation(ctx, this.tokens); classContent.rawDocumentation = documentation.rawDocumentation; classContent.documentation = documentation.parsedDocumentation; api.namespaces[ctx._name.text] = classContent; } return api; } visit(ctx) { let api = { classNames: [], namespaceNames: [], classes: {}, namespaces: {} }; for (const classDeclaration of ctx.classDeclaration()) { api = this.visitClassDeclaration(api, classDeclaration); } for (const namespaceDeclaration of ctx.namespaceDeclaration()) { api = this.visitNamespaceDeclaration(api, namespaceDeclaration); } return api; } } export { APIVisitor, APIVisitorError };