@swagger-api/apidom-ls
v1.0.1
Published
ApiDOM based language service library.
Readme
ApiDOM Language Service (apidom-ls)
The ApiDOM Language Service (apidom-ls) contains the language smarts powering ApiDOM supported languages processing, specifically editing experience.
ApiDOM Language Service APIs adhere to LSP Protocol and are therefore usable via a LSP Server wrapper in a variety of editors and IDEs.
Installation
After prerequisites for installing this package are satisfied, you can install it via npm CLI by running the following command:
$ npm install @swagger-api/apidom-lsSupported specifications
The ApiDOM Language Service fully supports the following API specifications:
- OpenAPI 2.0
- OpenAPI 3.0.x
- OpenAPI 3.1.0
- AsyncAPI 2.x.y
Quick start
Initializing ApiDOM Language Service
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);The capabilities of the ApiDOM Language Service can be extended by providing custom configuration.
import { DocumentLink } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
import {
Element,
MemberElement,
isStringElement,
} from "@swagger-api/apidom-core";
import { InfoElement } from '@swagger-api/apidom-ns-asyncapi-2';
import { getLanguageService, LinksProvider, config } from '@swagger-api/apidom-ls';
import type {
LanguageSettings,
ProviderMode,
LinksContext,
LinksProviderResult,
NamespaceVersion,
} from '@swagger-api/apidom-ls';
// Defining custom Links Provider
class RefLinksProvider implements LinksProvider {
break(): boolean {
return false;
}
providerMode(): ProviderMode {
return ProviderMode.REF;
}
configure(settings: LanguageSettings): void {}
async doRefLinks(
textDocument: TextDocument,
api: Element,
currentRefLinks: DocumentLink[],
linksContext?: LinksContext,
): Promise<LinksProviderResult> {
for (const link of currentRefLinks) {
if (link.target && link.target.startsWith('https://example.com')) {
link.target = link.target.replace('example.com', 'github.com');
}
}
return {
mergeStrategy: MergeStrategy.IGNORE,
links: [],
};
}
name(): string {
return 'RefProvider';
}
namespaces(): NamespaceVersion[] {
return [
{
namespace: 'asyncapi',
version: '2.6.0',
},
];
}
}
// Defining custom validation rules
const metadata = config();
const customMetadata = {
metadataMaps: metadata.metadataMaps
? JSON.parse(JSON.stringify(metadata.metadataMaps))
: undefined,
rules: metadata.rules ? JSON.parse(JSON.stringify(metadata.rules)) : undefined,
tokens: metadata.tokens ? JSON.parse(JSON.stringify(metadata.tokens)) : undefined,
symbols: metadata.symbols ? JSON.parse(JSON.stringify(metadata.symbols)) : undefined,
linterFunctions: metadata.linterFunctions,
};
// Defining custom linter functions
const apilintEqualsCustom = (element: Element, expectedValues: string[]) => {
if (element) {
if (!isStringElement(element)) {
return false;
}
return expectedValues.includes(toValue(element));
}
return true;
};
customMetadata.linterFunctions = {
asyncapi: {
apilintEqualsCustom,
},
};
// Defining custom rules
const camelCaseTitle = {
name: 'SB-API-052-title',
description: 'title MUST follow camelCase',
recommended: true,
code: 20001,
source: 'apilint',
message: 'title MUST follow camelCase',
given: '$.info.title',
givenFormat: 'JSONPATH',
severity: 1,
linterFunction: 'apilintValueCasing',
linterParams: ['camel'],
marker: 'key',
data: {},
};
const versionEquals = {
code: 70202,
source: "apilint",
message: "version must be equal to one of the specified values",
severity: 1,
linterFunction: "apilintEqualsCustom",
linterParams: ["1.0.0", "2.0.0"],
given: "$.info.version",
givenFormat: "JSONPATH",
marker: "value",
data: {},
};
customMetadata.rules = {
asyncapi: {
lint: [camelCaseTitle, versionEquals],
},
};
// Defining custom refractor plugins
const refractorPlugin = () => () => ({
visitor: {
MemberElement(element: MemberElement) {
if (
isStringElement(element.key) &&
element.key.equals("additionalInfo")
) {
element.value = InfoElement.refract(element.value);
}
},
},
});
// Initializing ApiDOM Language Service with custom configuration
const refLinksProvider = new RefLinksProvider();
const customContext = {
metadata: customMetadata,
linksProviders: [refLinksProvider],
refractorPlugins: {
'asyncapi-2': [refractorPlugin()],
}
};
const languageService = getLanguageService(customContext);Validation
The ApiDOM Language Service can validate the document according to the rules of the given specification and provide a list of detected errors and warnings.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
info:
title: Example API
components:
schemas:
Order:
type: object
properties:
id:
type: integer
format: int64
example: 1
status:
type: string
enum: placed
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const diagnostics = await languageService.doValidation(document);[
{
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 5
}
},
"message": "should always have a 'channels' section",
"severity": 1,
"code": 30102,
"source": "apilint",
"data": {
"quickFix": [
{
"message": "add 'channels' section",
"action": "addChild",
"snippetYaml": "channels: \n \n",
"snippetJson": "\"channels\": {\n \n },\n"
}
]
}
},
{
"range": {
"start": {
"line": 1,
"character": 0
},
"end": {
"line": 1,
"character": 4
}
},
"message": "should always have a 'version'",
"severity": 1,
"code": 70201,
"source": "apilint",
"data": {
"quickFix": [
{
"message": "add 'version' field",
"action": "addChild",
"snippetYaml": "version: \n ",
"snippetJson": "\"version\": \"\",\n "
}
]
}
},
{
"range": {
"start": {
"line": 14,
"character": 16
},
"end": {
"line": 14,
"character": 22
}
},
"message": "enum' value must be an array with unique values",
"severity": 1,
"code": 10009,
"source": "apilint",
"data": {}
}
]Documentation
The ApiDOM Language Service can provide documentation for the symbol at the specified position in the document.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = 'asyncapi: 2.6.0';
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const documentation = await languageService.doHover(document, {
line: 0,
character: 3,
});{
"contents": {
"kind": "markdown",
"value": "***asyncapi***: **asyncApiVersion**\n\n#### [AsyncAPI Version String](https://www.asyncapi.com/docs/reference/specification/v2.6.0#A2SVersionString)\n\nThe version string signifies the version of the AsyncAPI Specification that the document complies to.\nThe format for this string _must_ be `major`.`minor`.`patch`. The `patch` _may_ be suffixed by a hyphen and extra alphanumeric characters.\n\\\n\\\nA `major`.`minor` shall be used to designate the AsyncAPI Specification version, and will be considered compatible with the AsyncAPI Specification specified by that `major`.`minor` version.\nThe patch version will not be considered by tooling, making no distinction between `1.0.0` and `1.0.1`.\n\\\n\\\nIn subsequent versions of the AsyncAPI Specification, care will be given such that increments of the `minor` version should not interfere with operations of tooling developed to a lower minor version. Thus a hypothetical `1.1.0` specification should be usable with tooling designed for `1.0.0`."
},
"range": {
"start": {
"line": 0,
"character": 0
},
"end": {
"line": 0,
"character": 8
}
}
}Definition location
The ApiDOM Language Service can find the location of the definition referenced at the specified position in the document.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { Position } from 'vscode-languageserver-types';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
channels:
petstore.inventory.added:
publish:
message:
name: Inventory
contentType: application/json
payload:
"$ref": "#/components/schemas/Inventory"
components:
schemas:
Inventory:
type: object
additionalProperties:
type: integer
format: int64
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const position = Position.create(8, 22);
const location = await languageService.doProvideDefinition(document, {
textDocument: document,
position,
});{
"uri": "foo://bar/file.yaml",
"range": {
"start": {
"line": 12,
"character": 6
},
"end": {
"line": 16,
"character": 0
}
}
}Definition
The ApiDOM Language Service can retrieve the definition of the reference at the specified position in the document.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { Position } from 'vscode-languageserver-types';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
channels:
petstore.inventory.added:
publish:
message:
name: Inventory
contentType: application/json
payload:
"$ref": "#/components/schemas/Inventory"
components:
schemas:
Inventory:
type: object
additionalProperties:
type: integer
format: int64
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const definition = await languageService.doHover(document, {
line: 8,
character: 22,
});{
"contents": {
"kind": "markdown",
"value": "***$ref***: **string**\n\n\n\n\n\n```yaml\ntype: object\nadditionalProperties:\n type: integer\n format: int64\n\n\n```"
},
"range": {
"start": {
"line": 8,
"character": 18
},
"end": {
"line": 8,
"character": 50
}
}
}Completion
The ApiDOM Language Service can provide context-aware completion suggestions for the specified position in the document.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { Position } from 'vscode-languageserver-types';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
info:
title: Example API
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const position = Position.create(3, 2);
const completion = await languageService.doCompletion(document, {
textDocument: document,
position,
});{
"items": [
{
"label": "version",
"insertText": "version: '$1'",
"kind": 14,
"insertTextFormat": 2,
"documentation": {
"kind": "markdown",
"value": "**REQUIRED.** Provides the version of the application API (not to be confused with the specification version)."
}
},
{
"label": "description",
"insertText": "description: $1",
"kind": 14,
"insertTextFormat": 2,
"documentation": {
"kind": "markdown",
"value": "A short description of the application. [CommonMark syntax](https://spec.commonmark.org/) can be used for rich text representation."
}
},
{
"label": "termsOfService",
"insertText": "termsOfService: $1",
"kind": 14,
"insertTextFormat": 2,
"documentation": {
"kind": "markdown",
"value": "A URL to the Terms of Service for the API. This MUST be in the form of an absolute URL."
}
},
{
"label": "contact",
"insertText": "contact: \n $1",
"kind": 14,
"insertTextFormat": 2,
"documentation": {
"kind": "markdown",
"value": "[Contact Object](https://www.asyncapi.com/docs/reference/specification/v2.6.0#contactObject)\n\\\n\\\nContact information for the exposed API."
}
},
{
"label": "license",
"insertText": "license: \n $1",
"kind": 14,
"insertTextFormat": 2,
"documentation": {
"kind": "markdown",
"value": "[License Object](https://www.asyncapi.com/docs/reference/specification/v2.6.0#licenseObject)\n\\\n\\\nLicense information for the exposed API."
}
}
],
"isIncomplete": false
}Reference completion
The ApiDOM Language Service can suggest possible references that can be inserted at the specified position in the document.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { Position } from 'vscode-languageserver-types';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
channels:
petstore.inventory.added:
publish:
message:
name: Inventory
contentType: application/json
payload:
"$ref": "#/components/schemas/Inventory"
components:
schemas:
Inventory:
type: object
additionalProperties:
type: integer
format: int64
Category:
type: object
properties:
id:
type: integer
format: int64
example: 1
name:
type: string
example: Dogs
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const position = Position.create(8, 22);
const completion = await languageService.doCompletion(document, {
textDocument: document,
position,
});{
"items": [
{
"label": "#/components/schemas/Category",
"insertText": "'#/components/schemas/Category$1'",
"kind": 18,
"documentation": "type: object\n properties:\n id:\n type: integer\n format: int64\n example: 1\n name:\n type: string\n example: Dogs\n",
"insertTextFormat": 2,
"sortText": "a",
"filterText": "\"#/components/schemas/Inventory\"",
"textEdit": {
"range": {
"start": {
"line": 8,
"character": 18
},
"end": {
"line": 8,
"character": 50
}
},
"newText": "'#/components/schemas/Category$1'"
}
},
{
"label": "#/components/schemas/Inventory/additionalProperties",
"insertText": "'#/components/schemas/Inventory/additionalProperties$1'",
"kind": 18,
"documentation": "type: integer\n format: int64",
"insertTextFormat": 2,
"sortText": "c",
"filterText": "\"#/components/schemas/Inventory\"",
"textEdit": {
"range": {
"start": {
"line": 8,
"character": 18
},
"end": {
"line": 8,
"character": 50
}
},
"newText": "'#/components/schemas/Inventory/additionalProperties$1'"
}
},
{
"label": "#/components/schemas/Category/properties/name",
"insertText": "'#/components/schemas/Category/properties/name$1'",
"kind": 18,
"documentation": "type: string\n example: Dogs\n",
"insertTextFormat": 2,
"sortText": "d",
"filterText": "\"#/components/schemas/Inventory\"",
"textEdit": {
"range": {
"start": {
"line": 8,
"character": 18
},
"end": {
"line": 8,
"character": 50
}
},
"newText": "'#/components/schemas/Category/properties/name$1'"
}
},
{
"label": "#/components/schemas/Category/properties/id",
"insertText": "'#/components/schemas/Category/properties/id$1'",
"kind": 18,
"documentation": "type: integer\n format: int64\n example: 1",
"insertTextFormat": 2,
"sortText": "e",
"filterText": "\"#/components/schemas/Inventory\"",
"textEdit": {
"range": {
"start": {
"line": 8,
"character": 18
},
"end": {
"line": 8,
"character": 50
}
},
"newText": "'#/components/schemas/Category/properties/id$1'"
}
}
],
"isIncomplete": false
}Semantic Tokens
The ApiDOM Language Service can provide semantic tokens for the document, enabling context-aware syntax highlighting.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
components:
schemas:
Inventory:
type: object
additionalProperties:
type: integer
format: int64
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const tokens = await languageService.computeSemanticTokens(document);{
"data": [
0, 0, 8, 14, 0, 1, 0, 10, 16, 0, 1, 2,
7, 18, 0, 1, 4, 9, 23, 0, 1, 6, 4, 3,
64, 0, 6, 6, 0, 64, 1, 6, 20, 23, 0, 1,
8, 4, 3, 64, 0, 6, 7, 0, 64, 1, 8, 6,
3, 64, 0, 8, 5, 0, 64
]
}Dereferencing
The ApiDOM Language Service can dereference the document, replacing the references with their actual definitions.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { Position } from 'vscode-languageserver-types';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
channels:
petstore.inventory.added:
publish:
message:
name: Inventory
contentType: application/json
payload:
"$ref": "#/components/schemas/Inventory"
components:
schemas:
Inventory:
type: object
additionalProperties:
type: integer
format: int64
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const dereferenced = await languageService.doDeref(document);"asyncapi": "2.6.0"
"channels":
"petstore.inventory.added":
"publish":
"message":
"name": "Inventory"
"contentType": "application/json"
"payload":
"type": "object"
"additionalProperties":
"type": "integer"
"format": "int64"
"components":
"schemas":
"Inventory":
"type": "object"
"additionalProperties":
"type": "integer"
"format": "int64"JSON Pointer Position
The ApiDOM Language Service can find a position in the document corresponding to the given JSON Pointer.
import { TextDocument } from 'vscode-languageserver-textdocument';
import { getLanguageService } from '@swagger-api/apidom-ls';
const apidomContext = {};
const languageService = getLanguageService(apidomContext);
const asyncapiDoc = `asyncapi: 2.6.0
components:
schemas:
Inventory:
type: object
additionalProperties:
type: integer
format: int64
`;
const document = TextDocument.create('foo://bar/file.yaml', 'apidom', 0, asyncapiDoc);
const jsonPointer = '/components/schemas/Inventory';
const position = await languageService.getJsonPointerPosition(document, jsonPointer);{
"line": 4,
"character": 6
}