diff --git a/.chronus/changes/trailing-delimited-most-locations-2024-9-23-16-28-11.md b/.chronus/changes/trailing-delimited-most-locations-2024-9-23-16-28-11.md new file mode 100644 index 0000000000..c305222010 --- /dev/null +++ b/.chronus/changes/trailing-delimited-most-locations-2024-9-23-16-28-11.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Allow trailing delimiter in array values, tuple, decorator declaration, scalar initializer, etc. diff --git a/packages/compiler/src/core/messages.ts b/packages/compiler/src/core/messages.ts index d0033888ca..3198159fcf 100644 --- a/packages/compiler/src/core/messages.ts +++ b/packages/compiler/src/core/messages.ts @@ -150,12 +150,6 @@ const diagnostics = { typeofTarget: "Typeof expects a value literal or value reference.", }, }, - "trailing-token": { - severity: "error", - messages: { - default: paramMessage`Trailing ${"token"}`, - }, - }, "unknown-directive": { severity: "error", messages: { diff --git a/packages/compiler/src/core/parser.ts b/packages/compiler/src/core/parser.ts index c12954ffce..1b7116a7a6 100644 --- a/packages/compiler/src/core/parser.ts +++ b/packages/compiler/src/core/parser.ts @@ -171,7 +171,6 @@ interface ListKind { readonly delimiter: DelimiterToken; readonly toleratedDelimiter: DelimiterToken; readonly toleratedDelimiterIsValid: boolean; - readonly trailingDelimiterIsValid: boolean; readonly invalidAnnotationTarget?: string; readonly allowedStatementKeyword: Token; } @@ -193,7 +192,6 @@ namespace ListKind { const PropertiesBase = { allowEmpty: true, toleratedDelimiterIsValid: true, - trailingDelimiterIsValid: true, allowedStatementKeyword: Token.None, } as const; @@ -269,7 +267,6 @@ namespace ListKind { delimiter: Token.Comma, toleratedDelimiter: Token.Semicolon, toleratedDelimiterIsValid: false, - trailingDelimiterIsValid: false, invalidAnnotationTarget: "expression", allowedStatementKeyword: Token.None, } as const; @@ -3230,23 +3227,11 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa } r.items.push(item); - const delimiter = token(); - const delimiterPos = tokenPos(); if (parseOptionalDelimiter(kind)) { // Delimiter found: check if it's trailing. if (parseOptional(kind.close)) { mutate(r.range).end = previousTokenEnd; - if (!kind.trailingDelimiterIsValid) { - error({ - code: "trailing-token", - format: { token: TokenDisplay[delimiter] }, - target: { - pos: delimiterPos, - end: delimiterPos + 1, - }, - }); - } // It was trailing and we've consumed the close token. break; } diff --git a/packages/compiler/test/parser.test.ts b/packages/compiler/test/parser.test.ts index ff3169e504..749d1fe706 100644 --- a/packages/compiler/test/parser.test.ts +++ b/packages/compiler/test/parser.test.ts @@ -168,11 +168,18 @@ describe("compiler: parser", () => { `scalar bar extends uuid { init fromOther(abc: string) }`, + `scalar bar extends uuid { + init trailingComma(abc: string, def: string,) + }`, ]); parseErrorEach([["scalar uuid is string;", [{ message: "'{' expected." }]]]); }); + describe("operation statements", () => { + parseEach(["op foo(): int32;", "op trailingCommas(a: string,b: other,): int32;"]); + }); + describe("interface statements", () => { parseEach([ "interface Foo { }", @@ -202,6 +209,7 @@ describe("compiler: parser", () => { 'namespace A { op b(param: [number, string]): [1, "hi"]; }', "alias EmptyTuple = [];", "model Template { }", + "alias TrailingComma = [1, 2,];", ]); }); @@ -245,6 +253,7 @@ describe("compiler: parser", () => { `const a = int8(123);`, `const a = utcDateTime.fromISO("abc");`, `const a = utcDateTime.fromISO("abc", "def");`, + `const trailingComma = utcDateTime.fromISO("abc", "def",);`, ]); parseErrorEach([ [`const a = int8(123;`, [{ message: "')' expected." }]], @@ -266,6 +275,7 @@ describe("compiler: parser", () => { `const A = #["abc"];`, `const A = #["abc", 123];`, `const A = #["abc", 123, #{nested: true}];`, + `const Trailing = #["abc", 123,];`, ]); }); @@ -814,6 +824,7 @@ describe("compiler: parser", () => { "extern dec myDec(target: Type, optional?: StringLiteral);", "extern dec myDec(target: Type, ...rest: StringLiteral[]);", "extern dec myDec(target, arg1, ...rest);", + "extern dec trailingComma(target, arg1: other, arg2: string,);", ]); parseErrorEach([ @@ -861,6 +872,7 @@ describe("compiler: parser", () => { "extern fn myDec(optional?: StringLiteral): void;", "extern fn myDec(...rest: StringLiteral[]): void;", "extern fn myDec(arg1, ...rest): void;", + "extern fn trailingComma(arg1: other, arg2: string,);", ]); parseErrorEach([ @@ -1237,6 +1249,14 @@ describe("compiler: parser", () => { ); }); + describe("template parameters", () => { + parseEach(["model Foo {}", "model TrailingComma {}"]); + }); + + describe("template arguments", () => { + parseEach(["alias Test = Foo;", "alias TrailingComma = Foo;"]); + }); + describe("annotations order", () => { function expectHasDocComment(script: TypeSpecScriptNode, content: string, index: number = 0) { const docs = script.statements[0].docs;