diff --git a/.chronus/changes/reserve-keywords-2025-2-12-22-22-24.md b/.chronus/changes/reserve-keywords-2025-2-12-22-22-24.md new file mode 100644 index 00000000000..8553921358e --- /dev/null +++ b/.chronus/changes/reserve-keywords-2025-2-12-22-22-24.md @@ -0,0 +1,38 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: breaking +packages: + - "@typespec/compiler" +--- + +Adding new keywords for future use: + - `statemachine` + - `macro` + - `package` + - `metadata` + - `env` + - `arg` + - `declare` + - `array` + - `struct` + - `record` + - `module` + - `trait` + - `this` + - `self` + - `super` + - `keyof` + - `with` + - `implements` + - `impl` + - `satisfies` + - `flag` + - `auto` + - `partial` + - `private` + - `public` + - `protected` + - `internal` + - `sealed` + - `local` + - `async` diff --git a/.chronus/changes/reserve-keywords-2025-2-13-1-29-22.md b/.chronus/changes/reserve-keywords-2025-2-13-1-29-22.md new file mode 100644 index 00000000000..68b0c94322d --- /dev/null +++ b/.chronus/changes/reserve-keywords-2025-2-13-1-29-22.md @@ -0,0 +1,10 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: internal +packages: + - "@typespec/http-specs" + - "@typespec/openapi3" + - "@typespec/protobuf" +--- + +Reserve keywords diff --git a/packages/compiler/src/core/helpers/syntax-utils.ts b/packages/compiler/src/core/helpers/syntax-utils.ts index 9e5deaf29c8..6b7346b2fe3 100644 --- a/packages/compiler/src/core/helpers/syntax-utils.ts +++ b/packages/compiler/src/core/helpers/syntax-utils.ts @@ -1,5 +1,5 @@ import { CharCode, isIdentifierContinue, isIdentifierStart, utf16CodeUnits } from "../charcode.js"; -import { Keywords } from "../scanner.js"; +import { Keywords, ReservedKeywords } from "../scanner.js"; import { IdentifierNode, MemberExpressionNode, SyntaxKind, TypeReferenceNode } from "../types.js"; /** @@ -13,8 +13,11 @@ import { IdentifierNode, MemberExpressionNode, SyntaxKind, TypeReferenceNode } f * printIdentifier("foo bar") // `foo bar` * ``` */ -export function printIdentifier(sv: string) { - if (needBacktick(sv)) { +export function printIdentifier( + sv: string, + /** @internal */ context: "allow-reserved" | "disallow-reserved" = "disallow-reserved", +) { + if (needBacktick(sv, context)) { const escapedString = sv .replace(/\\/g, "\\\\") .replace(/\n/g, "\\n") @@ -27,10 +30,13 @@ export function printIdentifier(sv: string) { } } -function needBacktick(sv: string) { +function needBacktick(sv: string, context: "allow-reserved" | "disallow-reserved"): boolean { if (sv.length === 0) { return false; } + if (context === "allow-reserved" && ReservedKeywords.has(sv)) { + return false; + } if (Keywords.has(sv)) { return true; } diff --git a/packages/compiler/src/core/messages.ts b/packages/compiler/src/core/messages.ts index f10097fa469..33b68d93cd5 100644 --- a/packages/compiler/src/core/messages.ts +++ b/packages/compiler/src/core/messages.ts @@ -180,6 +180,7 @@ const diagnostics = { severity: "error", messages: { default: "Keyword cannot be used as identifier.", + future: paramMessage`${"name"} is a reserved keyword`, }, }, "invalid-directive-location": { diff --git a/packages/compiler/src/core/parser.ts b/packages/compiler/src/core/parser.ts index f975e38d000..28c174c37d1 100644 --- a/packages/compiler/src/core/parser.ts +++ b/packages/compiler/src/core/parser.ts @@ -7,6 +7,7 @@ import { isComment, isKeyword, isPunctuation, + isReservedKeyword, isStatementKeyword, isTrivia, skipContinuousIdentifier, @@ -737,8 +738,29 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa }; } + function parseIdOrValueForVariant(): Expression { + const nextToken = token(); + + let id: IdentifierNode | undefined; + if (isReservedKeyword(nextToken)) { + id = parseIdentifier({ allowReservedIdentifier: true }); + // If the next token is not a colon this means we tried to use the reserved keyword as a type reference + if (token() !== Token.Colon) { + error({ code: "reserved-identifier", messageId: "future", format: { name: id.sv } }); + } + return { + kind: SyntaxKind.TypeReference, + target: id, + arguments: [], + ...finishNode(id.pos), + }; + } else { + return parseExpression(); + } + } + function parseUnionVariant(pos: number, decorators: DecoratorExpressionNode[]): UnionVariantNode { - const idOrExpr = parseExpression(); + const idOrExpr = parseIdOrValueForVariant(); if (parseOptional(Token.Colon)) { let id: IdentifierNode | undefined = undefined; @@ -785,7 +807,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa function parseUsingStatement(pos: number): UsingStatementNode { parseExpected(Token.UsingKeyword); - const name = parseIdentifierOrMemberExpression(undefined, true); + const name = parseIdentifierOrMemberExpression(); parseExpected(Token.Semicolon); return { @@ -1010,6 +1032,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa const id = parseIdentifier({ message: "property", allowStringLiteral: true, + allowReservedIdentifier: true, }); const optional = parseOptional(Token.Question); @@ -1056,6 +1079,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa function parseObjectLiteralProperty(pos: number): ObjectLiteralPropertyNode { const id = parseIdentifier({ message: "property", + allowReservedIdentifier: true, }); parseExpected(Token.Colon); @@ -1170,6 +1194,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa const id = parseIdentifier({ message: "enumMember", allowStringLiteral: true, + allowReservedIdentifier: true, }); let value: StringLiteralNode | NumericLiteralNode | undefined; @@ -1366,7 +1391,10 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa message?: keyof CompilerDiagnostics["token-expected"], ): TypeReferenceNode { const pos = tokenPos(); - const target = parseIdentifierOrMemberExpression(message); + const target = parseIdentifierOrMemberExpression({ + message, + allowReservedIdentifierInMember: true, + }); return parseReferenceExpressionInternal(target, pos); } @@ -1374,7 +1402,10 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa message?: keyof CompilerDiagnostics["token-expected"], ): TypeReferenceNode | CallExpressionNode { const pos = tokenPos(); - const target = parseIdentifierOrMemberExpression(message); + const target = parseIdentifierOrMemberExpression({ + message, + allowReservedIdentifierInMember: true, + }); if (token() === Token.OpenParen) { const { items: args } = parseList(ListKind.FunctionArguments, parseExpression); return { @@ -1451,7 +1482,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa // identifier. We want to parse `@ model Foo` as invalid decorator // `@` applied to `model Foo`, and not as `@model` // applied to invalid statement `Foo`. - const target = parseIdentifierOrMemberExpression(undefined, false); + const target = parseIdentifierOrMemberExpression({ allowReservedIdentifierInMember: true }); const { items: args } = parseOptionalList(ListKind.DecoratorArguments, parseExpression); if (args.length === 0) { error({ code: "augment-decorator-target" }); @@ -1513,7 +1544,10 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa // identifier. We want to parse `@ model Foo` as invalid decorator // `@` applied to `model Foo`, and not as `@model` // applied to invalid statement `Foo`. - const target = parseIdentifierOrMemberExpression(undefined, false); + const target = parseIdentifierOrMemberExpression({ + allowReservedIdentifier: true, + allowReservedIdentifierInMember: true, + }); const { items: args } = parseOptionalList(ListKind.DecoratorArguments, parseExpression); return { kind: SyntaxKind.DecoratorExpression, @@ -1581,14 +1615,16 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa } } - function parseIdentifierOrMemberExpression( - message?: keyof CompilerDiagnostics["token-expected"], - recoverFromKeyword = true, - ): IdentifierNode | MemberExpressionNode { + function parseIdentifierOrMemberExpression(options?: { + message?: keyof CompilerDiagnostics["token-expected"]; + // Temporary solution see doc on parseIdentifier + allowReservedIdentifier?: boolean; + allowReservedIdentifierInMember?: boolean; + }): IdentifierNode | MemberExpressionNode { const pos = tokenPos(); let base: IdentifierNode | MemberExpressionNode = parseIdentifier({ - message, - recoverFromKeyword, + message: options?.message, + allowReservedIdentifier: options?.allowReservedIdentifier, }); while (token() !== Token.EndOfFile) { if (parseOptional(Token.Dot)) { @@ -1601,7 +1637,7 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa // `@Outer.model` applied to invalid statement `M {}` instead of // having incomplete decorator `@Outer.` applied to `model M {}`. id: parseIdentifier({ - recoverFromKeyword: false, + allowReservedIdentifier: options?.allowReservedIdentifierInMember, }), selector: ".", ...finishNode(pos), @@ -1912,10 +1948,17 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa function parseIdentifier(options?: { message?: keyof CompilerDiagnostics["token-expected"]; allowStringLiteral?: boolean; // Allow string literals to be used as identifiers for backward-compatibility, but convert to an identifier node. - recoverFromKeyword?: boolean; + + // Temporary solution to allow reserved keywords as identifiers in certain contexts. This should get expanded to a more general solution per keyword category. + allowReservedIdentifier?: boolean; }): IdentifierNode { - if (options?.recoverFromKeyword !== false && isKeyword(token())) { + if (isKeyword(token())) { error({ code: "reserved-identifier" }); + return createMissingIdentifier(); + } else if (isReservedKeyword(token())) { + if (!options?.allowReservedIdentifier) { + error({ code: "reserved-identifier", messageId: "future", format: { name: tokenValue() } }); + } } else if ( token() !== Token.Identifier && (!options?.allowStringLiteral || token() !== Token.StringLiteral) diff --git a/packages/compiler/src/core/scanner.ts b/packages/compiler/src/core/scanner.ts index 540c5f62b65..5158b23e10c 100644 --- a/packages/compiler/src/core/scanner.ts +++ b/packages/compiler/src/core/scanner.ts @@ -160,7 +160,44 @@ export enum Token { /** @internal */ __EndKeyword, /////////////////////////////////////////////////////////////// - /** @internal */ __Count = __EndKeyword, + /** @internal */ __StartReservedKeyword = __EndKeyword, + /////////////////////////////////////////////////////////////// + // List of keywords that have special meaning in the language but are reserved for future use + StatemachineKeyword = __StartReservedKeyword, + MacroKeyword, + PackageKeyword, + MetadataKeyword, + EnvKeyword, + ArgKeyword, + DeclareKeyword, + ArrayKeyword, + StructKeyword, + RecordKeyword, + ModuleKeyword, + TraitKeyword, + ThisKeyword, + SelfKeyword, + SuperKeyword, + KeyofKeyword, + WithKeyword, + ImplementsKeyword, + ImplKeyword, + SatisfiesKeyword, + FlagKeyword, + AutoKeyword, + PartialKeyword, + PrivateKeyword, + PublicKeyword, + ProtectedKeyword, + InternalKeyword, + SealedKeyword, + LocalKeyword, + AsyncKeyword, + + /** @internal */ __EndReservedKeyword, + /////////////////////////////////////////////////////////////// + + /** @internal */ __Count = __EndReservedKeyword, } export type DocToken = @@ -264,6 +301,38 @@ export const TokenDisplay = getTokenDisplayTable([ [Token.NeverKeyword, "'never'"], [Token.UnknownKeyword, "'unknown'"], [Token.ExternKeyword, "'extern'"], + + // Reserved keywords + [Token.StatemachineKeyword, "'statemachine'"], + [Token.MacroKeyword, "'macro'"], + [Token.PackageKeyword, "'package'"], + [Token.MetadataKeyword, "'metadata'"], + [Token.EnvKeyword, "'env'"], + [Token.ArgKeyword, "'arg'"], + [Token.DeclareKeyword, "'declare'"], + [Token.ArrayKeyword, "'array'"], + [Token.StructKeyword, "'struct'"], + [Token.RecordKeyword, "'record'"], + [Token.ModuleKeyword, "'module'"], + [Token.TraitKeyword, "'trait'"], + [Token.ThisKeyword, "'this'"], + [Token.SelfKeyword, "'self'"], + [Token.SuperKeyword, "'super'"], + [Token.KeyofKeyword, "'keyof'"], + [Token.WithKeyword, "'with'"], + [Token.ImplementsKeyword, "'implements'"], + [Token.ImplKeyword, "'impl'"], + [Token.SatisfiesKeyword, "'satisfies'"], + [Token.FlagKeyword, "'flag'"], + [Token.AutoKeyword, "'auto'"], + [Token.PartialKeyword, "'partial'"], + [Token.PrivateKeyword, "'private'"], + [Token.PublicKeyword, "'public'"], + [Token.ProtectedKeyword, "'protected'"], + [Token.InternalKeyword, "'internal'"], + [Token.SealedKeyword, "'sealed'"], + [Token.LocalKeyword, "'local'"], + [Token.AsyncKeyword, "'async'"], ]); /** @internal */ @@ -296,12 +365,78 @@ export const Keywords: ReadonlyMap = new Map([ ["never", Token.NeverKeyword], ["unknown", Token.UnknownKeyword], ["extern", Token.ExternKeyword], + + // Reserved keywords + ["statemachine", Token.StatemachineKeyword], + ["macro", Token.MacroKeyword], + ["package", Token.PackageKeyword], + ["metadata", Token.MetadataKeyword], + ["env", Token.EnvKeyword], + ["arg", Token.ArgKeyword], + ["declare", Token.DeclareKeyword], + ["array", Token.ArrayKeyword], + ["struct", Token.StructKeyword], + ["record", Token.RecordKeyword], + ["module", Token.ModuleKeyword], + ["trait", Token.TraitKeyword], + ["this", Token.ThisKeyword], + ["self", Token.SelfKeyword], + ["super", Token.SuperKeyword], + ["keyof", Token.KeyofKeyword], + ["with", Token.WithKeyword], + ["implements", Token.ImplementsKeyword], + ["impl", Token.ImplKeyword], + ["satisfies", Token.SatisfiesKeyword], + ["flag", Token.FlagKeyword], + ["auto", Token.AutoKeyword], + ["partial", Token.PartialKeyword], + ["private", Token.PrivateKeyword], + ["public", Token.PublicKeyword], + ["protected", Token.ProtectedKeyword], + ["internal", Token.InternalKeyword], + ["sealed", Token.SealedKeyword], + ["local", Token.LocalKeyword], + ["async", Token.AsyncKeyword], +]); +/** @internal */ +export const ReservedKeywords: ReadonlyMap = new Map([ + // Reserved keywords + ["statemachine", Token.StatemachineKeyword], + ["macro", Token.MacroKeyword], + ["package", Token.PackageKeyword], + ["metadata", Token.MetadataKeyword], + ["env", Token.EnvKeyword], + ["arg", Token.ArgKeyword], + ["declare", Token.DeclareKeyword], + ["array", Token.ArrayKeyword], + ["struct", Token.StructKeyword], + ["record", Token.RecordKeyword], + ["module", Token.ModuleKeyword], + ["trait", Token.TraitKeyword], + ["this", Token.ThisKeyword], + ["self", Token.SelfKeyword], + ["super", Token.SuperKeyword], + ["keyof", Token.KeyofKeyword], + ["with", Token.WithKeyword], + ["implements", Token.ImplementsKeyword], + ["impl", Token.ImplKeyword], + ["satisfies", Token.SatisfiesKeyword], + ["flag", Token.FlagKeyword], + ["auto", Token.AutoKeyword], + ["partial", Token.PartialKeyword], + ["private", Token.PrivateKeyword], + ["public", Token.PublicKeyword], + ["protected", Token.ProtectedKeyword], + ["internal", Token.InternalKeyword], + ["sealed", Token.SealedKeyword], + ["local", Token.LocalKeyword], + ["async", Token.AsyncKeyword], ]); /** @internal */ export const enum KeywordLimit { MinLength = 2, - MaxLength = 10, + MaxLength = 12, } export interface Scanner { @@ -392,6 +527,11 @@ export function isKeyword(token: Token) { return token >= Token.__StartKeyword && token < Token.__EndKeyword; } +/** If is a keyword with no actual use right now but will be in the future. */ +export function isReservedKeyword(token: Token) { + return token >= Token.__StartReservedKeyword && token < Token.__EndReservedKeyword; +} + export function isPunctuation(token: Token) { return token >= Token.__StartPunctuation && token < Token.__EndPunctuation; } diff --git a/packages/compiler/src/formatter/print/printer.ts b/packages/compiler/src/formatter/print/printer.ts index 752c2ad4d2b..58291e6d62e 100644 --- a/packages/compiler/src/formatter/print/printer.ts +++ b/packages/compiler/src/formatter/print/printer.ts @@ -180,7 +180,7 @@ export function printNode( return printInterfaceStatement(path as AstPath, options, print); // Others. case SyntaxKind.Identifier: - return printIdentifier(node, options); + return printIdentifier(node); case SyntaxKind.StringLiteral: return printStringLiteral(path as AstPath, options); case SyntaxKind.NumericLiteral: @@ -507,7 +507,12 @@ export function printDecorator( print: PrettierChildPrint, ) { const args = printDecoratorArgs(path, options, print); - return ["@", path.call(print, "target"), args]; + const node = path.node; + const name = + node.target.kind === SyntaxKind.Identifier + ? printIdentifier(node.target, "allow-reserved") + : path.call(print, "target"); + return ["@", name, args]; } export function printAugmentDecorator( @@ -902,7 +907,8 @@ export function printMemberExpression( ): Doc { const node = path.node; - return [node.base ? [path.call(print, "base"), node.selector] : "", path.call(print, "id")]; + const id = printIdentifier(node.id, "allow-reserved"); + return [node.base ? [path.call(print, "base"), node.selector] : "", id]; } export function printModelExpression( @@ -952,7 +958,7 @@ export function printObjectLiteralProperty( print: PrettierChildPrint, ) { const node = path.node; - const id = printIdentifier(node.id, options); + const id = printIdentifier(node.id, "allow-reserved"); return [printDirectives(path, options, print), id, ": ", path.call(print, "value")]; } @@ -1140,7 +1146,7 @@ export function printModelProperty( const { decorators } = printDecorators(path as AstPath, options, print, { tryInline: DecoratorsTryInline.modelProperty, }); - const id = printIdentifier(node.id, options); + const id = printIdentifier(node.id, "allow-reserved"); return [ printDirectives(path, options, print), decorators, @@ -1151,8 +1157,11 @@ export function printModelProperty( ]; } -function printIdentifier(id: IdentifierNode, options: TypeSpecPrettierOptions) { - return printIdentifierString(id.sv); +function printIdentifier( + id: IdentifierNode, + context: "allow-reserved" | "disallow-reserved" = "disallow-reserved", +) { + return printIdentifierString(id.sv, context); } function isModelExpressionInBlock(path: AstPath) { diff --git a/packages/compiler/src/server/type-signature.ts b/packages/compiler/src/server/type-signature.ts index 42844b372ed..e4d9df1ef83 100644 --- a/packages/compiler/src/server/type-signature.ts +++ b/packages/compiler/src/server/type-signature.ts @@ -119,7 +119,7 @@ function getStringTemplateSignature(stringTemplate: StringTemplate) { function getModelPropertySignature(property: ModelProperty) { const ns = getQualifier(property.model); - return `${ns}${printIdentifier(property.name)}: ${getPrintableTypeName(property.type)}`; + return `${ns}${printIdentifier(property.name, "allow-reserved")}: ${getPrintableTypeName(property.type)}`; } function getUnionVariantSignature(variant: UnionVariant) { @@ -127,15 +127,15 @@ function getUnionVariantSignature(variant: UnionVariant) { return getPrintableTypeName(variant.type); } const ns = getQualifier(variant.union); - return `${ns}${printIdentifier(variant.name)}: ${getPrintableTypeName(variant.type)}`; + return `${ns}${printIdentifier(variant.name, "allow-reserved")}: ${getPrintableTypeName(variant.type)}`; } function getEnumMemberSignature(member: EnumMember) { const ns = getQualifier(member.enum); const value = typeof member.value === "string" ? `"${member.value}"` : member.value; return value === undefined - ? `${ns}${printIdentifier(member.name)}` - : `${ns}${printIdentifier(member.name)}: ${value}`; + ? `${ns}${printIdentifier(member.name, "allow-reserved")}` + : `${ns}${printIdentifier(member.name, "allow-reserved")}: ${value}`; } function getAliasSignature(alias: AliasStatementNode) { diff --git a/packages/compiler/test/parser.test.ts b/packages/compiler/test/parser.test.ts index 29fd364f1ab..107f64a5c81 100644 --- a/packages/compiler/test/parser.test.ts +++ b/packages/compiler/test/parser.test.ts @@ -21,6 +21,34 @@ import { import { dumpAST } from "./ast-test-utils.js"; describe("compiler: parser", () => { + describe("future reserved keywords", () => { + const reserved = ["statemachine"]; + // Allowed as members + parseEach(reserved.map((x) => `model Foo { ${x}: string}`)); + parseEach(reserved.map((x) => `enum Foo { ${x} }`)); + parseEach(reserved.map((x) => `union Foo { ${x}: string }`)); + parseEach(reserved.map((x) => `const a = #{ ${x}: string };`)); + + // Allowed in decorator calls + parseEach(reserved.map((x) => `@${x} model Foo {}`)); + + // Allowed in member reference + parseEach(reserved.map((x) => `alias T = Base.${x};`)); + + // Error when used in declaration name + parseErrorEach( + reserved.map((x) => [`model ${x} {}`, [{ message: `${x} is a reserved keyword` }]]), + ); + + // Error when used as base or identifier reference + parseErrorEach( + reserved.map((x) => [`alias T = ${x};`, [{ message: `${x} is a reserved keyword` }]]), + ); + parseErrorEach( + reserved.map((x) => [`alias T = ${x}.prop;`, [{ message: `${x} is a reserved keyword` }]]), + ); + }); + describe("import statements", () => { parseEach(['import "x";']); diff --git a/packages/compiler/test/scanner.test.ts b/packages/compiler/test/scanner.test.ts index 540d38f2c62..41f8010a6ff 100644 --- a/packages/compiler/test/scanner.test.ts +++ b/packages/compiler/test/scanner.test.ts @@ -12,6 +12,7 @@ import { createScanner, isKeyword, isPunctuation, + isReservedKeyword, isStatementKeyword, } from "../src/core/scanner.js"; import { DiagnosticMatch, expectDiagnostics } from "../src/testing/expect.js"; @@ -103,10 +104,10 @@ function verify(tokens: TokenEntry[], expecting: TokenEntry[]) { describe("compiler: scanner", () => { /** verifies that we can scan tokens and get back some output. */ it("smoketest", () => { - const all = tokens('\tthis was "a" test'); + const all = tokens('\tit was "a" test'); verify(all, [ [Token.Whitespace], - [Token.Identifier, "this", { value: "this" }], + [Token.Identifier, "it", { value: "it" }], [Token.Whitespace], [Token.Identifier, "was", { value: "was" }], [Token.Whitespace], @@ -410,14 +411,19 @@ describe("compiler: scanner", () => { maxKeywordLengthFound = Math.max(maxKeywordLengthFound, name.length); assert.strictEqual(TokenDisplay[token], `'${name}'`, "token display should match"); - assert(isKeyword(token), `${name} should be classified as a keyword`); - if (nonStatementKeywords.includes(token)) { - assert( - !isStatementKeyword(token), - `${name} should not be classified as a statement keyword`, - ); - } else { - assert(isStatementKeyword(token), `${name} should be classified as statement keyword`); + assert( + isKeyword(token) || isReservedKeyword(token), + `${name} should be classified as a keyword or reserved keyword`, + ); + if (!isReservedKeyword(token)) { + if (nonStatementKeywords.includes(token)) { + assert( + !isStatementKeyword(token), + `${name} should not be classified as a statement keyword`, + ); + } else { + assert(isStatementKeyword(token), `${name} should be classified as statement keyword`); + } } } diff --git a/packages/http-specs/specs/routes/main.tsp b/packages/http-specs/specs/routes/main.tsp index dbc560016b1..d87580ec6c0 100644 --- a/packages/http-specs/specs/routes/main.tsp +++ b/packages/http-specs/specs/routes/main.tsp @@ -99,7 +99,7 @@ namespace PathParameters { Expected path: /routes/path/simple/standard/arraya,b """) @route("array{param}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -108,7 +108,7 @@ namespace PathParameters { Expected path: /routes/path/simple/standard/recorda,1,b,2 """) @route("record{param}") - op record(param: Record): void; + op `record`(param: Record): void; } @route("explode") @@ -129,7 +129,7 @@ namespace PathParameters { Expected path: /routes/path/simple/explode/arraya.b """) @route("array{param*}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -138,7 +138,7 @@ namespace PathParameters { Expected path: /routes/path/simple/explode/recorda=1,b=2 """) @route("record{param*}") - op record(param: Record): void; + op `record`(param: Record): void; } } @@ -162,7 +162,7 @@ namespace PathParameters { Expected path: /routes/path/path/standard/array/a,b """) @route("array{/param}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -171,7 +171,7 @@ namespace PathParameters { Expected path: /routes/path/path/standard/record/a,1,b,2 """) @route("record{/param}") - op record(param: Record): void; + op `record`(param: Record): void; } @route("explode") @@ -192,7 +192,7 @@ namespace PathParameters { Expected path: /routes/path/path/explode/array/a/b """) @route("array{/param*}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -201,7 +201,7 @@ namespace PathParameters { Expected path: /routes/path/path/explode/record/a=1/b=2 """) @route("record{/param*}") - op record(param: Record): void; + op `record`(param: Record): void; } } @@ -225,7 +225,7 @@ namespace PathParameters { Expected path: /routes/path/label/standard/array.a,b """) @route("array{.param}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -234,7 +234,7 @@ namespace PathParameters { Expected path: /routes/path/label/standard/record.a,1,b,2 """) @route("record{.param}") - op record(param: Record): void; + op `record`(param: Record): void; } @route("explode") @@ -255,7 +255,7 @@ namespace PathParameters { Expected path: /routes/path/label/explode/array.a.b """) @route("array{.param*}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -264,7 +264,7 @@ namespace PathParameters { Expected path: /routes/path/label/explode/record.a=1.b=2 """) @route("record{.param*}") - op record(param: Record): void; + op `record`(param: Record): void; } } @@ -288,7 +288,7 @@ namespace PathParameters { Expected path: /routes/path/matrix/standard/array;param=a;param=b """) @route("array{;param}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -297,7 +297,7 @@ namespace PathParameters { Expected path: /routes/path/matrix/standard/record;a=1;b=2 """) @route("record{;param}") - op record(param: Record): void; + op `record`(param: Record): void; } @route("explode") @@ -318,7 +318,7 @@ namespace PathParameters { Expected path: /routes/path/matrix/explode/array;param=a;param=b """) @route("array{;param*}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -327,7 +327,7 @@ namespace PathParameters { Expected path: /routes/path/matrix/explode/record;a=1;b=2 """) @route("record{;param*}") - op record(param: Record): void; + op `record`(param: Record): void; } } } @@ -369,7 +369,7 @@ namespace QueryParameters { Expected path: /routes/query/query-expansion/standard/array?param=a,b """) @route("array{?param}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -378,7 +378,7 @@ namespace QueryParameters { Expected path: /routes/query/query-expansion/standard/record?param=a,1,b,2 """) @route("record{?param}") - op record(param: Record): void; + op `record`(param: Record): void; } @route("explode") @@ -399,7 +399,7 @@ namespace QueryParameters { Expected path: /routes/query/query-expansion/explode/array?param=a¶m=b """) @route("array{?param*}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -408,7 +408,7 @@ namespace QueryParameters { Expected path: /routes/query/query-expansion/explode/record?a=1&b=2 """) @route("record{?param*}") - op record(param: Record): void; + op `record`(param: Record): void; } } @@ -432,7 +432,7 @@ namespace QueryParameters { Expected path: /routes/query/query-continuation/standard/array?fixed=true¶m=a,b """) @route("array?fixed=true{¶m}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -441,7 +441,7 @@ namespace QueryParameters { Expected path: /routes/query/query-continuation/standard/record?fixed=true¶m=a,1,b,2 """) @route("record?fixed=true{¶m}") - op record(param: Record): void; + op `record`(param: Record): void; } @route("explode") @@ -462,7 +462,7 @@ namespace QueryParameters { Expected path: /routes/query/query-continuation/explode/array?fixed=true¶m=a¶m=b """) @route("array?fixed=true{¶m*}") - op array(param: string[]): void; + op `array`(param: string[]): void; @scenario @scenarioDoc(""" @@ -471,7 +471,7 @@ namespace QueryParameters { Expected path: /routes/query/query-continuation/explode/record?fixed=true&a=1&b=2 """) @route("record?fixed=true{¶m*}") - op record(param: Record): void; + op `record`(param: Record): void; } } } diff --git a/packages/http-specs/specs/special-words/main.tsp b/packages/http-specs/specs/special-words/main.tsp index d802c373fdc..7517d04be5a 100644 --- a/packages/http-specs/specs/special-words/main.tsp +++ b/packages/http-specs/specs/special-words/main.tsp @@ -56,7 +56,7 @@ interface Operations { @opNameScenario("and") and(): void; @opNameScenario("as") as(): void; @opNameScenario("assert") assert(): void; - @opNameScenario("async") async(): void; + @opNameScenario("async") `async`(): void; @opNameScenario("await") await(): void; @opNameScenario("break") break(): void; @opNameScenario("class") class(): void; @@ -84,7 +84,7 @@ interface Operations { @opNameScenario("return") `return`(): void; @opNameScenario("try") try(): void; @opNameScenario("while") while(): void; - @opNameScenario("with") with(): void; + @opNameScenario("with") `with`(): void; @opNameScenario("yield") yield(): void; } @@ -144,7 +144,7 @@ namespace Models { model and is Base; model as is Base; model assert is Base; - model async is Base; + model `async` is Base; model await is Base; model break is Base; model class is Base; @@ -172,13 +172,13 @@ namespace Models { model `return` is Base; model try is Base; model while is Base; - model with is Base; + model `with` is Base; model yield is Base; @modelNameScenario("and") op withAnd(@body body: and): void; @modelNameScenario("as") op withAs(@body body: as): void; @modelNameScenario("assert") op withAssert(@body body: assert): void; - @modelNameScenario("async") op withAsync(@body body: async): void; + @modelNameScenario("async") op withAsync(@body body: `async`): void; @modelNameScenario("await") op withAwait(@body body: await): void; @modelNameScenario("break") op withBreak(@body body: break): void; @modelNameScenario("class") op withClass(@body body: class): void; @@ -206,7 +206,7 @@ namespace Models { @modelNameScenario("return") op withReturn(@body body: `return`): void; @modelNameScenario("try") op withTry(@body body: try): void; @modelNameScenario("while") op withWhile(@body body: while): void; - @modelNameScenario("with") op withWith(@body body: with): void; + @modelNameScenario("with") op withWith(@body body: `with`): void; @modelNameScenario("yield") op withYield(@body body: yield): void; } diff --git a/packages/openapi3/src/cli/actions/convert/utils/get-scope-and-name.ts b/packages/openapi3/src/cli/actions/convert/utils/get-scope-and-name.ts index b0bdf1c9f23..059ecdd927b 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/get-scope-and-name.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/get-scope-and-name.ts @@ -5,7 +5,7 @@ export function getScopeAndName(originalName: string): ScopeAndName { const path = originalName.split("."); const name = path.pop()!; - return { scope: path.map(printIdentifier), name: printIdentifier(name), rawName: name }; + return { scope: path.map((x) => printIdentifier(x)), name: printIdentifier(name), rawName: name }; } export function scopesMatch(a: string[], b: string[]): boolean { diff --git a/packages/protobuf/lib/proto.tsp b/packages/protobuf/lib/proto.tsp index 315037677d1..d9f37dd3aa0 100644 --- a/packages/protobuf/lib/proto.tsp +++ b/packages/protobuf/lib/proto.tsp @@ -252,7 +252,7 @@ model PackageDetails { * * @param details the optional details of the package */ -extern dec package(target: TypeSpec.Reflection.Namespace, details?: PackageDetails); +extern dec `package`(target: TypeSpec.Reflection.Namespace, details?: PackageDetails); /** * The streaming mode of an operation. One of: