Merge pull request #4450 from timotheeguerin/add-typespec-lang

Add support for TypeSpec language
This commit is contained in:
Henning Dieterichs 2024-04-23 13:56:08 +02:00 коммит произвёл GitHub
Родитель 0037b13fb5 ed45c2e7b8
Коммит 707338797b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 726 добавлений и 0 удалений

Просмотреть файл

@ -79,6 +79,7 @@ import './systemverilog/systemverilog.contribution';
import './tcl/tcl.contribution';
import './twig/twig.contribution';
import './typescript/typescript.contribution';
import './typespec/typespec.contribution';
import './vb/vb.contribution';
import './wgsl/wgsl.contribution';
import './xml/xml.contribution';

Просмотреть файл

@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerLanguage } from '../_.contribution';
declare var AMD: any;
declare var require: any;
registerLanguage({
id: 'typespec',
extensions: ['.tsp'],
aliases: ['TypeSpec'],
loader: () => {
if (AMD) {
return new Promise((resolve, reject) => {
require(['vs/basic-languages/typespec/typespec'], resolve, reject);
});
} else {
return import('./typespec');
}
}
});

Просмотреть файл

@ -0,0 +1,500 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { testTokenization } from '../test/testRunner';
// Those test were auto generated from the test in the https://github.com/microsoft/typespec repo
// to keep in sync you can follow the instruction in https://github.com/microsoft/typespec/blob/main/packages/monarch/README.md
testTokenization('typespec', [
[
{
line: 'import "@typespec/http";',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 6,
type: ''
},
{
startIndex: 7,
type: 'string.tsp'
},
{
startIndex: 23,
type: ''
}
]
}
],
[
{
line: 'using TypeSpec.Http',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 5,
type: ''
},
{
startIndex: 6,
type: 'identifier.tsp'
},
{
startIndex: 14,
type: ''
},
{
startIndex: 15,
type: 'identifier.tsp'
}
]
}
],
[
{
line: 'namespace Foo {}',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 9,
type: ''
},
{
startIndex: 10,
type: 'identifier.tsp'
},
{
startIndex: 13,
type: ''
}
]
}
],
[
{
line: 'namespace Foo {',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 9,
type: ''
},
{
startIndex: 10,
type: 'identifier.tsp'
},
{
startIndex: 13,
type: ''
}
]
},
{
line: ' model Bar {}',
tokens: [
{
startIndex: 0,
type: ''
},
{
startIndex: 4,
type: 'keyword.tsp'
},
{
startIndex: 9,
type: ''
},
{
startIndex: 10,
type: 'identifier.tsp'
},
{
startIndex: 13,
type: ''
}
]
},
{
line: ' }',
tokens: [
{
startIndex: 0,
type: ''
}
]
}
],
[
{
line: 'model Foo {}',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 5,
type: ''
},
{
startIndex: 6,
type: 'identifier.tsp'
},
{
startIndex: 9,
type: ''
}
]
}
],
[
{
line: 'model Foo is Bar;',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 5,
type: ''
},
{
startIndex: 6,
type: 'identifier.tsp'
},
{
startIndex: 9,
type: ''
},
{
startIndex: 10,
type: 'keyword.tsp'
},
{
startIndex: 12,
type: ''
},
{
startIndex: 13,
type: 'identifier.tsp'
},
{
startIndex: 16,
type: ''
}
]
}
],
[
{
line: 'model Foo extends Bar;',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 5,
type: ''
},
{
startIndex: 6,
type: 'identifier.tsp'
},
{
startIndex: 9,
type: ''
},
{
startIndex: 10,
type: 'keyword.tsp'
},
{
startIndex: 17,
type: ''
},
{
startIndex: 18,
type: 'identifier.tsp'
},
{
startIndex: 21,
type: ''
}
]
}
],
[
{
line: 'interface Foo {}',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 9,
type: ''
},
{
startIndex: 10,
type: 'identifier.tsp'
},
{
startIndex: 13,
type: ''
}
]
}
],
[
{
line: 'union Foo {}',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 5,
type: ''
},
{
startIndex: 6,
type: 'identifier.tsp'
},
{
startIndex: 9,
type: ''
}
]
}
],
[
{
line: 'scalar foo extends string;',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 6,
type: ''
},
{
startIndex: 7,
type: 'identifier.tsp'
},
{
startIndex: 10,
type: ''
},
{
startIndex: 11,
type: 'keyword.tsp'
},
{
startIndex: 18,
type: ''
},
{
startIndex: 19,
type: 'identifier.tsp'
},
{
startIndex: 25,
type: ''
}
]
}
],
[
{
line: 'op test(): void;',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 2,
type: ''
},
{
startIndex: 3,
type: 'identifier.tsp'
},
{
startIndex: 7,
type: ''
},
{
startIndex: 11,
type: 'keyword.tsp'
},
{
startIndex: 15,
type: ''
}
]
}
],
[
{
line: 'enum Direction { up, down }',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 4,
type: ''
},
{
startIndex: 5,
type: 'identifier.tsp'
},
{
startIndex: 14,
type: ''
},
{
startIndex: 17,
type: 'identifier.tsp'
},
{
startIndex: 19,
type: ''
},
{
startIndex: 21,
type: 'identifier.tsp'
},
{
startIndex: 25,
type: ''
}
]
}
],
[
{
line: 'alias Foo = "a" | "b";',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 5,
type: ''
},
{
startIndex: 6,
type: 'identifier.tsp'
},
{
startIndex: 9,
type: ''
},
{
startIndex: 12,
type: 'string.tsp'
},
{
startIndex: 15,
type: ''
},
{
startIndex: 18,
type: 'string.tsp'
},
{
startIndex: 21,
type: ''
}
]
}
],
[
{
line: 'alias T = """',
tokens: [
{
startIndex: 0,
type: 'keyword.tsp'
},
{
startIndex: 5,
type: ''
},
{
startIndex: 6,
type: 'identifier.tsp'
},
{
startIndex: 7,
type: ''
},
{
startIndex: 11,
type: 'string.tsp'
}
]
},
{
line: ' this',
tokens: [
{
startIndex: 0,
type: 'string.tsp'
}
]
},
{
line: ' is',
tokens: [
{
startIndex: 0,
type: 'string.tsp'
}
]
},
{
line: ' multiline',
tokens: [
{
startIndex: 0,
type: 'string.tsp'
}
]
},
{
line: ' """',
tokens: [
{
startIndex: 0,
type: 'string.tsp'
}
]
}
]
]);

