From ef167648ed006807f774749b2ee7d234618aeb6f Mon Sep 17 00:00:00 2001 From: ssssota Date: Thu, 6 Nov 2025 00:33:38 +0900 Subject: [PATCH 1/2] feat: add support for mixed labeled and unlabeled tuple types --- src/error.ts | 1 - src/index.ts | 7 - .../expected.json | 158 ++++++++++++++++++ test/tuple_named_and_anonymous_mixed/input.ts | 1 + 4 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 test/tuple_named_and_anonymous_mixed/expected.json create mode 100644 test/tuple_named_and_anonymous_mixed/input.ts diff --git a/src/error.ts b/src/error.ts index f7a99d1..3acf971 100644 --- a/src/error.ts +++ b/src/error.ts @@ -48,7 +48,6 @@ export const TypeScriptError = { 'You can either wrap the instantiation expression in parentheses, or delete the type arguments.', InvalidTupleMemberLabel: 'Tuple members must be labeled with a simple identifier.', MissingInterfaceName: "'interface' declarations must be followed by an identifier.", - MixedLabeledAndUnlabeledElements: 'Tuple members must all have names or all not have names.', NonAbstractClassHasAbstractMethod: 'Abstract methods can only appear within an abstract class.', NonClassMethodPropertyHasAbstractModifer: "'abstract' modifier can only appear on a class, method, or property declaration.", diff --git a/src/index.ts b/src/index.ts index 09762fa..da1d506 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1692,7 +1692,6 @@ export function tsPlugin(options?: { // Validate the elementTypes to ensure that no mandatory elements // follow optional elements let seenOptionalElement = false; - let labeledElements: boolean | null = null; node.elementTypes.forEach((elementNode) => { const { type } = elementNode; @@ -1714,12 +1713,6 @@ export function tsPlugin(options?: { elementNode = elementNode.typeAnnotation; checkType = elementNode.type; } - - const isLabeled = checkType === 'TSNamedTupleMember'; - labeledElements ??= isLabeled; - if (labeledElements !== isLabeled) { - this.raise(elementNode.start, TypeScriptError.MixedLabeledAndUnlabeledElements); - } }); return this.finishNode(node, 'TSTupleType'); diff --git a/test/tuple_named_and_anonymous_mixed/expected.json b/test/tuple_named_and_anonymous_mixed/expected.json new file mode 100644 index 0000000..bfd6df0 --- /dev/null +++ b/test/tuple_named_and_anonymous_mixed/expected.json @@ -0,0 +1,158 @@ +{ + "type": "Program", + "start": 0, + "end": 37, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 0 + } + }, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start": 0, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "id": { + "type": "Identifier", + "start": 5, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 6 + } + }, + "name": "A" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start": 9, + "end": 35, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 35 + } + }, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start": 10, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 21 + } + }, + "optional": false, + "label": { + "type": "Identifier", + "start": 10, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 13 + } + }, + "name": "foo" + }, + "elementType": { + "type": "TSStringKeyword", + "start": 15, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 21 + } + } + } + }, + { + "type": "TSRestType", + "start": 23, + "end": 34, + "loc": { + "start": { + "line": 1, + "column": 23 + }, + "end": { + "line": 1, + "column": 34 + } + }, + "typeAnnotation": { + "type": "TSArrayType", + "start": 26, + "end": 34, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 34 + } + }, + "elementType": { + "type": "TSNumberKeyword", + "start": 26, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 32 + } + } + } + } + } + ] + } + } + ], + "sourceType": "module" +} diff --git a/test/tuple_named_and_anonymous_mixed/input.ts b/test/tuple_named_and_anonymous_mixed/input.ts new file mode 100644 index 0000000..20be884 --- /dev/null +++ b/test/tuple_named_and_anonymous_mixed/input.ts @@ -0,0 +1 @@ +type A = [foo: string, ...number[]]; From a5346fb9b0f0dbad9624fcfcea9a6e7573f58f24 Mon Sep 17 00:00:00 2001 From: ssssota Date: Thu, 6 Nov 2025 00:38:45 +0900 Subject: [PATCH 2/2] chore: use tabs instead of space indent --- .../expected.json | 312 +++++++++--------- 1 file changed, 156 insertions(+), 156 deletions(-) diff --git a/test/tuple_named_and_anonymous_mixed/expected.json b/test/tuple_named_and_anonymous_mixed/expected.json index bfd6df0..43dded6 100644 --- a/test/tuple_named_and_anonymous_mixed/expected.json +++ b/test/tuple_named_and_anonymous_mixed/expected.json @@ -1,158 +1,158 @@ { - "type": "Program", - "start": 0, - "end": 37, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 2, - "column": 0 - } - }, - "body": [ - { - "type": "TSTypeAliasDeclaration", - "start": 0, - "end": 36, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 36 - } - }, - "id": { - "type": "Identifier", - "start": 5, - "end": 6, - "loc": { - "start": { - "line": 1, - "column": 5 - }, - "end": { - "line": 1, - "column": 6 - } - }, - "name": "A" - }, - "typeAnnotation": { - "type": "TSTupleType", - "start": 9, - "end": 35, - "loc": { - "start": { - "line": 1, - "column": 9 - }, - "end": { - "line": 1, - "column": 35 - } - }, - "elementTypes": [ - { - "type": "TSNamedTupleMember", - "start": 10, - "end": 21, - "loc": { - "start": { - "line": 1, - "column": 10 - }, - "end": { - "line": 1, - "column": 21 - } - }, - "optional": false, - "label": { - "type": "Identifier", - "start": 10, - "end": 13, - "loc": { - "start": { - "line": 1, - "column": 10 - }, - "end": { - "line": 1, - "column": 13 - } - }, - "name": "foo" - }, - "elementType": { - "type": "TSStringKeyword", - "start": 15, - "end": 21, - "loc": { - "start": { - "line": 1, - "column": 15 - }, - "end": { - "line": 1, - "column": 21 - } - } - } - }, - { - "type": "TSRestType", - "start": 23, - "end": 34, - "loc": { - "start": { - "line": 1, - "column": 23 - }, - "end": { - "line": 1, - "column": 34 - } - }, - "typeAnnotation": { - "type": "TSArrayType", - "start": 26, - "end": 34, - "loc": { - "start": { - "line": 1, - "column": 26 - }, - "end": { - "line": 1, - "column": 34 - } - }, - "elementType": { - "type": "TSNumberKeyword", - "start": 26, - "end": 32, - "loc": { - "start": { - "line": 1, - "column": 26 - }, - "end": { - "line": 1, - "column": 32 - } - } - } - } - } - ] - } - } - ], - "sourceType": "module" + "type": "Program", + "start": 0, + "end": 37, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 2, + "column": 0 + } + }, + "body": [ + { + "type": "TSTypeAliasDeclaration", + "start": 0, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "id": { + "type": "Identifier", + "start": 5, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 6 + } + }, + "name": "A" + }, + "typeAnnotation": { + "type": "TSTupleType", + "start": 9, + "end": 35, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 35 + } + }, + "elementTypes": [ + { + "type": "TSNamedTupleMember", + "start": 10, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 21 + } + }, + "optional": false, + "label": { + "type": "Identifier", + "start": 10, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 13 + } + }, + "name": "foo" + }, + "elementType": { + "type": "TSStringKeyword", + "start": 15, + "end": 21, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 21 + } + } + } + }, + { + "type": "TSRestType", + "start": 23, + "end": 34, + "loc": { + "start": { + "line": 1, + "column": 23 + }, + "end": { + "line": 1, + "column": 34 + } + }, + "typeAnnotation": { + "type": "TSArrayType", + "start": 26, + "end": 34, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 34 + } + }, + "elementType": { + "type": "TSNumberKeyword", + "start": 26, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 32 + } + } + } + } + } + ] + } + } + ], + "sourceType": "module" }