diff --git a/src/__tests__/index.e2e.spec.ts b/src/__tests__/index.e2e.spec.ts index 7b8829c..804d6da 100644 --- a/src/__tests__/index.e2e.spec.ts +++ b/src/__tests__/index.e2e.spec.ts @@ -11,6 +11,7 @@ describe('e2e:esast-util-from-code', () => { 'AbstractParser', 'Grammar', 'Lexer', + 'OperatorParser', 'Parser', 'PunctuatorParser', 'Token', diff --git a/src/parsers/__tests__/operator.parser.integration.spec.ts b/src/parsers/__tests__/operator.parser.integration.spec.ts new file mode 100644 index 0000000..b5422ae --- /dev/null +++ b/src/parsers/__tests__/operator.parser.integration.spec.ts @@ -0,0 +1,341 @@ +/** + * @file Integration Tests - OperatorParser + * @module esast-util-from-code/parsers/tests/punctuator/integration + */ + +import { keywords, types } from '#src/enums' +import type { BinaryOperatorResult } from '#src/types' +import test from '#tests/utils/test' +import type { + ArithmeticOperator, + AssignmentOperator, + BitwiseBinaryOperator, + BitwiseShiftOperator, + EqualityOperator, + ImportAssertionOperator, + LogicalOperator, + RelationalOperator, + UnaryOperator, + UnaryTypeOperator, + UpdateOperator +} from '@flex-development/esast' +import { chars } from '@flex-development/vfile-lexer' +import TestSubject from '../parser' + +describe('integration:parsers/OperatorParser', () => { + describe('#arithmeticOperator', () => { + it.each([ + chars.asterisk, + chars.asterisk.repeat(2) as ArithmeticOperator, + chars.minus, + chars.percent, + chars.plus, + chars.slash + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.arithmeticOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#assignmentOperator', () => { + it.each([ + '%=', + '&&=', + '&=', + '**=', + '*=', + '+=', + '-=', + '/=', + '<<=', + '=', + '>>=', + '>>>=', + '??=', + '^=', + '|=', + '||=' + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.assignmentOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#binaryOperator', () => { + it.each<[string, BinaryOperatorResult]>([ + ['arithmetic', [chars.plus, types.arithmeticExpression]], + ['bitwise', [chars.ampersand, types.bitwiseExpression]], + ['equality', [`${chars.equal}${chars.equal}`, types.equalityExpression]], + ['logical', [`${chars.bar}${chars.bar}`, types.logicalExpression]], + ['relational', [chars.gt, types.relationalExpression]] + ])('should parse %s operator', (_, result) => { + // Arrange + const subject: TestSubject = new TestSubject(result[0]) + + // Act + const output = test(subject.binaryOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eql(result) + }) + }) + + describe('#bitwiseBinaryOperator', () => { + it.each([ + chars.ampersand, + chars.bar, + chars.caret + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.bitwiseBinaryOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#bitwiseShiftOperator', () => { + it.each([ + chars.gt + chars.lf + chars.gt, + chars.lt + chars.space + chars.lt + ])('should fail on invalid whitespace (sample %#)', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + Expect + expect.failed(test(subject.bitwiseShiftOperator, subject.head)) + }) + + it.each([ + chars.gt.repeat(2) as BitwiseShiftOperator, + chars.gt.repeat(3) as BitwiseShiftOperator, + chars.lt.repeat(2) as BitwiseShiftOperator + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.bitwiseShiftOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#equalityOperator', () => { + it.each([ + chars.exclamation + chars.lf + chars.equal, + chars.equal + chars.space + chars.equal + ])('should fail on invalid whitespace (sample %#)', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + Expect + expect.failed(test(subject.equalityOperator, subject.head)) + }) + + it.each([ + '!=', + '!==', + chars.equal.repeat(2) as EqualityOperator, + chars.equal.repeat(3) as EqualityOperator + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.equalityOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#importAssertionOperator', () => { + it.each([ + keywords.assert, + keywords.with + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.importAssertionOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#logicalOperator', () => { + it.each([ + chars.ampersand + chars.cr + chars.ampersand, + chars.bar + chars.lf + chars.bar, + chars.question + chars.space + chars.question + ])('should fail on invalid whitespace (sample %#)', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + Expect + expect.failed(test(subject.logicalOperator, subject.head)) + }) + + it.each([ + chars.ampersand.repeat(2) as LogicalOperator, + chars.bar.repeat(2) as LogicalOperator, + chars.question.repeat(2) as LogicalOperator + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.logicalOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#optionalChainingOperator', () => { + it('should fail on invalid whitespace', () => { + // Arrange + const operator: string = chars.question + chars.space + chars.dot + const subject: TestSubject = new TestSubject(operator) + + // Act + Expect + expect.failed(test(subject.optionalChainingOperator, subject.head)) + }) + + it('should parse "?."', () => { + // Arrange + const operator: string = chars.question + chars.dot + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.optionalChainingOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.be.an('array').of.length(2) + expect(output.candidate?.result[0].value).to.eq(chars.question) + expect(output.candidate?.result[1].value).to.eq(chars.dot) + }) + }) + + describe('#relationalOperator', () => { + it.each([ + '<=', + '>=', + chars.gt, + chars.lt, + keywords.in, + keywords.instanceof + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.relationalOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#unaryOperator', () => { + it.each([ + chars.exclamation, + chars.minus, + chars.plus, + chars.tilde, + keywords.delete, + keywords.typeof, + keywords.void + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.unaryOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#unaryTypeOperator', () => { + it.each([ + keywords.keyof, + keywords.readonly, + keywords.typeof, + keywords.unique + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.unaryTypeOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eq(operator) + }) + }) + + describe('#updateOperator', () => { + it.each([ + chars.minus + chars.lf + chars.minus, + chars.plus + chars.space + chars.plus + ])('should fail on invalid whitespace (sample %#)', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + Expect + expect.failed(test(subject.updateOperator, subject.head)) + }) + + it.each([ + chars.minus.repeat(2) as UpdateOperator, + chars.plus.repeat(2) as UpdateOperator + ])('should parse "%s"', operator => { + // Arrange + const subject: TestSubject = new TestSubject(operator) + + // Act + const output = test(subject.updateOperator, subject.head) + + // Expect + expect.succeeded(output, subject.head) + expect(output.candidate?.result).to.eql({ + end: subject.tail.start, + operator, + start: subject.head.start + }) + }) + }) +}) diff --git a/src/parsers/grammar.ts b/src/parsers/grammar.ts index 6cae0b5..9a6b417 100644 --- a/src/parsers/grammar.ts +++ b/src/parsers/grammar.ts @@ -3,7 +3,26 @@ * @module esast-util-from-code/parsers/Grammar */ -import type { PunctuatorToken } from '#src/types' +import type { + ApplyOperatorValue, + BinaryOperatorResult, + Operator, + PunctuatorToken, + UpdateOperatorResult +} from '#src/types' +import type { + ArithmeticOperator, + AssignmentOperator, + BitwiseBinaryOperator, + BitwiseShiftOperator, + EqualityOperator, + ImportAssertionOperator, + LogicalOperator, + RelationalOperator, + UnaryOperator, + UnaryTypeOperator, + UpdateOperator +} from '@flex-development/esast' import type { Runner as P, RepNResult, @@ -32,6 +51,19 @@ abstract class Grammar { */ public abstract get ampersand(): P + /** + * Get the arithmetic operator parser. + * + * @see {@linkcode ArithmeticOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Arithmetic operator parser + */ + public abstract get arithmeticOperator(): P + /** * Get the arrow (`=>`) parser. * @@ -46,6 +78,19 @@ abstract class Grammar { */ public abstract get arrow(): P> + /** + * Get the assignment operator parser. + * + * @see {@linkcode AssignmentOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Assignment operator parser + */ + public abstract get assignmentOperator(): P + /** * Get the asterisk (`*`) parser. * @@ -116,6 +161,45 @@ abstract class Grammar { */ public abstract get bar(): P + /** + * Get the binary operator parser. + * + * @see {@linkcode BinaryOperatorResult} + * + * @public + * @abstract + * @instance + * + * @return {P} Binary operator parser + */ + public abstract get binaryOperator(): P + + /** + * Get the bitwise binary operator parser. + * + * @see {@linkcode BitwiseBinaryOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Bitwise binary operator parser + */ + public abstract get bitwiseBinaryOperator(): P + + /** + * Get the bitwise shift operator parser. + * + * @see {@linkcode BitwiseShiftOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Bitwise shift operator parser + */ + public abstract get bitwiseShiftOperator(): P + /** * Get the caret (`^`) parser. * @@ -214,6 +298,19 @@ abstract class Grammar { */ public abstract get equal(): P + /** + * Get the equality operator parser. + * + * @see {@linkcode EqualityOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Equality operator parser + */ + public abstract get equalityOperator(): P + /** * Get the exclamation mark (`!`) parser. * @@ -256,6 +353,19 @@ abstract class Grammar { */ public abstract get hash(): P + /** + * Get the import assertion operator parser. + * + * @see {@linkcode ImportAssertionOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Import assertion operator parser + */ + public abstract get importAssertionOperator(): P + /** * Get the left brace (`{`) parser. * @@ -298,6 +408,19 @@ abstract class Grammar { */ public abstract get leftParen(): P + /** + * Get the logical operator parser. + * + * @see {@linkcode LogicalOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Logical operator parser + */ + public abstract get logicalOperator(): P + /** * Get the less than symbol (`<`) parser. * @@ -326,6 +449,23 @@ abstract class Grammar { */ public abstract get minus(): P + /** + * Get the optional chaining operator (`?.`) parser. + * + * @see {@linkcode PunctuatorToken} + * @see {@linkcode RepNResult} + * + * @public + * @abstract + * @instance + * + * @return {P>} Optional chaining operator + * parser + */ + public abstract get optionalChainingOperator( + // + ): P> + /** * Get the percent sign (`%`) parser. * @@ -368,6 +508,19 @@ abstract class Grammar { */ public abstract get question(): P + /** + * Get the relational operator parser. + * + * @see {@linkcode RelationalOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Relational operator parser + */ + public abstract get relationalOperator(): P + /** * Get the right brace (`}`) parser. * @@ -452,6 +605,65 @@ abstract class Grammar { */ public abstract get tilde(): P + /** + * Get the unary operator parser. + * + * @see {@linkcode UnaryOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Unary operator parser + */ + public abstract get unaryOperator(): P + + /** + * Get the unary type operator parser. + * + * @see {@linkcode UnaryTypeOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Unary type operator parser + */ + public abstract get unaryTypeOperator(): P + + /** + * Get the update operator parser. + * + * @see {@linkcode UpdateOperatorResult} + * @see {@linkcode UpdateOperator} + * + * @public + * @abstract + * @instance + * + * @return {P} Update operator parser + */ + public abstract get updateOperator(): P + + /** + * Create an operator. + * + * @see {@linkcode ApplyOperatorValue} + * @see {@linkcode Operator} + * + * @protected + * @abstract + * @instance + * + * @template {Operator} T - Operator + * + * @param {ApplyOperatorValue} value - Apply callback value + * @return {T} Operator + */ + protected abstract applyOperator( + value: ApplyOperatorValue + ): T + /** * Forbid leading whitespace. * diff --git a/src/parsers/index.ts b/src/parsers/index.ts index 81dbc6d..794f3b8 100644 --- a/src/parsers/index.ts +++ b/src/parsers/index.ts @@ -5,5 +5,6 @@ export { default as AbstractParser } from './abstract.parser' export { default as Grammar } from './grammar' +export { default as OperatorParser } from './operator.parser' export { default as Parser } from './parser' export { default as PunctuatorParser } from './punctuator.parser' diff --git a/src/parsers/operator.parser.ts b/src/parsers/operator.parser.ts new file mode 100644 index 0000000..482760b --- /dev/null +++ b/src/parsers/operator.parser.ts @@ -0,0 +1,349 @@ +/** + * @file Parsers - OperatorParser + * @module esast-util-from-code/parsers/operator + */ + +import { keywords, types, type tt } from '#src/enums' +import type { + ApplyOperatorValue, + BinaryOperatorResult, + Operator, + PunctuatorToken, + UpdateOperatorResult +} from '#src/types' +import type { + ArithmeticOperator, + AssignmentOperator, + BitwiseBinaryOperator, + BitwiseShiftOperator, + EqualityOperator, + ImportAssertionOperator, + LogicalOperator, + RelationalOperator, + UnaryOperator, + UnaryTypeOperator, + UpdateOperator +} from '@flex-development/esast' +import { reduce, sift } from '@flex-development/tutils' +import { + alt, + apply, + combine, + condition, + opt, + seq, + succ, + val, + type Runner as P, + type RepNResult, + type TokenType as TT +} from '@flex-development/unist-util-parsec' +import { chars } from '@flex-development/vfile-lexer' +import { ok } from 'devlop' +import PunctuatorParser from './punctuator.parser' + +/** + * Operator parser. + * + * @see {@linkcode PunctuatorParser} + * + * @class + * @abstract + * @extends {PunctuatorParser} + */ +abstract class OperatorParser extends PunctuatorParser { + /** + * Get the arithmetic operator parser. + * + * @see {@linkcode ArithmeticOperator} + * + * @public + * @instance + * + * @return {P} Arithmetic operator parser + */ + public get arithmeticOperator(): P { + return apply(alt( + this.plus, + this.minus, + seq(this.asterisk, opt(this.nw(this.asterisk))), + this.slash, + this.percent + ), value => this.applyOperator(value)) + } + + /** + * Get the assignment operator parser. + * + * @see {@linkcode AssignmentOperator} + * + * @public + * @instance + * + * @return {P} Assignment operator parser + */ + public get assignmentOperator(): P { + return apply(combine(opt(alt( + seq(this.asterisk, opt(this.nw(this.asterisk))), + seq(this.ampersand, opt(this.nw(this.ampersand))), + seq(this.bar, opt(this.nw(this.bar))), + seq(this.question, this.nw(this.question)), + seq( + this.lt, + this.nw(this.lt), + opt(this.nw(this.lt)) + ), + seq( + this.gt, + this.nw(this.gt), + opt(this.nw(this.gt)) + ), + this.caret, + this.slash, + this.minus, + this.percent, + this.plus + )), result => { + return condition( + result, + seq(succ(result!), this.nw(this.equal)), + this.equal + ) + }), value => this.applyOperator(value)) + } + + /** + * Get the binary operator parser. + * + * @see {@linkcode BinaryOperatorResult} + * + * @public + * @instance + * + * @return {P} Binary operator parser + */ + public get binaryOperator(): P { + return alt( + apply(this.logicalOperator, op => [op, types.logicalExpression]), + apply(this.bitwiseBinaryOperator, op => [op, types.bitwiseExpression]), + apply(this.equalityOperator, op => [op, types.equalityExpression]), + apply(this.relationalOperator, op => [op, types.relationalExpression]), + apply(this.bitwiseShiftOperator, op => [op, types.bitwiseExpression]), + apply(this.arithmeticOperator, op => [op, types.arithmeticExpression]) + ) + } + + /** + * Get the bitwise binary operator parser. + * + * @see {@linkcode BitwiseBinaryOperator} + * + * @public + * @instance + * + * @return {P} Bitwise binary operator parser + */ + public get bitwiseBinaryOperator(): P { + return apply(alt( + this.bar, + this.caret, + this.ampersand + ), value => this.applyOperator(value)) + } + + /** + * Get the bitwise shift operator parser. + * + * @see {@linkcode BitwiseShiftOperator} + * + * @public + * @instance + * + * @return {P} Bitwise shift operator parser + */ + public get bitwiseShiftOperator(): P { + return apply(combine(alt(this.lt, this.gt), result => { + return seq(succ(result), condition( + result.value === chars.lt, + this.nw(this.lt), + seq(this.nw(this.gt), opt(this.nw(this.gt))) + )) + }), value => this.applyOperator(value)) + } + + /** + * Get the equality operator parser. + * + * @see {@linkcode EqualityOperator} + * + * @public + * @instance + * + * @return {P} Equality operator parser + */ + public get equalityOperator(): P { + return apply(seq( + alt(this.exclamation, this.equal), + this.nw(this.equal), + opt(this.nw(this.equal)) + ), value => this.applyOperator(value)) + } + + /** + * Get the import assertion operator parser. + * + * @see {@linkcode importAssertionOperator} + * + * @public + * @instance + * + * @return {P} Import assertion operator parser + */ + public get importAssertionOperator(): P { + return apply(alt( + val(keywords.assert), + val(keywords.with) + ), value => this.applyOperator(value)) + } + + /** + * Get the logical operator parser. + * + * @see {@linkcode LogicalOperator} + * + * @public + * @instance + * + * @return {P} Logical operator parser + */ + public get logicalOperator(): P { + return apply(alt( + seq(this.question, this.nw(this.question)), + seq(this.bar, this.nw(this.bar)), + seq(this.ampersand, this.nw(this.ampersand)) + ), value => this.applyOperator(value)) + } + + /** + * Get the optional chaining operator (`?.`) parser. + * + * @see {@linkcode PunctuatorToken} + * @see {@linkcode RepNResult} + * + * @public + * @instance + * + * @return {P>} Optional chaining operator + * parser + */ + public get optionalChainingOperator(): P> { + return seq(this.question, this.nw(this.dot)) + } + + /** + * Get the relational operator parser. + * + * @see {@linkcode RelationalOperator} + * + * @public + * @instance + * + * @return {P} Relational operator parser + */ + public get relationalOperator(): P { + return apply(alt( + seq(alt(this.lt, this.gt), opt(this.nw(this.equal))), + val(keywords.in), + val(keywords.instanceof) + ), value => this.applyOperator(value)) + } + + /** + * Get the unary operator parser. + * + * @see {@linkcode UnaryOperator} + * + * @public + * @instance + * + * @return {P} Unary operator parser + */ + public get unaryOperator(): P { + return apply(alt( + val(keywords.delete), + val(keywords.typeof), + val(keywords.void), + this.exclamation, + this.minus, + this.plus, + this.tilde + ), value => this.applyOperator(value)) + } + + /** + * Get the unary type operator parser. + * + * @see {@linkcode UnaryTypeOperator} + * + * @public + * @instance + * + * @return {P} Unary type operator parser + */ + public get unaryTypeOperator(): P { + return apply(alt( + val(keywords.keyof), + val(keywords.readonly), + val(keywords.typeof), + val(keywords.unique) + ), value => this.applyOperator(value)) + } + + /** + * Get the update operator parser. + * + * @see {@linkcode UpdateOperatorResult} + * @see {@linkcode UpdateOperator} + * + * @public + * @instance + * + * @return {P} Update operator parser + */ + public get updateOperator(): P { + return apply(combine(alt(this.minus, this.plus), result => { + return seq(succ(result), this.nw(condition( + result.value === chars.minus, + this.minus, + this.plus + ))) + }), value => ({ + end: value[1].end, + operator: this.applyOperator(value), + start: value[0].start + })) + } + + /** + * Create an operator. + * + * @see {@linkcode ApplyOperatorValue} + * @see {@linkcode Operator} + * + * @protected + * @instance + * + * @template {Operator} T - Operator + * + * @param {ApplyOperatorValue} value - Apply callback value + * @return {T} Operator + */ + protected applyOperator(value: ApplyOperatorValue): T { + return reduce(sift([value].flat(3)), (acc, token) => { + ok(token.value !== null, 'expected token value') + return acc + token.value + }, '') + } +} + +export default OperatorParser diff --git a/src/parsers/parser.ts b/src/parsers/parser.ts index 6354425..88a0089 100644 --- a/src/parsers/parser.ts +++ b/src/parsers/parser.ts @@ -8,19 +8,19 @@ import Lexer from '#src/lexer' import type Token from '#src/token' import type { Value, VFile } from 'vfile' import type Grammar from './grammar' -import PunctuatorParser from './punctuator.parser' +import OperatorParser from './operator.parser' /** * ECMAScript parser. * * @see {@linkcode Grammar} - * @see {@linkcode PunctuatorParser} + * @see {@linkcode OperatorParser} * * @class - * @extends {PunctuatorParser} + * @extends {OperatorParser} * @implements {Grammar} */ -class Parser extends PunctuatorParser implements Grammar { +class Parser extends OperatorParser implements Grammar { /** * Head token. * diff --git a/src/types/__tests__/apply-operator-value.spec-d.ts b/src/types/__tests__/apply-operator-value.spec-d.ts new file mode 100644 index 0000000..1438d1e --- /dev/null +++ b/src/types/__tests__/apply-operator-value.spec-d.ts @@ -0,0 +1,40 @@ +/** + * @file Type Tests - ApplyOperatorValue + * @module esast-util-from-code/types/tests/unit-d/ApplyOperatorValue + */ + +import type Token from '#src/token' +import type TestSubject from '../apply-operator-value' + +describe('unit-d:types/ApplyOperatorValue', () => { + type I = Token | null | undefined + type I1 = I | readonly I[] + type I2 = I1 | readonly I1[] + type I3 = I2 | readonly I2[] + + it('should allow 1 dimensional array', () => { + expectTypeOf().toMatchTypeOf() + }) + + it('should allow 2 dimensional array', () => { + expectTypeOf().toMatchTypeOf() + expectTypeOf().toMatchTypeOf() + }) + + it('should allow 3 dimensional array', () => { + expectTypeOf().toMatchTypeOf() + expectTypeOf().toMatchTypeOf() + }) + + it('should allow Token', () => { + expectTypeOf().toMatchTypeOf() + }) + + it('should allow null', () => { + expectTypeOf().toMatchTypeOf() + }) + + it('should allow undefined', () => { + expectTypeOf().toMatchTypeOf() + }) +}) diff --git a/src/types/__tests__/operator.spec-d.ts b/src/types/__tests__/operator.spec-d.ts new file mode 100644 index 0000000..0a7936f --- /dev/null +++ b/src/types/__tests__/operator.spec-d.ts @@ -0,0 +1,42 @@ +/** + * @file Type Tests - Operator + * @module esast-util-from-code/types/tests/unit-d/Operator + */ + +import type { + AssignmentOperator, + BinaryOperator, + ImportAssertionOperator, + UnaryOperator, + UnaryTypeOperator, + UpdateOperator +} from '@flex-development/esast' +import type TestSubject from '../operator' + +describe('unit-d:types/Operator', () => { + it('should extract AssignmentOperator', () => { + expectTypeOf().extract().not.toBeNever() + }) + + it('should extract BinaryOperator', () => { + expectTypeOf().extract().not.toBeNever() + }) + + it('should extract ImportAssertionOperator', () => { + expectTypeOf() + .extract() + .not.toBeNever() + }) + + it('should extract UnaryOperator', () => { + expectTypeOf().extract().not.toBeNever() + }) + + it('should extract UnaryTypeOperator', () => { + expectTypeOf().extract().not.toBeNever() + }) + + it('should extract UpdateOperator', () => { + expectTypeOf().extract().not.toBeNever() + }) +}) diff --git a/src/types/__tests__/result-operator-binary.spec-d.ts b/src/types/__tests__/result-operator-binary.spec-d.ts new file mode 100644 index 0000000..9c51f27 --- /dev/null +++ b/src/types/__tests__/result-operator-binary.spec-d.ts @@ -0,0 +1,25 @@ +/** + * @file Type Tests - BinaryOperatorResult + * @module esast-util-from-code/types/tests/unit-d/BinaryOperatorResult + */ + +import type { + AnyBinaryExpression, + BinaryOperator +} from '@flex-development/esast' +import type { Type } from '@flex-development/unist-util-types' +import type TestSubject from '../result-operator-binary' + +describe('unit-d:types/BinaryOperatorResult', () => { + it('should match [0: BinaryOperator]', () => { + expectTypeOf() + .toHaveProperty(0) + .toEqualTypeOf() + }) + + it('should match [1: Type]', () => { + expectTypeOf() + .toHaveProperty(1) + .toEqualTypeOf>() + }) +}) diff --git a/src/types/__tests__/result-operator-update.spec-d.ts b/src/types/__tests__/result-operator-update.spec-d.ts new file mode 100644 index 0000000..7285be9 --- /dev/null +++ b/src/types/__tests__/result-operator-update.spec-d.ts @@ -0,0 +1,24 @@ +/** + * @file Type Tests - UpdateOperatorResult + * @module esast-util-from-code/types/tests/unit-d/UpdateOperatorResult + */ + +import type { UpdateOperator } from '@flex-development/esast' +import type { Point } from '@flex-development/unist-util-parsec' +import type TestSubject from '../result-operator-update' + +describe('unit-d:types/UpdateOperatorResult', () => { + it('should match [end: Point]', () => { + expectTypeOf().toHaveProperty('end').toEqualTypeOf() + }) + + it('should match [operator: UpdateOperator]', () => { + expectTypeOf() + .toHaveProperty('operator') + .toEqualTypeOf() + }) + + it('should match [start: Point]', () => { + expectTypeOf().toHaveProperty('start').toEqualTypeOf() + }) +}) diff --git a/src/types/apply-operator-value.ts b/src/types/apply-operator-value.ts new file mode 100644 index 0000000..435f68b --- /dev/null +++ b/src/types/apply-operator-value.ts @@ -0,0 +1,21 @@ +/** + * @file Type Aliases - ApplyOperatorValue + * @module esast-util-from-code/types/ApplyOperatorValue + */ + +import type Token from '#src/token' +import type { apply } from '@flex-development/unist-util-parsec' + +/** + * Construct a union of {@linkcode apply} callback values that can be used to + * build an operator. + */ +type ApplyOperatorValue = Token | null | undefined extends + infer H extends Token | null | undefined + ? H | H[] | readonly H[] extends infer I + ? I | I[] | readonly I[] extends infer J ? J | J[] | readonly J[] + : never + : never + : never + +export type { ApplyOperatorValue as default } diff --git a/src/types/index.ts b/src/types/index.ts index 105de79..b0d91ed 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -4,6 +4,10 @@ */ export type { Code, Offset } from '@flex-development/vfile-lexer' +export type { default as ApplyOperatorValue } from './apply-operator-value' +export type { default as Operator } from './operator' +export type { default as BinaryOperatorResult } from './result-operator-binary' +export type { default as UpdateOperatorResult } from './result-operator-update' export type { default as TokenInfo } from './token-info' export type { default as PunctuatorToken } from './token-punctuator' export type { default as TokenValue } from './token-value' diff --git a/src/types/operator.ts b/src/types/operator.ts new file mode 100644 index 0000000..ce06359 --- /dev/null +++ b/src/types/operator.ts @@ -0,0 +1,33 @@ +/** + * @file Type Aliases - Operator + * @module esast-util-from-code/types/Operator + */ + +import type { + AssignmentOperator, + BinaryOperator, + ImportAssertionOperator, + UnaryOperator, + UnaryTypeOperator, + UpdateOperator +} from '@flex-development/esast' + +/** + * Union of esast operators. + * + * @see {@linkcode AssignmentOperator} + * @see {@linkcode BinaryOperator} + * @see {@linkcode ImportAssertionOperator} + * @see {@linkcode UnaryOperator} + * @see {@linkcode UnaryTypeOperator} + * @see {@linkcode UpdateOperator} + */ +type Operator = + | AssignmentOperator + | BinaryOperator + | ImportAssertionOperator + | UnaryOperator + | UnaryTypeOperator + | UpdateOperator + +export type { Operator as default } diff --git a/src/types/result-operator-binary.ts b/src/types/result-operator-binary.ts new file mode 100644 index 0000000..a69b189 --- /dev/null +++ b/src/types/result-operator-binary.ts @@ -0,0 +1,55 @@ +/** + * @file Type Aliases - BinaryOperatorResult + * @module esast-util-from-code/types/BinaryOperatorResult + */ + +import type { + ArithmeticExpression, + ArithmeticOperator, + BitwiseExpression, + BitwiseOperator, + EqualityExpression, + EqualityOperator, + LogicalExpression, + LogicalOperator, + RelationalExpression, + RelationalOperator +} from '@flex-development/esast' +import type { Type } from '@flex-development/unist-util-types' + +/** + * Binary operator parser result. + * + * @see {@linkcode ArithmeticExpression} + * @see {@linkcode ArithmeticOperator} + * @see {@linkcode BitwiseExpression} + * @see {@linkcode BitwiseOperator} + * @see {@linkcode LogicalExpression} + * @see {@linkcode LogicalOperator} + * @see {@linkcode RelationalExpression} + * @see {@linkcode RelationalOperator} + * @see {@linkcode Type} + */ +type BinaryOperatorResult = + | [ + operator: ArithmeticOperator, + type: Type + ] + | [ + operator: BitwiseOperator, + type: Type + ] + | [ + operator: EqualityOperator, + type: Type + ] + | [ + operator: LogicalOperator, + type: Type + ] + | [ + operator: RelationalOperator, + type: Type + ] + +export type { BinaryOperatorResult as default } diff --git a/src/types/result-operator-update.ts b/src/types/result-operator-update.ts new file mode 100644 index 0000000..084d0ff --- /dev/null +++ b/src/types/result-operator-update.ts @@ -0,0 +1,35 @@ +/** + * @file Type Aliases - UpdateOperatorResult + * @module esast-util-from-code/types/UpdateOperatorResult + */ + +import type { UpdateOperator } from '@flex-development/esast' +import type { Point } from '@flex-development/unist-util-parsec' + +/** + * Update operator parser result. + */ +type UpdateOperatorResult = { + /** + * Operator end point. + * + * @see {@linkcode Point} + */ + end: Point + + /** + * Update operator. + * + * @see {@linkcode UpdateOperator} + */ + operator: UpdateOperator + + /** + * Operator start point. + * + * @see {@linkcode Point} + */ + start: Point +} + +export type { UpdateOperatorResult as default }