Просмотреть файл

@ -0,0 +1,130 @@
import type { languages } from '../../fillers/monaco-editor-core';
const bounded = (text: string) => `\\b${text}\\b`;
const notBefore = (regex: string) => `(?!${regex})`;
const identifierStart = '[_a-zA-Z]';
const identifierContinue = '[_a-zA-Z0-9]';
const identifier = bounded(`${identifierStart}${identifierContinue}*`);
const directive = bounded(`[_a-zA-Z-0-9]+`);
const keywords = [
'import',
'model',
'scalar',
'namespace',
'op',
'interface',
'union',
'using',
'is',
'extends',
'enum',
'alias',
'return',
'void',
'if',
'else',
'projection',
'dec',
'extern',
'fn'
];
const namedLiterals = ['true', 'false', 'null', 'unknown', 'never'];
const nonCommentWs = `[ \\t\\r\\n]`;
const numericLiteral = `[0-9]+`;
export const conf: languages.LanguageConfiguration = {
comments: {
lineComment: '//',
blockComment: ['/*', '*/']
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: '/**', close: ' */', notIn: ['string'] }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' }
],
indentationRules: {
decreaseIndentPattern: new RegExp('^((?!.*?/\\*).*\\*/)?\\s*[\\}\\]].*$'),
increaseIndentPattern: new RegExp(
'^((?!//).)*(\\{([^}"\'`/]*|(\\t|[ ])*//.*)|\\([^)"\'`/]*|\\[[^\\]"\'`/]*)$'
),
// e.g. * ...| or */| or *-----*/|
unIndentedLinePattern: new RegExp(
'^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$'
)
}
};
export const language: languages.IMonarchLanguage = {
defaultToken: '',
tokenPostfix: '.tsp',
brackets: [
{ open: '{', close: '}', token: 'delimiter.curly' },
{ open: '[', close: ']', token: 'delimiter.square' },
{ open: '(', close: ')', token: 'delimiter.parenthesis' }
],
symbols: /[=:;<>]+/,
keywords,
namedLiterals,
escapes: `\\\\(u{[0-9A-Fa-f]+}|n|r|t|\\\\|"|\\\${)`,
tokenizer: {
root: [{ include: '@expression' }, { include: '@whitespace' }],
stringVerbatim: [
{ regex: `(|"|"")[^"]`, action: { token: 'string' } },
{ regex: `"""${notBefore(`"`)}`, action: { token: 'string', next: '@pop' } }
],
stringLiteral: [
{ regex: `\\\${`, action: { token: 'delimiter.bracket', next: '@bracketCounting' } },
{ regex: `[^\\\\"$]+`, action: { token: 'string' } },
{ regex: '@escapes', action: { token: 'string.escape' } },
{ regex: `\\\\.`, action: { token: 'string.escape.invalid' } },
{ regex: `"`, action: { token: 'string', next: '@pop' } }
],
bracketCounting: [
{ regex: `{`, action: { token: 'delimiter.bracket', next: '@bracketCounting' } },
{ regex: `}`, action: { token: 'delimiter.bracket', next: '@pop' } },
{ include: '@expression' }
],
comment: [
{ regex: `[^\\*]+`, action: { token: 'comment' } },
{ regex: `\\*\\/`, action: { token: 'comment', next: '@pop' } },
{ regex: `[\\/*]`, action: { token: 'comment' } }
],
whitespace: [
{ regex: nonCommentWs },
{ regex: `\\/\\*`, action: { token: 'comment', next: '@comment' } },
{ regex: `\\/\\/.*$`, action: { token: 'comment' } }
],
expression: [
{ regex: `"""`, action: { token: 'string', next: '@stringVerbatim' } },
{ regex: `"${notBefore(`""`)}`, action: { token: 'string', next: '@stringLiteral' } },
{ regex: numericLiteral, action: { token: 'number' } },
{
regex: identifier,
action: {
cases: {
'@keywords': { token: 'keyword' },
'@namedLiterals': { token: 'keyword' },
'@default': { token: 'identifier' }
}
}
},
{ regex: `@${identifier}`, action: { token: 'tag' } },
{ regex: `#${directive}`, action: { token: 'directive' } }
]
}
};

Просмотреть файл

@ -0,0 +1,71 @@
import "@typespec/rest";
import "@typespec/openapi";
import "./decorators.js";
using TypeSpec.Http;
@service({
title: "Pet Store Service",
})
/** This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters. */
namespace PetStore;
// Model types
model Pet {
name: string;
tag?: string;
@minValue(0)
@maxValue(20)
age: int32;
}
model Toy {
id: int64;
petId: int64;
name: string;
}
/** Error */
@error
model Error {
code: int32;
message: string;
}
/** Not modified */
model NotModified<Body> {
@statusCode _: 304;
@body body: Body;
}
@friendlyName("{name}ListResults", Item)
model ResponsePage<Item> {
items: Item[];
nextLink?: string;
}
model PetId {
@path petId: int32;
}
/** Manage your pets. */
@route("/pets")
namespace Pets {
/** Delete a pet. */
@delete
op delete(...PetId): OkResponse | Error;
@fancyDoc("List pets.")
op list(@query nextLink?: string): ResponsePage<Pet> | Error;
/** Returns a pet. Supports eTags. */
op read(...PetId): Pet | (NotModifiedResponse & Pet) | Error;
@post op create(@body pet: Pet): Pet | Error;
}
@route("/pets/{petId}/toys")
namespace ListPetToysResponse {
op list(@path petId: string, @query nameFilter: string): ResponsePage<Toy> | Error;
}