UNPKG

@vuedoc/parser

Version:

Generate a JSON documentation for a Vue file

1,608 lines (1,293 loc) 57.1 kB
import { Value, generateUndefineValue, generateNullGenerator, generateObjectGenerator, generateArrayGenerator, generateAnyGenerator } from '../entity/Value.js'; import { Syntax, Type, CompositionTypes, Feature, CompositionFeature } from '../lib/Enum.js'; import { clear, get } from '@b613/utils/lib/object.js'; import { Entry } from '../../types/Entry.js'; import { Parser } from '../../types/Parser.js'; import { FileSystem } from '../../types/FileSystem.js'; import { Composition } from '../lib/Composition.js'; import { PropType } from '@b613/utils/typings.js'; import { DTS } from '../lib/DTS.js'; import * as Babel from '@babel/types'; export type CompositionValueOptions = Required<Pick<Babel.VariableDeclarator, 'init'>> & { key?: string; local?: string; id: { type: Babel.Node['type']; typeAnnotation?: Babel.Node; extra?: Partial<PropType<Parser.AST.Node, 'extra'>>; }; }; type CompositionValue = { ref?: Parser.Value<any>; node?: Babel.Node; argument?: Babel.Node; tsValue?: Parser.AST.TSValue; composition?: Parser.CompiledComposition; }; const NATIVE_OBJECTS = ['Symbol', 'BigInt', 'Boolean', 'Number']; const DUPLICATED_SPACES_RE = /\s+/g; const BOOLEAN_OPERATORS = ['&&', '||', '==', '===', '!=', '!==', 'in', 'instanceof', '>', '>=', '<', '<=']; const BITWISE_OPERATORS = ['&', '|', '^', '<<', '>>', '>>>', '|>']; const NUMERIC_OPERATORS = ['*', '+', '-', '/', '**', '%']; const IGNORED_SCOPED_TYPES: string[] = [Type.any, Type.unknown]; const FUNCTION_EXPRESSIONS = [ Syntax.ObjectMethod, Syntax.ClassMethod, Syntax.ClassPrivateMethod, Syntax.ObjectMethod, Syntax.FunctionExpression, Syntax.ArrowFunctionExpression, Syntax.FunctionDeclaration, Syntax.TSDeclareMethod, Syntax.TSDeclareFunction, Syntax.TSFunctionType, ]; const TS_FUNCTION_EXPRESSIONS = [ Syntax.TSDeclareMethod, Syntax.TSDeclareFunction, Syntax.TSFunctionType, Syntax.TSDeclareFunction, ]; export function* generateName(prefix: string): Generator<string, string> { let index = 0; while (true) { yield `${prefix}$${index++}`; } } const LEFT_SIDEPART_KEY = '$leftSidePart'; const generateObjectName = generateName('object'); const generateArrayName = generateName('array'); function normalizeType(item: { type: Parser.Type | Parser.Type[] }) { item.type = item.type instanceof Array ? item.type.map(Value.parseNativeType) : Value.parseNativeType(item.type); } function copyComments(dest: Babel.Node, source: Babel.Node) { if (source.extra.comment && !dest.extra.comment) { dest.extra.comment = source.extra.comment; } } export class AbstractParser<Source extends Parser.Source, Root> { readonly fs: FileSystem; root: Root; emitter: Parser.Interface; source: Source; features: Parser.Feature[]; parserOptions: Parser.Options; scope: Parser.Scope<any, any>; private scopeRef: Parser.Scope<any, any>; composition: Composition; constructor(root: Root, emitter: Parser.Interface, source: Source, scope: Parser.Scope<any, any>) { this.root = root; this.emitter = emitter; this.source = source; this.parserOptions = emitter.options; this.composition = emitter.composition; this.features = emitter.features; this.fs = emitter.fs; this.scopeRef = scope; this.scope = { ...scope }; } static isFunction(node: { type: string }) { return FUNCTION_EXPRESSIONS.includes(node.type as any); } static isTsFunction(node: Babel.Node) { if (TS_FUNCTION_EXPRESSIONS.includes(node.type as any)) { return true; } if ('typeAnnotation' in node) { return AbstractParser.isTsFunction(node.typeAnnotation); } return false; } static hasComments(node: Babel.Node) { // TODO Handle node.innerComments return 'leadingComments' in node || 'trailingComments' in node; } reset() { clear(this.scope); Object.assign(this.scope, this.scopeRef); } sync(scope: Parser.Scope<any, any> = {}) { this.scope = { ...this.scopeRef, ...this.scope, ...scope }; return this; } emit(entry: Entry.Type) { if (this.root) { if ('type' in entry) { normalizeType(entry); } if ('params' in entry) { entry.params.forEach(normalizeType); normalizeType(entry.returns); } if ('arguments' in entry) { entry.arguments.forEach(normalizeType); } (this.root as any).emit(entry); } } emitWarning(message: string) { this.emitter.emitWarning(message); } emitError(message: string) { this.emitter.emitError(message); } parse(node) { switch (node.type) { case Syntax.ObjectProperty: this.parseObjectProperty(node); break; case Syntax.ObjectExpression: this.parseObjectExpression(node); break; case Syntax.CallExpression: this.parseCallExpression(node); break; case Syntax.ArrayExpression: this.parseArrayExpression(node); break; case Syntax.Identifier: this.parseIdentifier(node); break; case Syntax.ExpressionStatement: this.parseExpressionStatement(node); break; default: if (AbstractParser.isFunction(node)) { this.parseFunctionExpression(node.body); } break; } } parseFunctionDeclaration(node: Babel.FunctionDeclaration) { this.registerFunctionDeclaration(node); } registerFunctionDeclaration(node) { if (node.id) { const fname = this.parseKey(node); const value = generateUndefineValue.next().value; this.setScopeValue(fname, node, value); } } /* istanbul ignore next */ /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ parseEntryComment(_entry: Entry.Type, _node) {} /* istanbul ignore next */ /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ parseArrayExpression(_node: Babel.ArrayExpression) {} /* istanbul ignore next */ /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ parseCallExpression(_node: Babel.CallExpression) {} /* istanbul ignore next */ /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ parseFunctionExpression(_node: Babel.FunctionExpression) {} /* istanbul ignore next */ /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ parseExpressionStatement(_node: Babel.ExpressionStatement) {} /* istanbul ignore next */ /* eslint-disable no-unused-vars */ /* eslint-disable class-methods-use-this */ parseObjectExpression(_node: Babel.ObjectExpression) {} parseObjectProperty(node: Babel.ObjectProperty) { this.parse(node.value); } parseIdentifier(node: Babel.Identifier) { const ref = this.getScopeValue(node.name); if (ref && ref.node.value !== node) { this.parse(ref.node.value); } } parseVariableDeclaration(node: Parser.AST.VariableDeclaration) { node.declarations .filter(({ type }) => type === Syntax.VariableDeclarator) .forEach((declaration) => this.parseVariableDeclarator(declaration, node)); } parseNonCompositionRef(decoratorValue: CompositionValue, ref: Parser.Value<any>, nodeTyping: Babel.Node, initNode: Babel.Node) { if (!decoratorValue.composition) { const tsType = this.getTSType(nodeTyping, this.getNodeType(initNode)); if (tsType !== Type.unknown) { ref.type = tsType; } } } parseVariableDeclarator(declarator: Parser.AST.VariableDeclarator, node: Parser.AST.VariableDeclaration) { if (declarator.init) { this.setLeftSidePart(declarator.init, node, declarator); } switch (declarator.id.type) { case Syntax.ArrayPattern: this.parseVariableDeclarationArrayPattern(declarator, declarator.id); break; case Syntax.ObjectPattern: this.parseVariableDeclarationObjectPattern(declarator, declarator.id); break; default: { const name = declarator.id.name; const decoratorValue = this.getDeclaratorValue({ id: declarator.id as any, init: declarator.init, }); const ref = decoratorValue.ref || generateUndefineValue.next().value; const initNode = declarator.init ? declarator.init : declarator; const nodeValue = decoratorValue.argument || initNode; const nodeComment = node.declarations.length > 1 ? declarator : node; const nodeTyping = declarator.init && 'typeParameters' in declarator.init ? declarator.init.typeParameters : 'typeAnnotation' in declarator.id ? declarator.id?.typeAnnotation : declarator; this.parseNonCompositionRef(decoratorValue, ref, nodeTyping, initNode); this.setScopeValue(name, nodeValue, ref, { nodeTyping, nodeComment, tsValue: decoratorValue.tsValue, composition: decoratorValue.composition, isFunction: AbstractParser.isFunction(initNode) || AbstractParser.isTsFunction(initNode), }); break; } } } parseVariableDeclarationArrayPattern(declarator: Parser.AST.VariableDeclarator, id: Babel.ArrayPattern) { const array = generateArrayGenerator.next().value; declarator.id.name = generateArrayName.next().value; this.setScopeValue(declarator.id.name, declarator.init, array); this.parseVariableDeclarationElements(id.elements as any, declarator as any); } parseVariableDeclarationObjectPattern(declarator: Parser.AST.VariableDeclarator, id: Babel.ObjectPattern) { const object = generateObjectGenerator.next().value; declarator.id.name = generateObjectName.next().value; this.setScopeValue(declarator.id.name, declarator.init, object); this.parseVariableDeclarationElements(id.properties, declarator as any); } getVariableDeclarationElementIdentifier( key: string | number, options: CompositionValueOptions, defaultValue?: Parser.Value<any> ) { const decoratorValue = this.getDeclaratorValue({ init: options.init, id: options.id as any, }); const memberObject = decoratorValue?.ref || this.getValue(options.init); const memberValue = memberObject?.value ? memberObject?.value[key] : undefined; if (typeof memberValue === 'undefined' && defaultValue) { return defaultValue; } const memberType = memberValue === null || memberValue === undefined ? Type.unknown : typeof memberValue; const memberRawValue = JSON.stringify(memberValue) || ''; return new Value(memberType, memberValue, memberRawValue); } parseVariableDeclarationElements( // eslint-disable-next-line max-len elements: Array<Babel.ObjectProperty | Babel.ObjectMethod | Babel.SpreadElement | Babel.Identifier | Babel.MemberExpression | Babel.AssignmentPattern | Babel.ArrayPattern | Babel.ObjectPattern | Babel.TSParameterProperty | Babel.TSAsExpression | Babel.TSTypeAssertion | Babel.TSNonNullExpression | Babel.RestElement>, options: CompositionValueOptions ) { for (let index = 0; index < elements.length; index++) { const element = elements[index]; if (element === null) { continue; } switch (element.type) { case Syntax.Identifier: { const name = element.name; const memberRef = this.getVariableDeclarationElementIdentifier(index, options); this.setScopeValue(name, element, memberRef); break; } case Syntax.AssignmentPattern: { const name = this.getValue(element.left).value; const defaultValue = this.getValue(element.right); const memberRef = this.getVariableDeclarationElementIdentifier(index, options, defaultValue); this.setScopeValue(name, element, memberRef); break; } case Syntax.ArrayPattern: { const tsValue = this.getTSValue(options.init); const node = tsValue.kind === Type.array && 'elements' in tsValue.node ? (tsValue.node as Babel.ArrayExpression).elements[index] : { type: Syntax.NullLiteral, }; this.parseVariableDeclarationElements(element.elements as any, { id: element, init: node as any, }); break; } case Syntax.ObjectProperty: { let name: string | undefined; let source: string; let nodeComment; let defaultValue: Value | undefined; let defaultNode: Babel.Expression | undefined; if ('key' in element) { source = this.parseKey(element); } switch (element.value.type) { case Syntax.Identifier: name = element.value.name; nodeComment = element; break; case Syntax.AssignmentPattern: name = this.getValue(element.value.left).value; nodeComment = element.value.left; defaultValue = this.getValue(element.value.right); defaultNode = element.value.right; break; case Syntax.ObjectPattern: { const tsValue = this.getTSValue(options.init); const properties = 'type' in tsValue.node && tsValue.node.type === Syntax.ObjectExpression ? tsValue.node.properties : []; const node: Babel.ObjectProperty | null = properties.find((property) => this.parseKey(property as any) === source) as any || null; this.parseVariableDeclarationElements(element.value.properties, { id: element, init: node?.value as any, }); name = null; break; } } if (name) { switch (options.init.type) { case Syntax.MemberExpression: { const memberKey = source || name; const memberValue = this.getMemberValue(options.init, memberKey) || defaultValue || generateUndefineValue.next().value; const memberNode = memberValue === defaultValue ? defaultNode : element; this.setScopeValue(name, memberNode, memberValue, { source, nodeComment }); break; } case Syntax.ArrayExpression: { break; } case Syntax.ObjectExpression: { const propertyValue: Babel.ObjectProperty = options.init.properties.find((item) => { return 'key' in item && this.getValue(item.key).value === name; }) || defaultNode as any; if (defaultValue && defaultValue.raw !== Type.undefined) { this.setScopeValue(name, propertyValue, defaultValue, { source, nodeComment }); } else if (propertyValue) { const ref = 'value' in propertyValue ? this.getValue(propertyValue.value) : generateUndefineValue.next().value; this.setScopeValue(name, propertyValue, ref, { source, nodeComment }); } else { const ref = generateUndefineValue.next().value; this.setScopeValue(name, options.init, ref, { source, nodeComment }); } break; } default: { const decoratorValue = this.getDeclaratorValue({ key: source, local: name, init: options.init, id: options.id, }); const ref = defaultValue || decoratorValue.ref || generateUndefineValue.next().value; const initNode = decoratorValue.node || element; const nodeValue = decoratorValue.argument || initNode; element.extra.$declarator = options; if (decoratorValue.node) { copyComments(nodeComment, decoratorValue.node); } this.parseNonCompositionRef(decoratorValue, ref, element, initNode); this.setScopeValue(name, nodeValue, ref, { source, nodeComment, nodeTyping: element, composition: decoratorValue.composition, tsValue: decoratorValue.tsValue, }); break; } } } break; } case Syntax.RestElement: case Syntax.SpreadElement: this.parseVariableDeclarationElementsRest(element, options); break; } } } parseVariableDeclarationElementsRest( element: Babel.RestElement | Babel.SpreadElement, options: CompositionValueOptions ) { switch (element.argument.type) { case Syntax.Identifier: { const decoratorValue = this.getDeclaratorValue({ init: options.init, id: options.id as any, }); const rest = decoratorValue.ref || this.getValue(options.init); this.setScopeValue(element.argument.name, element, rest, { nodeTyping: element, composition: decoratorValue.composition, tsValue: decoratorValue.tsValue, }); break; } } } setScopeValue( key: string, node, value: Parser.Value<any>, { source = undefined, nodeComment = node, nodeTyping = node, isFunction = false, forceType = false, global = false, tsValue = undefined, composition = undefined, } = {} ) { if (!forceType) { const ref = this.getScopeValue(key); if (ref && ref.value.type !== value.type) { if ((typeof ref.value.type === 'string' && !IGNORED_SCOPED_TYPES.includes(ref.value.type)) || (ref.value.type instanceof Array && ref.value.type.some((item) => !IGNORED_SCOPED_TYPES.includes(item))) ) { value.type = Type.unknown; } } } const scopeValue: Parser.ScopeEntry = { key, value, source, tsValue, function: isFunction || value.function, computed: node.computed || node.extra.computed, composition, node: { type: nodeTyping || node, value: node, comment: nodeComment || node, }, }; if (source) { scopeValue.source = source; } if (key in this.scope) { const ref = this.getScopeValue(key); if (ref?.composition) { scopeValue.composition = { ...scopeValue.composition, ...ref.composition }; } } this.scope[key] = scopeValue; if (global && this.root) { (this.root as any).scope[key] = this.scope[key]; } } getScopeValue(key: string): Parser.ScopeEntry<any, any, any> | null { if (key in this.scope) { const ref = this.scope[key]; if ('node' in ref) { return ref; } } return null; } setLeftSidePart(node: Babel.Expression, parent: Parser.AST.VariableDeclaration, declarator: Parser.AST.VariableDeclarator) { node.extra[LEFT_SIDEPART_KEY] = { declarator, parent }; } getLeftSidePart<T = null>(node, defaultResult: T = null): T | { parent: Babel.Node, declarator: Parser.AST.VariableDeclarator } { return node.extra[LEFT_SIDEPART_KEY] || defaultResult; } hasLeftSidePart(node: Parser.AST.Node) { return LEFT_SIDEPART_KEY in node.extra; } getReturnNode(node: Babel.Node): Babel.Node | null { const _node = node as any; if (AbstractParser.isFunction(_node)) { const blockNode = _node.body?.body || _node.body; const returnNode: Babel.Node = blockNode instanceof Array ? blockNode.length === 1 ? blockNode[0] : blockNode.find((node) => node.type === Syntax.ReturnStatement) : blockNode; return returnNode?.type === Syntax.ReturnStatement ? returnNode.argument : returnNode; } return null; } getReturnType(node: Babel.Node, defaultType: Parser.Type = Type.unknown): Parser.Type | Parser.Type[] { let type: Parser.Type | Parser.Type[] = defaultType; const _node = node as any; if (_node.returnType) { type = this.getValue(_node.returnType.typeAnnotation).value; } else if (_node.typeAnnotation?.typeAnnotation) { type = _node.typeAnnotation.typeAnnotation.type === Syntax.TSFunctionType ? this.getTSValue(_node.typeAnnotation.typeAnnotation.typeAnnotation).type as string : this.getTSValue(_node.typeAnnotation.typeAnnotation).type as string; } else if (AbstractParser.isFunction(node)) { const returnNode: Babel.Node = this.getReturnNode(node); if (returnNode) { switch (returnNode.type) { case Syntax.Identifier: type = this.getValue(returnNode).type; break; case Syntax.MemberExpression: type = this.getMemberExpression(returnNode).type; break; default: type = this.getNodeType(returnNode, Type.void); break; } } else { type = Type.void; } if ('async' in node && node.async) { type = `${Type.Promise}<${type}>`; } } return type; } getNodeType(node: Babel.Node, defaultType?: Parser.Type | Parser.Type[]) { let type: Parser.Type | Parser.Type[] = defaultType; switch (node.type) { case Syntax.Identifier: { const ref = this.getScopeValue(node.name); type = ref ? ref.value.type : this.getValue(node).type; break; } case Syntax.ReturnStatement: type = node.argument ? this.getNodeType(node.argument, defaultType) : defaultType; break; case Syntax.CallExpression: type = 'object' in node.callee && 'name' in node.callee.object && node.callee.object.name === 'Math' ? Type.number : Type.unknown; break; case Syntax.UpdateExpression: type = Type.number; break; case Syntax.StringLiteral: case Syntax.TemplateLiteral: type = Type.string; break; case Syntax.BooleanLiteral: type = Type.boolean; break; case Syntax.LogicalExpression: case Syntax.BinaryExpression: case Syntax.AssignmentExpression: type = this.getExpressionType(node); break; case Syntax.ConditionalExpression: type = this.getConditionalExpressionType(node); break; case Syntax.NumericLiteral: type = Type.number; break; case Syntax.BigIntLiteral: type = Type.bigint; break; case Syntax.RegExpLiteral: type = Type.regexp; break; case Syntax.ArrayExpression: type = DTS.parseType(this.getValue(node)); break; case Syntax.ObjectExpression: type = DTS.parseType(this.getValue(node)); break; case Syntax.NullLiteral: type = Type.unknown; break; case Syntax.UnaryExpression: type = this.getUnaryExpressionType(node); break; case Syntax.NewExpression: type = 'name' in node.callee ? Value.parseNativeType(node.callee.name) : Type.unknown; break; case Syntax.MemberExpression: type = this.getMemberExpression(node).type; break; default: if (AbstractParser.isFunction(node)) { type = Type.function; } break; } return type; } private getCompositionReturnType(argument: Babel.Node, options: CompositionValueOptions) { if ('typeAnnotation' in options.id && options.id.typeAnnotation) { return this.getTSValue(options.id.typeAnnotation).type as string; } if ('returnType' in argument) { return this.getTSValue(argument.returnType).type as string; } return this.getReturnType(argument, Type.unknown); } getTypeParameterDeclaration(node: Babel.Node): Required<Babel.TypeParameterDeclaration> { return 'typeParameters' in node && node.typeParameters && 'params' in node.typeParameters ? node.typeParameters as Required<Babel.TypeParameterDeclaration> : null; } getTypeParameterValue(functionDeclaration: Babel.Node, callExpression: Babel.CallExpression) { const functionHasTypeParameters = !!this.getTypeParameterDeclaration(functionDeclaration); if (functionHasTypeParameters) { const callTypeParameters = this.getTypeParameterDeclaration(callExpression); if (callTypeParameters) { if ('returnType' in functionDeclaration && 'typeAnnotation' in functionDeclaration.returnType && 'typeParameters' in functionDeclaration.returnType.typeAnnotation) { const originalParams = [...functionDeclaration.returnType.typeAnnotation.typeParameters.params]; functionDeclaration.returnType.typeAnnotation.typeParameters.params.splice( 0, callTypeParameters.params.length, ...callTypeParameters.params ); const tsValue = this.getTSValue(functionDeclaration, true); functionDeclaration.returnType.typeAnnotation.typeParameters.params = originalParams as any; delete functionDeclaration.extra.$tsvalue; return DTS.parseTsValueType(tsValue); } } } return null; } getCompositionValue(options: CompositionValueOptions): CompositionValue { let ref: Parser.Value<any>; let refNode: Babel.Node; let argumentNode: Babel.Node; let tsValue = this.getDeclaratorType(options); let fname: string; const init: Babel.Node = options.init.type === Syntax.TSAsExpression ? options.init.expression : options.init; if (init.type === Syntax.CallExpression && 'callee' in init && 'name' in init.callee) { fname = init.callee.name; let composition = this.composition.get(fname, [ // order is important CompositionFeature.props, CompositionFeature.data, CompositionFeature.computed, CompositionFeature.methods, CompositionFeature.events, ]); if (composition) { if (composition.returningType) { tsValue = { type: composition.returningType, node: options.id as any, }; ref = generateUndefineValue.next().value; ref.type = composition.returningType; } if (init.typeParameters) { if (composition.typeParameterIndex >= 0 && composition.typeParameterIndex < init.typeParameters.params.length) { tsValue = this.getTSValue(init.typeParameters.params[composition.typeParameterIndex]); if (ref) { ref.type = DTS.parseTsValueType(tsValue); } } } if (!ref && composition.valueCanBeUndefined) { ref = generateUndefineValue.next().value; } if (typeof tsValue?.type === 'string') { const tsref = this.getScopeValue(tsValue.type); if (tsref) { tsValue = this.getTSValue(tsref.node.value); } } if ('parseEntryValue' in composition && typeof composition.parseEntryValue === 'function') { ref = composition.parseEntryValue(init, this as any); } else if (typeof composition.valueIndex === 'number' && composition.valueIndex >= 0 && composition.valueIndex < init?.arguments?.length) { const argument = init.arguments.at(composition.valueIndex); ref = this.getValue(argument); argumentNode = argument; if (argument.type === Syntax.CallExpression) { const nested = this.getCompositionValue({ ...options, init: argument }); if (nested.ref) { ref = nested.ref; } if (nested.node) { argumentNode = nested.node; } if (nested.tsValue) { tsValue = nested.tsValue; } if (nested.composition) { composition = nested.composition; } } if (tsValue) { ref.type = this.getTypeParameterValue(argumentNode, init) || DTS.parseTsValueType(tsValue); if (options.key) { if (typeof tsValue.type === 'object') { ref.type = Array.isArray(tsValue.type) ? tsValue.type : tsValue.type[options.key]; } } else if (options.id.type === Syntax.Identifier || options.id.type === Syntax.ObjectProperty) { ref.type = this.getTypeParameterValue(argumentNode, init) || DTS.parseTsValueType(tsValue); } } else if (composition.feature === Feature.computed) { if (AbstractParser.isFunction(argument)) { ref.type = this.getCompositionReturnType(argument, options); } else if (argument.type === Syntax.ObjectExpression) { const property = argument.properties.find((property) => { return property.type !== Syntax.SpreadElement && this.parseKey(property) === 'get'; }); if (property && AbstractParser.isFunction(property)) { ref.type = this.getCompositionReturnType(property, options); } } } } return this.getCallExpressionComposition(fname, options, { ref, tsValue, composition, node: refNode, argument: argumentNode }); } } ref = this.getValue(options.init); ref.function = AbstractParser.isFunction(options.init) || AbstractParser.isTsFunction(options.init); if (tsValue) { ref.type = DTS.parseTsValueType(tsValue); } if (options.id.type !== Syntax.Identifier && options.init?.type === Syntax.CallExpression) { ref.value = ''; ref.raw = ''; } return fname ? this.getCallExpressionComposition(fname, options, { ref, tsValue }) : {}; } private parseRaw(ref: Parser.Value<any>) { if (!ref.rawObject) { switch (ref.type) { case Type.object: ref.rawObject = {}; break; case Type.array: ref.rawObject = []; break; } } if (!ref.rawNode) { switch (ref.type) { case Type.object: ref.rawNode = {}; break; case Type.array: ref.rawNode = []; break; } } } private parseCallExpressionCompositionKey(options: CompositionValueOptions, result: CompositionValue) { if (options.key) { if (result.tsValue) { const keyType: string | Parser.AST.TSValue = typeof result.tsValue.type === 'string' ? result.tsValue.type : result.tsValue.type[options.key]; if (!result.ref) { const keyref = this.getScopeValue(options.key); if (keyref) { result.ref = keyref.value; result.tsValue = keyref.tsValue; result.node = keyref.node.value; } else { result.ref = generateUndefineValue.next().value; } } if (typeof result.tsValue.type === 'object' && typeof result.ref.value === 'object') { this.parseRaw(result.ref); const rawRef = options.key in result.ref.rawObject ? result.ref.rawObject[options.key] : generateUndefineValue.next().value; result.ref.value = rawRef.value; result.ref.raw = rawRef.raw; if (typeof result.ref.value === 'undefined' && keyType instanceof Array && keyType.includes(Type.undefined)) { result.ref.raw = 'undefined'; } } if (typeof keyType === 'string' || keyType instanceof Array) { result.ref.type = keyType; } else { result.ref.type = keyType.type as string; result.tsValue = keyType; } result.node = result.tsValue.node[options.key]; } else if (result.ref?.type === Type.object) { this.parseRaw(result.ref); result.ref = options.key in result.ref.rawObject ? result.ref.rawObject[options.key] : generateUndefineValue.next().value; } } } private getCallExpressionComposition(fname: string, options: CompositionValueOptions, result: CompositionValue): CompositionValue { const refScope = this.getScopeValue(fname); if (refScope) { if (refScope.value.type === Type.function && result.composition && result.composition?.feature !== Feature.methods) { const tsValue = this.getTSValue(refScope.node.value); const value = generateUndefineValue.next().value; if (options.key) { if (typeof tsValue.type === 'object') { value.type = Array.isArray(tsValue.type) ? tsValue.type : tsValue.type[options.key]; result.node = tsValue.node[options.key]; } } else if (options.id.type === Syntax.Identifier || options.id.type === Syntax.ObjectProperty) { value.type = this.getTypeParameterValue(refScope.node.value, options.init as Babel.CallExpression) || DTS.parseTsValueType(tsValue); if (!Array.isArray(tsValue.node)) { result.node = tsValue.node as Babel.Node; } } if (result.ref) { result.ref.type = value.type; } else { result.ref = value; result.tsValue = tsValue; result.ref.type = this.getTypeParameterValue(refScope.node.value, options.init as Babel.CallExpression) || (result.ref.type === Type.unknown ? DTS.parseTsValueType(tsValue) : result.ref.type); } } else if (result.ref) { if (AbstractParser.isFunction(refScope.node.value)) { if (options.key) { const ref = this.getScopeValue(options.key); if (ref && 'node' in ref) { result.ref = ref.value; result.argument = ref.node.value; result.node = ref.node.comment; } else { const returnNode = this.getReturnNode(refScope.node.value); if (returnNode) { const returnValue = this.getValue(returnNode); if (returnValue.rawObject && options.key in returnValue.rawObject) { result.ref = returnValue.rawObject[options.key]; result.argument = returnValue.rawNode[options.key]; } } } } } else if (refScope.value.type === Type.function) { result.ref.type = this.getReturnType(refScope.node.value); } else if (refScope.value.type !== Type.unknown) { result.ref.type = refScope.value.type; } } else { result.ref = generateUndefineValue.next().value; result.tsValue = refScope.tsValue; if (result.tsValue) { result.ref.type = this.getTypeParameterValue(refScope.node.value, options.init as Babel.CallExpression) || DTS.parseTsValueType(result.tsValue); } } } this.parseCallExpressionCompositionKey(options, result); if (!result.ref && result.tsValue) { result.ref = DTS.parseValue(result.tsValue.type); } if (result.tsValue && AbstractParser.isTsFunction(result.tsValue.node as Babel.Node)) { result.ref.function = true; result.node = result.tsValue.node as Babel.Node; } else if (result.ref && result.node) { result.ref.function = AbstractParser.isFunction(options.init) || AbstractParser.isTsFunction(options.init); } return result; } getDeclaratorValue(options: CompositionValueOptions) { let declarationValue: CompositionValue; if (options.init) { if (options.init.type === Syntax.CallExpression) { if ('name' in options.init.callee) { declarationValue = this.getCompositionValue(options); if (declarationValue.ref) { declarationValue.ref.$kind = options.init.callee.name; } } } } if (!declarationValue) { declarationValue = { ref: options.init ? this.getValue(options.init) : generateUndefineValue.next().value, }; } return declarationValue; } getDeclaratorType(options: CompositionValueOptions) { let tsValue: Parser.AST.TSValue; if ('typeAnnotation' in options.id) { tsValue = this.getTSValue(options.id.typeAnnotation); } if (options.init) { if (options.init.type === Syntax.TSAsExpression) { tsValue = this.getTSValue(options.init.typeAnnotation); } } return tsValue; } getTSTypeLiteralValue(node: Babel.TSTypeLiteral) { const type = {}; const members = {}; for (const member of node.members) { const key = this.parseKey(member as any); const ts = this.getTSValue(member); const ref = this.getScopeValue(key); if (ref) { ref.node.comment = member; } member.extra.computed = ts.computed || (member as any).computed; members[key] = member; type[key] = 'optional' in member && member.optional ? [ts.type, Type.undefined] : ts.type; } return { type, members }; } parseAssignmentExpression(node: Babel.AssignmentExpression) { if ('name' in node.left) { this.setScopeValue(node.left.name, node.right, this.getValue(node.right)); } } parseKey({ computed = false, id = undefined, key = id, value = undefined, body = undefined }) { const keyName: string = key?.type === Syntax.PrivateName ? key.id.name : key?.name || key?.value; if (computed) { const $value = value || body; switch (key.type) { case Syntax.Identifier: if ($value && $value.type !== Syntax.Identifier) { const ref = this.getValue(key); if (ref.value !== undefined) { return `${ref.value}`; } } break; case Syntax.CallExpression: case Syntax.MemberExpression: return this.getValue(key).raw; } } return keyName; } getTSTypeRaw(node: Babel.Node, defaultType: Parser.Type | Parser.Type[] = Type.unknown) { let type = defaultType; switch (node.type) { case Syntax.TSAsExpression: case Syntax.TSTypeAnnotation: type = this.getTSType(node.typeAnnotation, defaultType); break; case Syntax.TSTypeParameterInstantiation: if (node.params.length) { type = this.getInlineSourceString(node.params[0]); } break; case Syntax.TSFunctionType: case Syntax.TSTypeReference: type = this.getInlineSourceString(node); break; case Syntax.TSUnionType: type = node.types.map((item) => this.getTSTypeRaw(item)); break; case Syntax.TSDeclareMethod: case Syntax.TSDeclareFunction: if ('returnType' in node) { type = this.getTSTypeRaw(node.returnType, defaultType); } break; default: { if ('typeAnnotation' in node && 'typeAnnotation' in node.typeAnnotation) { type = this.getInlineSourceString(node.typeAnnotation.typeAnnotation); } else if (node.type.startsWith('TS')) { type = this.getInlineSourceString(node); } else { const guestType = this.getNodeType(node, type); if (guestType && guestType !== Type.unknown) { type = guestType; } } break; } } return type; } getElementsObjectType(elements: Array<Babel.TSTypeElement | Babel.ObjectProperty | Babel.ObjectMethod>) { const type = {}; const node = {}; for (const item of elements) { const key = this.parseKey(item as any); const tsValue = this.getTSValue(item); type[key] = tsValue; node[key] = item; } return { type, node }; } getTSValue(node: Babel.Node, force = false): Parser.AST.TSValue { if (node.extra.$tsvalue && !force) { return node.extra.$tsvalue as Parser.AST.TSValue; } switch (node.type) { case Syntax.TSAsExpression: case Syntax.TSTypeAnnotation: case Syntax.TSPropertySignature: copyComments(node.typeAnnotation, node); node.extra.$tsvalue = this.getTSValue(node.typeAnnotation, force); break; case Syntax.CallExpression: if (node.typeParameters) { node.extra.$tsvalue = this.getTSValue(node.typeParameters, force); } break; case Syntax.ArrayExpression: { const result = this.getElementsObjectType(node.elements as any); node.extra.$tsvalue = { kind: Type.array, type: Object.values(result.type), node }; break; } case Syntax.ObjectExpression: node.extra.$tsvalue = { kind: Type.object, ...this.getElementsObjectType(node.properties as any), node }; break; case Syntax.ObjectMethod: node.extra.$tsvalue = { node, kind: Type.function, type: this.getNodeType(node) }; break; case Syntax.ObjectProperty: node.extra.$tsvalue = this.getTSValue(node.value, force); break; case Syntax.TSInterfaceDeclaration: node.extra.$tsvalue = { kind: Type.object, ...this.getElementsObjectType(node.body.body) }; break; case Syntax.TSTypeParameterInstantiation: if (node.params.length) { node.extra.$tsvalue = this.getTSValue(node.params[0], force); } break; case Syntax.TSTypeReference: if ('name' in node.typeName && CompositionTypes.includes(node.typeName.name) && node.typeParameters) { const ref = this.getTSValue(node.typeParameters, force); if (!Array.isArray(ref.node) && ref.node.type === Syntax.TSTypeParameterInstantiation) { ref.node = ref.node.params[0]; } node.extra.$tsvalue = { ...ref, computed: true, compositionType: node.typeName.name }; } else if (node.typeParameters) { const typeName = this.getSourceString(node.typeName); const params = node.typeParameters.params.map((param) => this.getInlineSourceString(param)); node.extra.$tsvalue = { node, type: `${typeName}<${params.join(', ')}>` }; } else { node.extra.$tsvalue = { node, type: this.getInlineSourceString(node) }; } break; case Syntax.Identifier: { const ref = this.getScopeValue(node.name); node.extra.$tsvalue = ref ? (ref.tsValue ? ref.tsValue : this.getTSValue(ref.node.value, force)) : { node, type: this.getInlineSourceString(node) }; break; } case Syntax.TSUnionType: node.extra.$tsvalue = { node: node.types, type: node.types.map((item) => this.getTSTypeRaw(item)), }; break; case Syntax.TSTypeParameter: if (node.constraint) { node.extra.$tsvalue = this.getTSValue(node.constraint, force); } else { node.extra.$tsvalue = { type: node.name, node }; } break; case Syntax.TSTypeLiteral: { const result = this.getTSTypeLiteralValue(node); node.extra.$tsvalue = { ...result, node: result.members }; break; } case Syntax.TSDeclareMethod: case Syntax.TSDeclareFunction: if ('returnType' in node) { copyComments(node.returnType, node); node.extra.$tsvalue = { ...this.getTSValue(node.returnType, force), kind: Type.function }; } break; } if (!node.extra.$tsvalue) { node.extra.$tsvalue = { node, kind: Type.unknown, type: this.getTSType(node) }; } return node.extra.$tsvalue as any; } getTSType(node, defaultType: Parser.Type | Parser.Type[] = Type.unknown) { if (node.extra.$tstype) { return node.extra.$tstype; } let type = this.getTSTypeRaw(node, defaultType); if (typeof type === 'string') { type = type.replace(/\(\s+/g, '(').replace(/\s+\)/g, ')'); } if (node.optional) { return [type, Type.undefined]; } node.extra.$tstype = type; return type; } parseElements<T extends Babel.Expression | Babel.ObjectProperty | Babel.ObjectMethod | Babel.SpreadElement>(elements: T[]) { return elements.reduce((accumulator, element) => { const node = element.type === Syntax.SpreadElement ? element.argument : element as Exclude<T, Babel.SpreadElement>; const items = this.parseElementsItem(element, node); if (items.length) { accumulator.push(...items); } else if (element.type === Syntax.SpreadElement && element.argument?.type === Syntax.Identifier) { const varName = `...${element.argument.name}`; const ref = generateAnyGenerator.next().value; this.setScopeValue(varName, element, ref); accumulator.push({ type: Syntax.Identifier, name: varName, }); } return accumulator; }, [] as Array<Exclude<T, Babel.SpreadElement> | Babel.Identifier>); } parseElementsItem( _element: Babel.Expression | Babel.ObjectProperty | Babel.ObjectMethod | Babel.SpreadElement, item: Babel.Expression | Babel.ObjectProperty | Babel.ObjectMethod ) { switch (item.type) { case Syntax.Identifier: { const ref = this.getScopeValue(item.name); return ref && 'node' in ref ? this.parseElements([ref.node.value]) : []; } case Syntax.ObjectExpression: return this.parseElements(item.properties); default: return [item]; } } getObjectExpressionValue(node: Babel.ObjectExpression): Value<object> { const object = generateObjectGenerator.next().value; const setValue = (key: string, ref: Parser.Value, refNode: Babel.Node) => { object.value[key] = ref.expression ? ref : ref.value; object.rawObject[key] = ref; object.rawNode[key] = refNode; }; for (const item of this.parseElements(node.properties)) { switch (item.type) { case Syntax.ObjectProperty: { const ref = this.getValue(item.value); const key = 'name' in item.key ? item.key.name : this.getValue(item.key).value; if (typeof key === 'string') { const refNode = item.value.type === Syntax.Identifier && item.value.name in this.scope && 'node' in this.scope[item.value.name] ? (this.scope[item.value.name] as Parser.ScopeEntry).node.value : item.value; setValue(key, ref, refNode); if (item.value.type === Syntax.Identifier && !(key in this.scope)) { this.setScopeValue(key, item.value, ref); } } break; } case Syntax.Identifier: { const ref = this.getValue(item); const refNode = item.name in this.scope && 'node' in this.scope[item.name] ? (this.scope[item.name] as Parser.ScopeEntry).node.value : item; setValue(item.name, ref, refNode); break; } default: { const ref = this.getValue(item); const key = 'name' in item.key ? item.key.name : this.getValue(item.key).value; setValue(key, ref, item); break; } } } object.raw = JSON.stringify(object.value); return object; } getObjectProperty(node: Babel.ObjectProperty) { const ref = this.getValue(node.value); return ref; } getFunctionExpressionValue(node) { return this.getSourceValue(node, Type.function); } getSourceString(node) { const source = (node as Parser.AST.Node).extra.file?.script?.content || this.source.content; return source.substring(node.start, node.end); } getInlineSourceString(node) { return this.getSourceString(node).replace(DUPLICATED_SPACES_RE, ' '); } getSourceValue(node, type: Parser.Type | Parser.Type[] = Type.unknown) { const value = this.getSourceString(node); return new Value(type, value, value); } getIdentifierValue(node: Babel.Identifier): Parser.Value<any> | Parser.NS { if (node.name in this.scope) { const ref = this.scope[node.name]; return '$ns' in ref ? ref : ref.value; } if (node.name === Type.undefined) { return generateUndefineValue.next().value; } const type = Value.isNativeType(node.type) ? node.type : Type.unknown; return new Value(type, node.name, JSON.stringify(node.name)); } getUnaryExpressionType(node: Babel.UnaryExpression) { let type; switch (node.operator) { case 'typeof': case '!': type = Type.boolean; break; case '~': type = Type.binary; break; case '+': case '-': type = Type.number; break; default: type = Type.unknown; break; } return type; } getUnaryExpression(node): Value { let type = this.getUnaryExpressionType(node); let raw; let value; switch (node.argument.type) { case Syntax.NullLiteral: case Syntax.StringLiteral: case Syntax.TemplateLiteral: case Syntax.BooleanLiteral: case Syntax.NumericLiteral: raw = `${node.operator}${node.argument.value}`; try { value = JSON.parse(raw); } catch (e) { value = raw; } break; default: raw = this.getSourceString(node); value = raw; type = Type.unknown; } return new Value(type, value, raw); } getMemberExpression(node: Babel.MemberExpression): Parser.Value<object | string> { const exp = this.getSourceString(node); const type = exp.startsWith('Math.') || exp.startsWith('Number.') || (node.property.type === Syntax.Identifier && node.property.name === 'length') ? Type.number : Type.un