diff --git a/test/typescript-tests/nodeExtensions.ts b/test/typescript-tests/nodeExtensions.ts new file mode 100644 index 0000000000..dc8d176b7b --- /dev/null +++ b/test/typescript-tests/nodeExtensions.ts @@ -0,0 +1,52 @@ +import { expectTypeOf } from 'expect-type' +import { + MathNode, + BaseNode, + MathNodeCommon, + ConstantNode, + Node, + FunctionAssignmentNode, +} from 'mathjs' + +interface MyNode extends BaseNode { + type: 'MyNode' + a: MathNode +} +declare module 'mathjs' { + interface MathNodeTypes { + MyNode: MyNode + } +} + +/* +MathNode examples +*/ +{ + class CustomNode extends Node implements MyNode { + a: MathNode + type: 'MyNode' + constructor(a: MathNode) { + super() + this.a = a + } + } + + // Built-in subclass of Node + const instance1 = new ConstantNode(2) + + // Custom subclass of node + const instance2 = new CustomNode(new ConstantNode(2)) + + expectTypeOf(instance1).toMatchTypeOf() + expectTypeOf(instance1).toMatchTypeOf() + expectTypeOf(instance1).toMatchTypeOf() + + expectTypeOf(instance2).toMatchTypeOf() + expectTypeOf(instance2).toMatchTypeOf() + expectTypeOf(instance2).toMatchTypeOf() + + let instance3: MathNode + if (instance3.type === 'FunctionAssignmentNode') { + expectTypeOf(instance3).toMatchTypeOf() + } +} diff --git a/test/typescript-tests/testTypes.ts b/test/typescript-tests/testTypes.ts index b397b9a078..08a6523a62 100644 --- a/test/typescript-tests/testTypes.ts +++ b/test/typescript-tests/testTypes.ts @@ -41,10 +41,9 @@ import { SimplifyRule, SLUDecomposition, SymbolNode, - MathNodeCommon, Unit, - Node, isSymbolNode, + BaseNode, } from 'mathjs' import * as assert from 'assert' import { expectTypeOf } from 'expect-type' @@ -1110,7 +1109,7 @@ Transform examples expectTypeOf( math.parse('sqrt(3^2 + 4^2)').transform(myTransform1) - ).toMatchTypeOf>() + ).toMatchTypeOf>() assert.deepStrictEqual( math.parse('sqrt(3^2 + 4^2)').transform(myTransform1).toString(), @@ -1122,7 +1121,7 @@ Transform examples .parse('sqrt(3^2 + 4^2)') .transform(myTransform1) .transform(myTransform2) - ).toMatchTypeOf>() + ).toMatchTypeOf>() assert.deepStrictEqual( math @@ -2238,7 +2237,7 @@ Factory Test } if (math.isOperatorNode(x)) { expectTypeOf(x).toMatchTypeOf< - OperatorNode + OperatorNode >() } if (math.isParenthesisNode(x)) { @@ -2320,36 +2319,3 @@ Random examples MathJsChain >() } - -/* -MathNode examples -*/ -{ - class CustomNode extends Node { - a: MathNode - constructor(a: MathNode) { - super() - this.a = a - } - } - - // Basic node - const instance1 = new Node() - - // Built-in subclass of Node - const instance2 = new ConstantNode(2) - - // Custom subclass of node - const instance3 = new CustomNode(new ConstantNode(2)) - - expectTypeOf(instance1).toMatchTypeOf() - expectTypeOf(instance1).toMatchTypeOf() - - expectTypeOf(instance2).toMatchTypeOf() - expectTypeOf(instance2).toMatchTypeOf() - expectTypeOf(instance2).toMatchTypeOf() - - expectTypeOf(instance3).toMatchTypeOf() - expectTypeOf(instance3).toMatchTypeOf() - expectTypeOf(instance3).toMatchTypeOf() -} diff --git a/types/index.d.ts b/types/index.d.ts index a4ecfdae78..f3a095fad1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -162,36 +162,36 @@ declare namespace math { } interface NodeCtor { - new (): MathNode + new (): BaseNode } - interface AccessorNode extends MathNode { + interface AccessorNode extends BaseNode { type: 'AccessorNode' isAccessorNode: true - object: TObject + object: BaseNode index: IndexNode name: string } interface AccessorNodeCtor { - new ( + new ( object: TObject, index: IndexNode ): AccessorNode } - interface ArrayNode extends MathNode { + interface ArrayNode extends BaseNode type: 'ArrayNode' isArrayNode: true items: TItems } interface ArrayNodeCtor { new ( - items: MathNode[] + items: TItems ): ArrayNode } - interface AssignmentNode - extends MathNode { + interface AssignmentNode + extends BaseNode { type: 'AssignmentNode' isAssignmentNode: true object: SymbolNode | AccessorNode @@ -200,33 +200,33 @@ declare namespace math { name: string } interface AssignmentNodeCtor { - new ( + new ( object: SymbolNode, value: TValue ): AssignmentNode - new ( + new ( object: SymbolNode | AccessorNode, index: IndexNode, value: TValue ): AssignmentNode } - interface BlockNode extends MathNode { + interface BlockNode extends BaseNode { type: 'BlockNode' isBlockNode: true blocks: Array<{ node: TNode; visible: boolean }> } interface BlockNodeCtor { - new ( + new ( arr: Array<{ node: TNode } | { node: TNode; visible: boolean }> ): BlockNode } interface ConditionalNode< - TCond extends MathNode = MathNode, - TTrueNode extends MathNode = MathNode, - TFalseNode extends MathNode = MathNode - > extends MathNode { + TCond extends BaseNode = BaseNode, + TTrueNode extends BaseNode = BaseNode, + TFalseNode extends BaseNode = BaseNode + > extends BaseNode { type: 'ConditionalNode' isConditionalNode: boolean condition: TCond @@ -235,9 +235,9 @@ declare namespace math { } interface ConditionalNodeCtor { new < - TCond extends MathNode = MathNode, - TTrueNode extends MathNode = MathNode, - TFalseNode extends MathNode = MathNode + TCond extends BaseNode = BaseNode, + TTrueNode extends BaseNode = BaseNode, + TFalseNode extends BaseNode = BaseNode >( condition: TCond, trueExpr: TTrueNode, @@ -245,8 +245,7 @@ declare namespace math { ): ConditionalNode } - interface ConstantNode - extends MathNode { + interface ConstantNode extends BaseNode { type: 'ConstantNode' isConstantNode: true // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -259,8 +258,8 @@ declare namespace math { ): ConstantNode } - interface FunctionAssignmentNode - extends MathNode { + interface FunctionAssignmentNode + extends BaseNode { type: 'FunctionAssignmentNode' isFunctionAssignmentNode: true name: string @@ -268,7 +267,7 @@ declare namespace math { expr: TExpr } interface FunctionAssignmentNodeCtor { - new ( + new ( name: string, params: string[], expr: TExpr @@ -277,7 +276,7 @@ declare namespace math { interface FunctionNode< TFn = SymbolNode, - TArgs extends MathNode[] = MathNode[] + TArgs extends BaseNode[] = BaseNode[] > extends MathNode { type: 'FunctionNode' isFunctionNode: true @@ -285,37 +284,38 @@ declare namespace math { args: TArgs } interface FunctionNodeCtor { - new ( + new ( fn: TFn, args: TArgs ): FunctionNode + // eslint-disable-next-line @typescript-eslint/no-explicit-any onUndefinedFunction: (name: string) => any } - interface IndexNode extends MathNode { + interface IndexNode extends BaseNode { type: 'IndexNode' isIndexNode: true dimensions: TDims dotNotation: boolean } interface IndexNodeCtor { - new (dimensions: TDims): IndexNode - new ( + new (dimensions: TDims): IndexNode + new ( dimensions: TDims, dotNotation: boolean ): IndexNode } interface ObjectNode< - TProps extends Record = Record - > extends MathNode { + TProps extends Record = Record + > extends BaseNode { type: 'ObjectNode' isObjectNode: true properties: TProps } interface ObjectNodeCtor { - new = Record>( + new = Record>( properties: TProps ): ObjectNode } @@ -359,8 +359,8 @@ declare namespace math { interface OperatorNode< TOp extends OperatorNodeMap[TFn] = never, TFn extends OperatorNodeFn = never, - TArgs extends MathNode[] = MathNode[] - > extends MathNode { + TArgs extends BaseNode[] = BaseNode[] + > extends BaseNode { type: 'OperatorNode' isOperatorNode: true op: TOp @@ -371,11 +371,11 @@ declare namespace math { isBinary(): boolean } - interface OperatorNodeCtor extends MathNode { + interface OperatorNodeCtor extends BaseNode { new < TOp extends OperatorNodeMap[TFn], TFn extends OperatorNodeFn, - TArgs extends MathNode[] + TArgs extends BaseNode[] >( op: TOp, fn: TFn, @@ -383,22 +383,22 @@ declare namespace math { implicit?: boolean ): OperatorNode } - interface ParenthesisNode - extends MathNode { + interface ParenthesisNode + extends BaseNode { type: 'ParenthesisNode' isParenthesisNode: true content: TContent } interface ParenthesisNodeCtor { - new ( + new ( content: TContent ): ParenthesisNode } interface RangeNode< - TStart extends MathNode = MathNode, - TEnd extends MathNode = MathNode, - TStep extends MathNode = MathNode + TStart extends BaseNode = BaseNode, + TEnd extends BaseNode = BaseNode, + TStep extends BaseNode = BaseNode > extends MathNode { type: 'RangeNode' isRangeNode: true @@ -408,9 +408,9 @@ declare namespace math { } interface RangeNodeCtor { new < - TStart extends MathNode = MathNode, - TEnd extends MathNode = MathNode, - TStep extends MathNode = MathNode + TStart extends BaseNode = BaseNode, + TEnd extends BaseNode = BaseNode, + TStep extends BaseNode = BaseNode >( start: TStart, end: TEnd, @@ -418,21 +418,21 @@ declare namespace math { ): RangeNode } - interface RelationalNode - extends MathNode { + interface RelationalNode + extends BaseNode { type: 'RelationalNode' isRelationalNode: true conditionals: string[] params: TParams } interface RelationalNodeCtor { - new ( + new ( conditionals: string[], params: TParams ): RelationalNode } - interface SymbolNode extends MathNode { + interface SymbolNode extends BaseNode { type: 'SymbolNode' isSymbolNode: true name: string @@ -443,8 +443,44 @@ declare namespace math { onUndefinedSymbol: (name: string) => any } + interface MathNodeTypes { + AccessorNode: AccessorNode + ArrayNode: ArrayNode + AssignmentNode: AssignmentNode + BlockNode: BlockNode + ConditionalNode: ConditionalNode + ConstantNode: ConstantNode + FunctionAssignmentNode: FunctionAssignmentNode + FunctionNode: FunctionNode + IndexNode: IndexNode + ObjectNode: ObjectNode + OperatorNode: OperatorNode + ParenthesisNode: ParenthesisNode + RangeNode: RangeNode + RelationalNode: RelationalNode + SymbolNode: SymbolNode + } + + /** + * Discriminated union describing all registered node types + * + * Deriving this union from MathNodeTypes makes the union extensible for implementors using `declare module 'mathjs'` + * + * e.g. + * + * declare module 'mathjs' { + * interface MyNode extends BaseNode { + * } + + * interface MathNodeTypes { + * MyNode: MyNode + * } + * } + */ + type MathNode = MathNodeTypes[keyof MathNodeTypes] + /** - * @deprecated since version 11.3. Prefer `MathNode` instead + * @deprecated since version 11.3. Prefer `BaseNode` instead */ type MathNodeCommon = MathNode @@ -3968,7 +4004,7 @@ declare namespace math { evaluate(scope?: any): any } - interface MathNode { + interface BaseNode { isNode: true comment: string type: string @@ -4014,9 +4050,9 @@ declare namespace math { * ``` * var node = math.parse('x^2 + x/4 + 3*y'); * var filtered = node.filter(function (node) { - * return node.isSymbolMathNode && node.name == 'x'; + * return node.isSymbolNode && node.name == 'x'; * }); - * // returns an array with two entries: two SymbolMathNodes 'x' + * // returns an array with two entries: two SymbolNodes 'x' * ``` * * The callback function is called as callback(node: MathNode, path: @@ -4080,13 +4116,13 @@ declare namespace math { * and must return a MathNode. Parameter path is a string containing a * relative JSON Path. * - * For example, to replace all nodes of type SymbolMathNode having name - * ‘x’ with a ConstantMathNode with value 3: + * For example, to replace all nodes of type SymbolNode having name + * ‘x’ with a ConstantNode with value 3: * ```js * var node = math.parse('x^2 + 5*x'); * var transformed = node.transform(function (node, path, parent) { - * if (node.SymbolMathNode && node.name == 'x') { - * return new math.expression.node.ConstantMathNode(3); + * if (node.SymbolNode && node.name == 'x') { + * return new math.expression.node.ConstantNode(3); * } * else { * return node; @@ -4114,18 +4150,18 @@ declare namespace math { * var node = math.parse('3 * x + 2'); * node.traverse(function (node, path, parent) { * switch (node.type) { - * case 'OperatorMathNode': console.log(node.type, node.op); break; - * case 'ConstantMathNode': console.log(node.type, node.value); break; - * case 'SymbolMathNode': console.log(node.type, node.name); break; + * case 'OperatorNode': console.log(node.type, node.op); break; + * case 'ConstantNode': console.log(node.type, node.value); break; + * case 'SymbolNode': console.log(node.type, node.name); break; * default: console.log(node.type); * } * }); * // outputs: - * // OperatorMathNode + - * // OperatorMathNode * - * // ConstantMathNode 3 - * // SymbolMathNode x - * // ConstantMathNode 2 + * // OperatorNode + + * // OperatorNode * + * // ConstantNode 3 + * // SymbolNode x + * // ConstantNode 2 * ``` */ traverse(