diff --git a/README.md b/README.md index 564da8e6..abded8b2 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Ace linters supports the following languages by default with webworkers approach - Typescript, Javascript, JSX, TSX *powered by* [Typescript](https://github.com/Microsoft/TypeScript) - Lua *powered by* [luaparse](https://github.com/fstirlitz/luaparse) - YAML *powered by* [Yaml Language Server](https://github.com/redhat-developer/yaml-language-server) +- XML *powered by* [XML-Tools](https://github.com/SAP/xml-tools) For WebSockets you could connect any of your Language Server folowing LSP diff --git a/package-lock.json b/package-lock.json index 613e1472..0dfc9cd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -871,6 +871,70 @@ } } }, + "node_modules/@xml-tools/ast": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@xml-tools/ast/-/ast-5.0.5.tgz", + "integrity": "sha512-avvzTOvGplCx9JSKdsTe3vK+ACvsHy2HxVfkcfIqPzu+kF5CT4rw5aUVzs0tJF4cnDyMRVkSyVxR07X0Px8gPA==", + "dependencies": { + "@xml-tools/common": "^0.1.6", + "@xml-tools/parser": "^1.0.11", + "lodash": "4.17.21" + } + }, + "node_modules/@xml-tools/common": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@xml-tools/common/-/common-0.1.6.tgz", + "integrity": "sha512-7aVZeEYccs1KI/Asd6KKnrB4dTAWXTkjRMjG40ApGEUp5NpfQIvWLEBvMv85Koj2lbSpagcAERwDy9qMsfWGdA==", + "dependencies": { + "lodash": "4.17.21" + } + }, + "node_modules/@xml-tools/constraints": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@xml-tools/constraints/-/constraints-1.1.1.tgz", + "integrity": "sha512-c9K/Ozmem2zbLta7HOjJpXszZA/UQkm3pKT3AAa+tKdnsIomPwcRXkltdd+UtdXcOTbqsuTV0fnSkLBgjlnxbQ==", + "dependencies": { + "@xml-tools/validation": "^1.0.16", + "lodash": "4.17.21" + } + }, + "node_modules/@xml-tools/content-assist": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@xml-tools/content-assist/-/content-assist-3.1.11.tgz", + "integrity": "sha512-ExVgzRLutvBEMi0JQ8vi4ccao0lrq8DTsWKeAEH6/Zy2Wfp+XAR4ERNpFK7yp+QHQkWROr/XSNVarcTuopE+lg==", + "dependencies": { + "@xml-tools/common": "^0.1.6", + "@xml-tools/parser": "^1.0.11", + "lodash": "4.17.21" + } + }, + "node_modules/@xml-tools/parser": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", + "integrity": "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==", + "dependencies": { + "chevrotain": "7.1.1" + } + }, + "node_modules/@xml-tools/simple-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@xml-tools/simple-schema/-/simple-schema-3.0.5.tgz", + "integrity": "sha512-8qrm23eGAFtvNWmJu46lttae+X8eOEPMXryf4JH6NBpFl1pphkNXqr7bAKnOjELmLPXHStoeKZO2vptyn4cPPA==", + "dependencies": { + "@xml-tools/ast": "^5.0.5", + "@xml-tools/content-assist": "^3.1.11", + "lodash": "4.17.21" + } + }, + "node_modules/@xml-tools/validation": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@xml-tools/validation/-/validation-1.0.16.tgz", + "integrity": "sha512-w/+kUYcxKXfQz8TQ3qAAuLl9N2WZ0HGiwI8nVuIe29dAZmyeXx5ZpODAW7yJoarH0/wZ+rhbc3XxbRqIPcSofA==", + "dependencies": { + "@xml-tools/ast": "^5.0.5", + "lodash": "4.17.21" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -1249,6 +1313,14 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chevrotain": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", + "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", + "dependencies": { + "regexp-to-ast": "0.5.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2668,6 +2740,11 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3414,6 +3491,11 @@ "node": ">= 0.10" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "node_modules/request-light": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", @@ -4827,6 +4909,10 @@ "packages/ace-linters": { "version": "0.2.4", "dependencies": { + "@xml-tools/ast": "^5.0.5", + "@xml-tools/constraints": "^1.1.1", + "@xml-tools/parser": "^1.0.11", + "@xml-tools/simple-schema": "^3.0.5", "htmlhint": "^1.1.4", "luaparse": "latest", "showdown": "latest", @@ -4835,6 +4921,7 @@ "vscode-json-languageservice": "4.2.1", "vscode-languageserver-protocol": "^3.17.2", "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.2", "vscode-ws-jsonrpc": "^2.0.1" } }, @@ -5472,6 +5559,70 @@ "dev": true, "requires": {} }, + "@xml-tools/ast": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@xml-tools/ast/-/ast-5.0.5.tgz", + "integrity": "sha512-avvzTOvGplCx9JSKdsTe3vK+ACvsHy2HxVfkcfIqPzu+kF5CT4rw5aUVzs0tJF4cnDyMRVkSyVxR07X0Px8gPA==", + "requires": { + "@xml-tools/common": "^0.1.6", + "@xml-tools/parser": "^1.0.11", + "lodash": "4.17.21" + } + }, + "@xml-tools/common": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@xml-tools/common/-/common-0.1.6.tgz", + "integrity": "sha512-7aVZeEYccs1KI/Asd6KKnrB4dTAWXTkjRMjG40ApGEUp5NpfQIvWLEBvMv85Koj2lbSpagcAERwDy9qMsfWGdA==", + "requires": { + "lodash": "4.17.21" + } + }, + "@xml-tools/constraints": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@xml-tools/constraints/-/constraints-1.1.1.tgz", + "integrity": "sha512-c9K/Ozmem2zbLta7HOjJpXszZA/UQkm3pKT3AAa+tKdnsIomPwcRXkltdd+UtdXcOTbqsuTV0fnSkLBgjlnxbQ==", + "requires": { + "@xml-tools/validation": "^1.0.16", + "lodash": "4.17.21" + } + }, + "@xml-tools/content-assist": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/@xml-tools/content-assist/-/content-assist-3.1.11.tgz", + "integrity": "sha512-ExVgzRLutvBEMi0JQ8vi4ccao0lrq8DTsWKeAEH6/Zy2Wfp+XAR4ERNpFK7yp+QHQkWROr/XSNVarcTuopE+lg==", + "requires": { + "@xml-tools/common": "^0.1.6", + "@xml-tools/parser": "^1.0.11", + "lodash": "4.17.21" + } + }, + "@xml-tools/parser": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@xml-tools/parser/-/parser-1.0.11.tgz", + "integrity": "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==", + "requires": { + "chevrotain": "7.1.1" + } + }, + "@xml-tools/simple-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@xml-tools/simple-schema/-/simple-schema-3.0.5.tgz", + "integrity": "sha512-8qrm23eGAFtvNWmJu46lttae+X8eOEPMXryf4JH6NBpFl1pphkNXqr7bAKnOjELmLPXHStoeKZO2vptyn4cPPA==", + "requires": { + "@xml-tools/ast": "^5.0.5", + "@xml-tools/content-assist": "^3.1.11", + "lodash": "4.17.21" + } + }, + "@xml-tools/validation": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@xml-tools/validation/-/validation-1.0.16.tgz", + "integrity": "sha512-w/+kUYcxKXfQz8TQ3qAAuLl9N2WZ0HGiwI8nVuIe29dAZmyeXx5ZpODAW7yJoarH0/wZ+rhbc3XxbRqIPcSofA==", + "requires": { + "@xml-tools/ast": "^5.0.5", + "lodash": "4.17.21" + } + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -5501,6 +5652,10 @@ "ace-linters": { "version": "file:packages/ace-linters", "requires": { + "@xml-tools/ast": "^5.0.5", + "@xml-tools/constraints": "^1.1.1", + "@xml-tools/parser": "^1.0.11", + "@xml-tools/simple-schema": "^3.0.5", "htmlhint": "^1.1.4", "luaparse": "latest", "showdown": "latest", @@ -5509,6 +5664,7 @@ "vscode-json-languageservice": "4.2.1", "vscode-languageserver-protocol": "^3.17.2", "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.2", "vscode-ws-jsonrpc": "^2.0.1" } }, @@ -5759,6 +5915,14 @@ "supports-color": "^7.1.0" } }, + "chevrotain": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-7.1.1.tgz", + "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", + "requires": { + "regexp-to-ast": "0.5.0" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -6812,6 +6976,11 @@ "p-locate": "^4.1.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7332,6 +7501,11 @@ "resolve": "^1.9.0" } }, + "regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "request-light": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.8.tgz", diff --git a/packages/ace-linters/README.md b/packages/ace-linters/README.md index 9ae6b52c..2e2f6a4f 100644 --- a/packages/ace-linters/README.md +++ b/packages/ace-linters/README.md @@ -25,6 +25,7 @@ Ace linters supports the following languages by default with webworkers approach - Typescript, Javascript, JSX, TSX *powered by* [Typescript](https://github.com/Microsoft/TypeScript) - Lua *powered by* [luaparse](https://github.com/fstirlitz/luaparse) - YAML *powered by* [Yaml Language Server](https://github.com/redhat-developer/yaml-language-server) +- XML *powered by* [XML-Tools](https://github.com/SAP/xml-tools) For WebSockets you could connect any of your Language Server folowing LSP @@ -45,20 +46,30 @@ editor and an instance of LanguageProvider. ```javascript import * as ace from "ace-code"; -import {Mode as JsonMode} from "ace-code/src/mode/json"; +import {Mode as TypescriptMode} from "ace-code/src/mode/typescript"; import {registerStyles, LanguageProvider} from "ace-linters"; +import {ScriptTarget, JsxEmit} from "ace-linters/type-converters/typescript-converters"; // Create a web worker let worker = new Worker(new URL('./webworker.js', import.meta.url)); // Create an Ace editor let editor = ace.edit("container", { - mode: new JsonMode() // Set the mode of the editor to JSON + mode: new TypescriptMode() // Set the mode of the editor to Typescript }); // Create a language provider for web worker let languageProvider = LanguageProvider.for(worker); +// Set global options for the Typescript service +languageProvider.setGlobalOptions("typescript", { + compilerOptions: { + allowJs: true, + target: ScriptTarget.ESNext, + jsx: JsxEmit.Preserve + } +}); + // Register the editor with the language provider languageProvider.registerEditor(editor); diff --git a/packages/ace-linters/package.json b/packages/ace-linters/package.json index 9d099af9..748c46cf 100644 --- a/packages/ace-linters/package.json +++ b/packages/ace-linters/package.json @@ -14,10 +14,16 @@ "vscode-json-languageservice": "4.2.1", "vscode-languageserver-protocol": "^3.17.2", "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.2", "showdown": "latest", "luaparse": "latest", "vscode-ws-jsonrpc": "^2.0.1", - "htmlhint": "^1.1.4" + "htmlhint": "^1.1.4", + "@xml-tools/constraints": "^1.1.1", + "@xml-tools/parser": "^1.0.11", + "@xml-tools/ast": "^5.0.5", + "@xml-tools/simple-schema": "^3.0.5" + }, "main": "build/ace-linters.js", "types": "types/index.d.ts", diff --git a/packages/ace-linters/services/json/json-service.ts b/packages/ace-linters/services/json/json-service.ts index f1039cb2..87607a03 100644 --- a/packages/ace-linters/services/json/json-service.ts +++ b/packages/ace-linters/services/json/json-service.ts @@ -28,7 +28,7 @@ export class JsonService extends BaseService implements AceL } private $getJsonSchemaUri(sessionID): string | undefined { - return this.getOption(sessionID, "jsonSchemaUri"); + return this.getOption(sessionID, "schemaUri"); } addDocument(document: TextDocumentItem) { @@ -37,7 +37,7 @@ export class JsonService extends BaseService implements AceL } private $configureService(sessionID: string) { - let schemas = this.getOption(sessionID, "jsonSchemas"); + let schemas = this.getOption(sessionID, "schemas"); schemas?.forEach((el) => { if (el.uri === this.$getJsonSchemaUri(sessionID)) { el.fileMatch ??= []; @@ -59,7 +59,7 @@ export class JsonService extends BaseService implements AceL removeDocument(document: TextDocumentIdentifier) { super.removeDocument(document); - let schemas = this.getOption(document.uri, "jsonSchemas"); + let schemas = this.getOption(document.uri, "schemas"); schemas?.forEach((el) => { if (el.uri === this.$getJsonSchemaUri(document.uri)) { el.fileMatch = el.fileMatch?.filter((pattern) => pattern != document.uri); diff --git a/packages/ace-linters/services/xml/xml-converters.ts b/packages/ace-linters/services/xml/xml-converters.ts new file mode 100644 index 00000000..47e45179 --- /dev/null +++ b/packages/ace-linters/services/xml/xml-converters.ts @@ -0,0 +1,50 @@ +import {Diagnostic, DiagnosticSeverity, Range} from "vscode-languageserver-protocol"; +import {TextDocument} from "vscode-languageserver-textdocument"; + +export function lexingErrorToDiagnostic(document: TextDocument, error): Diagnostic { + return { + message: error.message, + range: Range.create( + document.positionAt(error.offset), + document.positionAt(error.offset + error.length) + ), + severity: DiagnosticSeverity.Error, + }; +} + +export function parsingErrorToDiagnostic(document: TextDocument, error): Diagnostic { + return { + message: error.message, + range: { + start: document.positionAt(error.token.startOffset), + end: document.positionAt( + error.token.endOffset ? error.token.endOffset : 0 + ), + }, + severity: DiagnosticSeverity.Error, + }; +} + +export function issueToDiagnostic(document: TextDocument, issue): Diagnostic { + return { + message: issue.msg, + range: { + start: document.positionAt(issue.position.startOffset), + // Chevrotain Token positions are non-inclusive for endOffsets + end: document.positionAt(issue.position.endOffset + 1), + }, + severity: toDiagnosticSeverity(issue.severity), + }; +} + +function toDiagnosticSeverity(issueSeverity: string): DiagnosticSeverity { + switch (issueSeverity) { + case "error": + return DiagnosticSeverity.Error; + case "warning": + return DiagnosticSeverity.Warning; + case "info": + default: + return DiagnosticSeverity.Information; + } +} diff --git a/packages/ace-linters/services/xml/xml-service.ts b/packages/ace-linters/services/xml/xml-service.ts new file mode 100644 index 00000000..0e574396 --- /dev/null +++ b/packages/ace-linters/services/xml/xml-service.ts @@ -0,0 +1,88 @@ +import {BaseService} from "../base-service"; +import {AceLinters} from "../../types"; +import * as lsp from "vscode-languageserver-protocol"; +import {DocumentCstNode, parse} from "@xml-tools/parser"; +import {buildAst} from "@xml-tools/ast"; +import {checkConstraints} from "@xml-tools/constraints"; +import {getSchemaValidators} from "@xml-tools/simple-schema"; +import {validate, ValidationIssue} from "@xml-tools/validation"; + +import { + issueToDiagnostic, + lexingErrorToDiagnostic, + parsingErrorToDiagnostic +} from "./xml-converters"; +import {TextDocumentItem} from "vscode-languageserver-protocol"; +import XmlServiceOptions = AceLinters.XmlServiceOptions; + + +export class XmlService extends BaseService implements AceLinters.LanguageService { + $service; + schemas: { [schemaUri: string]: string } = {}; + + constructor(mode: string) { + super(mode); + } + + addDocument(document: TextDocumentItem) { + super.addDocument(document); + this.$configureService(document.uri); + } + + private $getXmlSchemaUri(sessionID): string | undefined { + return this.getOption(sessionID, "schemaUri"); + } + + private $configureService(sessionID: string) { + let schemas = this.getOption(sessionID, "schemas"); + schemas?.forEach((el) => { + if (el.uri === this.$getXmlSchemaUri(sessionID)) { + el.fileMatch ??= []; + el.fileMatch.push(sessionID); + } + let schema = el.schema ?? this.schemas[el.uri]; + if (schema) + this.schemas[el.uri] = schema; + el.schema = undefined; + }); + } + + $getSchema(sessionId) { + let schemaId = this.$getXmlSchemaUri(sessionId); + if (schemaId && this.schemas[schemaId]) { + return JSON.parse(this.schemas[schemaId]) + } + } + + async doValidation(document: lsp.TextDocumentIdentifier): Promise { + let fullDocument = this.getDocument(document.uri); + if (!fullDocument) + return []; + + const {cst, tokenVector, lexErrors, parseErrors} = parse( + fullDocument.getText() + ); + const xmlDoc = buildAst(cst as DocumentCstNode, tokenVector); + const constraintsIssues = checkConstraints(xmlDoc as any); + + let schema = this.$getSchema(document.uri); + let schemaIssues: ValidationIssue[] = []; + if (schema) { + const schemaValidators = getSchemaValidators(schema); + schemaIssues = validate({ + doc: xmlDoc, + validators: { + attribute: [schemaValidators.attribute], + element: [schemaValidators.element], + }, + }); + } + + return [ + ...lexErrors.map((_) => lexingErrorToDiagnostic(fullDocument, _)), + ...parseErrors.map((_) => parsingErrorToDiagnostic(fullDocument, _)), + ...constraintsIssues.map((_) => issueToDiagnostic(fullDocument, _)), + ...schemaIssues.map((_) => issueToDiagnostic(fullDocument, _)) + ]; + } +} diff --git a/packages/ace-linters/services/yaml/yaml-service.ts b/packages/ace-linters/services/yaml/yaml-service.ts index b2cae0c0..efea665f 100644 --- a/packages/ace-linters/services/yaml/yaml-service.ts +++ b/packages/ace-linters/services/yaml/yaml-service.ts @@ -23,7 +23,7 @@ export class YamlService extends BaseService implements AceL } private $getYamlSchemaUri(sessionID): string | undefined { - return this.getOption(sessionID, "yamlSchemaUri"); + return this.getOption(sessionID, "schemaUri"); } addDocument(document: TextDocumentItem) { @@ -32,7 +32,7 @@ export class YamlService extends BaseService implements AceL } private $configureService(sessionID: string) { - let schemas = this.getOption(sessionID, "yamlSchemas"); + let schemas = this.getOption(sessionID, "schemas"); schemas?.forEach((el) => { if (el.uri === this.$getYamlSchemaUri(sessionID)) { el.fileMatch ??= []; @@ -57,7 +57,7 @@ export class YamlService extends BaseService implements AceL removeDocument(document: TextDocumentIdentifier) { super.removeDocument(document); - let schemas = this.getOption(document.uri, "yamlSchemas"); + let schemas = this.getOption(document.uri, "schemas"); schemas?.forEach((el) => { if (el.uri === this.$getYamlSchemaUri(document.uri)) { el.fileMatch = el.fileMatch?.filter((pattern) => pattern != document.uri); diff --git a/packages/ace-linters/types/language-service.d.ts b/packages/ace-linters/types/language-service.d.ts index 186f42e6..c0b32715 100644 --- a/packages/ace-linters/types/language-service.d.ts +++ b/packages/ace-linters/types/language-service.d.ts @@ -59,23 +59,23 @@ export declare namespace AceLinters { } export interface JsonServiceOptions { - jsonSchemas?: { + schemas?: { uri: string, fileMatch?: string[], schema?: string, }[], - jsonSchemaUri?: string, + schemaUri?: string, allowComments?: boolean, trailingCommas?: boolean } export interface YamlServiceOptions { - yamlSchemas?: { + schemas?: { uri: string, fileMatch?: string[], schema?: string, }[], - yamlSchemaUri?: string, + schemaUri?: string, } export interface TsServiceOptions { @@ -86,6 +86,15 @@ export declare namespace AceLinters { validationOptions?: { [option: string]: boolean } } + export interface XmlServiceOptions { + schemas?: { + uri: string, + fileMatch?: string[], + schema?: string, + }[], + schemaUri?: string, + } + export interface PhpServiceOptions { inline: boolean } @@ -96,6 +105,7 @@ export declare namespace AceLinters { typescript: TsServiceOptions, html: HtmlServiceOptions, yaml: YamlServiceOptions, - php: PhpServiceOptions + php: PhpServiceOptions, + xml: XmlServiceOptions } } diff --git a/packages/ace-linters/webpack.config.js b/packages/ace-linters/webpack.config.js index 1ce24493..60ce15b1 100644 --- a/packages/ace-linters/webpack.config.js +++ b/packages/ace-linters/webpack.config.js @@ -23,7 +23,8 @@ module.exports = (env, argv) => { "json-service": './services/json/json-service.ts', "lua-service": './services/lua/lua-service.ts', "typescript-service": './services/typescript/typescript-service.ts', - "yaml-service": './services/yaml/yaml-service.ts' + "yaml-service": './services/yaml/yaml-service.ts', + "xml-service": './services/xml/xml-service.ts' }, module: { rules: [ diff --git a/packages/demo/enable-threads.js b/packages/demo/enable-threads.js new file mode 100644 index 00000000..14f09ddc --- /dev/null +++ b/packages/demo/enable-threads.js @@ -0,0 +1,69 @@ +// NOTE: This file creates a service worker that cross-origin-isolates the page (read more here: https://web.dev/coop-coep/) which allows us to use wasm threads. +// Normally you would set the COOP and COEP headers on the server to do this, but Github Pages doesn't allow this, so this is a hack to do that. + +/* Edited version of: coi-serviceworker v0.1.6 - Guido Zuidhof, licensed under MIT */ +// From here: https://github.com/gzuidhof/coi-serviceworker +if(typeof window === 'undefined') { + self.addEventListener("install", () => self.skipWaiting()); + self.addEventListener("activate", e => e.waitUntil(self.clients.claim())); + + async function handleFetch(request) { + if(request.cache === "only-if-cached" && request.mode !== "same-origin") { + return; + } + + if(request.mode === "no-cors") { // We need to set `credentials` to "omit" for no-cors requests, per this comment: https://bugs.chromium.org/p/chromium/issues/detail?id=1309901#c7 + request = new Request(request.url, { + cache: request.cache, + credentials: "omit", + headers: request.headers, + integrity: request.integrity, + destination: request.destination, + keepalive: request.keepalive, + method: request.method, + mode: request.mode, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + signal: request.signal, + }); + } + + let r = await fetch(request).catch(e => console.error(e)); + + if(r.status === 0) { + return r; + } + + const headers = new Headers(r.headers); + headers.set("Cross-Origin-Embedder-Policy", "credentialless"); // or: require-corp + headers.set("Cross-Origin-Opener-Policy", "same-origin"); + + return new Response(r.body, { status: r.status, statusText: r.statusText, headers }); + } + + self.addEventListener("fetch", function(e) { + e.respondWith(handleFetch(e.request)); // respondWith must be executed synchonously (but can be passed a Promise) + }); + +} else { + (async function() { + if(window.crossOriginIsolated !== false) return; + + let registration = await navigator.serviceWorker.register(window.document.currentScript.src).catch(e => console.error("COOP/COEP Service Worker failed to register:", e)); + if(registration) { + console.log("COOP/COEP Service Worker registered", registration.scope); + + registration.addEventListener("updatefound", () => { + console.log("Reloading page to make use of updated COOP/COEP Service Worker."); + window.location.reload(); + }); + + // If the registration is active, but it's not controlling the page + if(registration.active && !navigator.serviceWorker.controller) { + console.log("Reloading page to make use of COOP/COEP Service Worker."); + window.location.reload(); + } + } + })(); +} diff --git a/packages/demo/rust_analyzer.html b/packages/demo/rust_analyzer.html index 6209dda9..aeb52306 100644 --- a/packages/demo/rust_analyzer.html +++ b/packages/demo/rust_analyzer.html @@ -1,6 +1,9 @@ + + + Ace linters Rust-Analyzer demo diff --git a/packages/demo/webworker-lsp/demo.ts b/packages/demo/webworker-lsp/demo.ts index d08c2de7..8a87be22 100644 --- a/packages/demo/webworker-lsp/demo.ts +++ b/packages/demo/webworker-lsp/demo.ts @@ -16,6 +16,7 @@ import {Mode as TSXMode} from "ace-code/src/mode/tsx"; import {Mode as LuaMode} from "ace-code/src/mode/lua"; import {Mode as YamlMode} from "ace-code/src/mode/yaml"; import {Mode as PhpMode} from "ace-code/src/mode/php"; +import {Mode as XmlMode} from "ace-code/src/mode/xml"; import {cssContent} from "./docs-example/css-example"; import {lessContent} from "./docs-example/less-example"; @@ -34,10 +35,11 @@ import {luaContent} from "./docs-example/lua-example"; import {createEditorWithLSP} from "../utils"; import {yamlContent, yamlSchema} from "./docs-example/yaml-example"; import {phpContent} from "./docs-example/php-example"; +import {xmlContent, xmlSchema} from "./docs-example/xml-example"; let modes = [ - {name: "json", mode: JsonMode, content: jsonContent, options: {jsonSchemaUri: "common-form.schema.json"}}, - {name: "json5", mode: Json5Mode, content: json5Content, options: {jsonSchemaUri: "json5Schema"}}, + {name: "json", mode: JsonMode, content: jsonContent, options: {schemaUri: "common-form.schema.json"}}, + {name: "json5", mode: Json5Mode, content: json5Content, options: {schemaUri: "json5Schema"}}, {name: "html", mode: HTMLMode, content: htmlContent}, {name: "css", mode: CSSMode, content: cssContent}, {name: "less", mode: LessMode, content: lessContent}, @@ -48,7 +50,8 @@ let modes = [ {name: "tsx", mode: TSXMode, content: tsxContent}, {name: "jsx", mode: JavascriptMode, content: jsxContent, options: {jsx: true}}, //TODO: {name: "lua", mode: LuaMode, content: luaContent}, - {name: "yaml", mode: YamlMode, content: yamlContent, options:{yamlSchemaUri: "yamlSchema.json"}}, + {name: "yaml", mode: YamlMode, content: yamlContent, options: {schemaUri: "yamlSchema.json"}}, + {name: "xml", mode: XmlMode, content: xmlContent, options: {schemaUri: "xmlSchema.json"}}, {name: "php", mode: PhpMode, content: phpContent} ]; let worker = new Worker(new URL('./webworker.ts', import.meta.url)); @@ -63,7 +66,7 @@ languageProvider.setGlobalOptions("typescript", { }); languageProvider.setGlobalOptions("json", { - jsonSchemas: [ + schemas: [ { uri: "common-form.schema.json", schema: jsonSchema2 @@ -72,7 +75,7 @@ languageProvider.setGlobalOptions("json", { }); languageProvider.setGlobalOptions("json5", { - jsonSchemas: [ + schemas: [ { uri: "json5Schema", schema: json5Schema @@ -81,7 +84,7 @@ languageProvider.setGlobalOptions("json5", { }); languageProvider.setGlobalOptions("yaml", { - yamlSchemas: [ + schemas: [ { uri: "yamlSchema.json", schema: yamlSchema @@ -89,13 +92,22 @@ languageProvider.setGlobalOptions("yaml", { ] }); +languageProvider.setGlobalOptions("xml", { + schemas: [ + { + uri: "xmlSchema.json", + schema: xmlSchema + } + ] +}); + let i = 0; for (let mode of modes) { createEditorWithLSP(mode, i, languageProvider); i++; } languageProvider.setGlobalOptions("json", { - jsonSchemas: [{ + schemas: [{ uri: "colors.schema.json", schema: jsonSchema },] diff --git a/packages/demo/webworker-lsp/docs-example/xml-example.js b/packages/demo/webworker-lsp/docs-example/xml-example.js new file mode 100644 index 00000000..f44ff042 --- /dev/null +++ b/packages/demo/webworker-lsp/docs-example/xml-example.js @@ -0,0 +1,38 @@ +export var xmlContent = ` + + + Daenerys Targaryen + + +`; + +export var xmlSchema = `{ + "name": "people", + "required": true, + "cardinality": "single", + "attributes": {}, + "elements": { + "person": { + "name": "person", + "required": false, + "cardinality": "many", + "attributes": { + "eyeColor": { + "key": "eyeColor", + "required": false, + "value": ["grey", "blue", "green", "red"] + } + }, + "elements": { + "name": { + "cardinality": "single", + "required": true, + "name": "name", + "attributes": {}, + "elements": {} + } + } + } + } +} +`; diff --git a/packages/demo/webworker-lsp/webworker.ts b/packages/demo/webworker-lsp/webworker.ts index e966b08f..da24fd24 100644 --- a/packages/demo/webworker-lsp/webworker.ts +++ b/packages/demo/webworker-lsp/webworker.ts @@ -7,7 +7,6 @@ manager.registerService("html", { className: "HtmlService", modes: "html" }); - manager.registerService("css", { module: () => import("ace-linters/services/css/css-service"), className: "CssService", @@ -48,6 +47,11 @@ manager.registerService("yaml", { className: "YamlService", modes: "yaml", }); +manager.registerService("xml", { + module: () => import("ace-linters/services/xml/xml-service"), + className: "XmlService", + modes: "xml", +}); manager.registerService("php", { module: () => import("ace-linters/services/php/php-service"), className: "PhpService", diff --git a/webpack.config.js b/webpack.config.js index 7315b6b2..17959d9e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -81,6 +81,9 @@ module.exports = (env, argv) => { }, { from: "packages/demo/rust_analyzer.html", to: "." + }, { + from: "packages/demo/enable-threads.js", + to: "." } ] })