From 6f297863ae4124792f45d318ff174b7a21d81817 Mon Sep 17 00:00:00 2001 From: Rikki Schulte Date: Sat, 19 Aug 2023 15:29:49 +0200 Subject: [PATCH] .js file imports and schema loading as statefield --- dev/index.html | 13 ++ dev/index.ts | 72 +++++++++-- package.json | 6 + pnpm-lock.yaml | 121 +++++++++++++++--- src/__tests__/__helpers__/completion.ts | 10 +- src/__tests__/json-completion.spec.ts | 2 +- src/__tests__/json-hover.spec.ts | 32 +++-- src/__tests__/json-validation.spec.ts | 10 +- src/bundled.ts | 16 ++- src/index.ts | 16 ++- src/json-completion.ts | 40 +++--- src/json-hover.ts | 32 +++-- src/json-validation.ts | 43 ++++--- src/json5-bundled.ts | 13 +- src/json5-hover.ts | 6 +- src/json5-validation.ts | 19 +-- src/json5.ts | 10 +- src/state.ts | 31 +++++ src/utils/__tests__/jsonPointers.spec.ts | 7 +- src/utils/__tests__/parseJSONDocument.spec.ts | 4 +- src/utils/jsonPointers.ts | 6 +- src/utils/node.ts | 4 +- src/utils/parseJSON5Document.ts | 2 +- src/utils/parseJSONDocument.ts | 2 +- src/utils/schema-lib/README.md | 2 +- src/utils/schema-lib/getSchema.ts | 2 +- src/utils/schema-lib/step.ts | 9 +- tsconfig.json | 7 +- 28 files changed, 387 insertions(+), 150 deletions(-) create mode 100644 src/state.ts diff --git a/dev/index.html b/dev/index.html index c11b971..eecd032 100644 --- a/dev/index.html +++ b/dev/index.html @@ -88,6 +88,19 @@

codemirror-json-schema demo

+
+ +

package.json demo

diff --git a/dev/index.ts b/dev/index.ts index 39d0b0c..8c8b8d7 100644 --- a/dev/index.ts +++ b/dev/index.ts @@ -1,11 +1,31 @@ -import { EditorState } from "@codemirror/state"; -import { gutter, EditorView, lineNumbers } from "@codemirror/view"; -import { basicSetup } from "codemirror"; -import { history } from "@codemirror/commands"; -import { autocompletion, closeBrackets } from "@codemirror/autocomplete"; +import { EditorState, StateEffect, StateField } from "@codemirror/state"; +import { + gutter, + EditorView, + lineNumbers, + drawSelection, + keymap, + highlightActiveLineGutter, +} from "@codemirror/view"; +// import { basicSetup } from "@codemirror/basic-setup"; import { lintGutter } from "@codemirror/lint"; -import { bracketMatching, syntaxHighlighting } from "@codemirror/language"; +import { lintKeymap } from "@codemirror/lint"; +import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; +import { + syntaxHighlighting, + indentOnInput, + bracketMatching, + foldGutter, + foldKeymap, +} from "@codemirror/language"; import { oneDarkHighlightStyle, oneDark } from "@codemirror/theme-one-dark"; +import { + autocompletion, + completionKeymap, + closeBrackets, + closeBracketsKeymap, +} from "@codemirror/autocomplete"; + import { JSONSchema7 } from "json-schema"; // sample data @@ -13,7 +33,7 @@ import { jsonText, json5Text } from "./sample-text"; import packageJsonSchema from "./package.schema.json"; // json4 -import { jsonSchema } from "../src"; +import { jsonSchema, updateSchema } from "../src/index"; // json5 import { json5Schema } from "../src/json5"; @@ -27,12 +47,25 @@ const schema = packageJsonSchema as JSONSchema7; const commonExtensions = [ gutter({ class: "CodeMirror-lint-markers" }), bracketMatching(), - basicSetup, + highlightActiveLineGutter(), + // basicSetup, closeBrackets(), history(), autocompletion(), lineNumbers(), lintGutter(), + indentOnInput(), + drawSelection(), + foldGutter(), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...historyKeymap, + ...foldKeymap, + ...completionKeymap, + ...lintKeymap, + ]), + oneDark, EditorView.lineWrapping, EditorState.tabSize.of(2), @@ -48,7 +81,7 @@ const state = EditorState.create({ extensions: [commonExtensions, jsonSchema(schema)], }); -new EditorView({ +const editor1 = new EditorView({ state, parent: document.querySelector("#editor")!, }); @@ -61,12 +94,31 @@ const json5State = EditorState.create({ extensions: [commonExtensions, json5Schema(schema)], }); -new EditorView({ +const editor2 = new EditorView({ state: json5State, parent: document.querySelector("#editor-json5")!, }); +const handleSchema = (newSchema: JSONSchema7) => { + updateSchema(editor1, newSchema); + updateSchema(editor2, newSchema); +}; +handleSchema(schema); +// new EditorState.fi(editor1, editor2); // Hot Module Replacement // if (module.hot) { // module.hot.accept(); // } + +const schemaSelect = document.getElementById("schema-selection"); + +schemaSelect!.onchange = async (e) => { + const val = e.target!.value!; + if (!val) { + return; + } + const data = await ( + await fetch(`https://json.schemastore.org/${val}`) + ).json(); + handleSchema(data); +}; diff --git a/package.json b/package.json index 4f43186..dd4bc50 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,12 @@ "@evilmartians/lefthook": "^1.4.6", "@vitest/coverage-v8": "^0.34.6", "codemirror": "^6.0.1", + "@codemirror/language": "^6.8.0", + "@codemirror/lint": "^6.4.0", + "@codemirror/state": "^6.2.1", + "@codemirror/view": "^6.14.1", + "@codemirror/basic-setup": "^0.20.0", + "@lezer/common": "^1.0.3", "codemirror-json5": "^1.0.3", "happy-dom": "^10.3.2", "json5": "^2.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 026ee76..e801a1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,21 +8,6 @@ dependencies: '@changesets/changelog-github': specifier: ^0.4.8 version: 0.4.8 - '@codemirror/language': - specifier: ^6.8.0 - version: 6.8.0 - '@codemirror/lint': - specifier: ^6.4.0 - version: 6.4.0 - '@codemirror/state': - specifier: ^6.2.1 - version: 6.2.1 - '@codemirror/view': - specifier: ^6.14.1 - version: 6.14.1 - '@lezer/common': - specifier: ^1.0.3 - version: 1.0.3 '@sagold/json-pointer': specifier: ^5.1.1 version: 5.1.1 @@ -57,15 +42,33 @@ devDependencies: '@codemirror/autocomplete': specifier: ^6.8.1 version: 6.8.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.14.1)(@lezer/common@1.0.3) + '@codemirror/basic-setup': + specifier: ^0.20.0 + version: 0.20.0 '@codemirror/commands': specifier: ^6.2.4 version: 6.2.4 + '@codemirror/language': + specifier: ^6.8.0 + version: 6.8.0 + '@codemirror/lint': + specifier: ^6.4.0 + version: 6.4.0 + '@codemirror/state': + specifier: ^6.2.1 + version: 6.2.1 '@codemirror/theme-one-dark': specifier: ^6.1.2 version: 6.1.2 + '@codemirror/view': + specifier: ^6.14.1 + version: 6.14.1 '@evilmartians/lefthook': specifier: ^1.4.6 version: 1.4.6 + '@lezer/common': + specifier: ^1.0.3 + version: 1.0.3 '@vitest/coverage-v8': specifier: ^0.34.6 version: 0.34.6(vitest@0.34.6) @@ -339,6 +342,15 @@ packages: prettier: 2.8.8 dev: true + /@codemirror/autocomplete@0.20.3: + resolution: {integrity: sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==} + dependencies: + '@codemirror/language': 0.20.2 + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + '@lezer/common': 0.16.1 + dev: true + /@codemirror/autocomplete@6.8.1(@codemirror/language@6.8.0)(@codemirror/state@6.2.1)(@codemirror/view@6.14.1)(@lezer/common@1.0.3): resolution: {integrity: sha512-HpphvDcTdOx+9R3eUw9hZK9JA77jlaBF0kOt2McbyfvY0rX9pnMoO8rkkZc0GzSbzhIY4m5xJ0uHHgjfqHNmXQ==} peerDependencies: @@ -353,6 +365,28 @@ packages: '@lezer/common': 1.0.3 dev: true + /@codemirror/basic-setup@0.20.0: + resolution: {integrity: sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==} + deprecated: In version 6.0, this package has been renamed to just 'codemirror' + dependencies: + '@codemirror/autocomplete': 0.20.3 + '@codemirror/commands': 0.20.0 + '@codemirror/language': 0.20.2 + '@codemirror/lint': 0.20.3 + '@codemirror/search': 0.20.1 + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + dev: true + + /@codemirror/commands@0.20.0: + resolution: {integrity: sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==} + dependencies: + '@codemirror/language': 0.20.2 + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + '@lezer/common': 0.16.1 + dev: true + /@codemirror/commands@6.2.4: resolution: {integrity: sha512-42lmDqVH0ttfilLShReLXsDfASKLXzfyC36bzwcqzox9PlHulMcsUOfHXNo2X2aFMVNUoQ7j+d4q5bnfseYoOA==} dependencies: @@ -371,6 +405,17 @@ packages: dev: false optional: true + /@codemirror/language@0.20.2: + resolution: {integrity: sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==} + dependencies: + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + '@lezer/common': 0.16.1 + '@lezer/highlight': 0.16.0 + '@lezer/lr': 0.16.3 + style-mod: 4.0.3 + dev: true + /@codemirror/language@6.8.0: resolution: {integrity: sha512-r1paAyWOZkfY0RaYEZj3Kul+MiQTEbDvYqf8gPGaRvNneHXCmfSaAVFjwRUPlgxS8yflMxw2CTu6uCMp8R8A2g==} dependencies: @@ -381,12 +426,29 @@ packages: '@lezer/lr': 1.3.9 style-mod: 4.0.3 + /@codemirror/lint@0.20.3: + resolution: {integrity: sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==} + dependencies: + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + crelt: 1.0.6 + dev: true + /@codemirror/lint@6.4.0: resolution: {integrity: sha512-6VZ44Ysh/Zn07xrGkdtNfmHCbGSHZzFBdzWi0pbd7chAQ/iUcpLGX99NYRZTa7Ugqg4kEHCqiHhcZnH0gLIgSg==} dependencies: '@codemirror/state': 6.2.1 '@codemirror/view': 6.14.1 crelt: 1.0.6 + dev: true + + /@codemirror/search@0.20.1: + resolution: {integrity: sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==} + dependencies: + '@codemirror/state': 0.20.1 + '@codemirror/view': 0.20.7 + crelt: 1.0.6 + dev: true /@codemirror/search@6.5.0: resolution: {integrity: sha512-64/M40YeJPToKvGO6p3fijo2vwUEj4nACEAXElCaYQ50HrXSvRaK+NHEhSh73WFBGdvIdhrV+lL9PdJy2RfCYA==} @@ -396,6 +458,10 @@ packages: crelt: 1.0.6 dev: true + /@codemirror/state@0.20.1: + resolution: {integrity: sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==} + dev: true + /@codemirror/state@6.2.1: resolution: {integrity: sha512-RupHSZ8+OjNT38zU9fKH2sv+Dnlr8Eb8sl4NOnnqz95mCFTZUaiRP8Xv5MeeaG0px2b8Bnfe7YGwCV3nsBhbuw==} @@ -408,6 +474,14 @@ packages: '@lezer/highlight': 1.1.6 dev: true + /@codemirror/view@0.20.7: + resolution: {integrity: sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==} + dependencies: + '@codemirror/state': 0.20.1 + style-mod: 4.0.3 + w3c-keyname: 2.2.8 + dev: true + /@codemirror/view@6.14.1: resolution: {integrity: sha512-ofcsI7lRFo4N0rfnd+V3Gh2boQU3DmaaSKhDOvXUWjeOeuupMXer2e/3i9TUFN7aEIntv300EFBWPEiYVm2svg==} dependencies: @@ -667,9 +741,19 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@lezer/common@0.16.1: + resolution: {integrity: sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==} + dev: true + /@lezer/common@1.0.3: resolution: {integrity: sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA==} + /@lezer/highlight@0.16.0: + resolution: {integrity: sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==} + dependencies: + '@lezer/common': 0.16.1 + dev: true + /@lezer/highlight@1.1.6: resolution: {integrity: sha512-cmSJYa2us+r3SePpRCjN5ymCqCPv+zyXmDl0ciWtVaNiORT/MxM7ZgOMQZADD0o51qOaOg24qc/zBViOIwAjJg==} dependencies: @@ -684,6 +768,12 @@ packages: dev: false optional: true + /@lezer/lr@0.16.3: + resolution: {integrity: sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==} + dependencies: + '@lezer/common': 0.16.1 + dev: true + /@lezer/lr@1.3.9: resolution: {integrity: sha512-XPz6dzuTHlnsbA5M2DZgjflNQ+9Hi5Swhic0RULdp3oOs3rh6bqGZolosVqN/fQIT8uNiepzINJDnS39oweTHQ==} dependencies: @@ -1145,6 +1235,7 @@ packages: /crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + dev: true /cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} diff --git a/src/__tests__/__helpers__/completion.ts b/src/__tests__/__helpers__/completion.ts index bbcae18..1ae90fb 100644 --- a/src/__tests__/__helpers__/completion.ts +++ b/src/__tests__/__helpers__/completion.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vitest, Mock } from "vitest"; +import { expect, vitest } from "vitest"; import { json, jsonLanguage } from "@codemirror/lang-json"; import { json5, json5Language } from "codemirror-json5"; @@ -10,10 +10,11 @@ import { CompletionResult, CompletionSource, } from "@codemirror/autocomplete"; -import { jsonCompletion } from "../../json-completion"; +import { jsonCompletion } from "../../json-completion.js"; import { JSONSchema7 } from "json-schema"; -import { testSchema2 } from "../__fixtures__/schemas"; +import { testSchema2 } from "../__fixtures__/schemas.js"; import { EditorView } from "@codemirror/view"; +import { stateExtensions } from "../../state.js"; vitest.mock("@codemirror/autocomplete", async () => { const mod = await vitest.importActual< @@ -58,8 +59,9 @@ export async function expectCompletion( selection: { anchor: cur }, extensions: [ jsonMode(), + stateExtensions(currentSchema), jsonLang.data.of({ - autocomplete: jsonCompletion(currentSchema, { mode: conf.mode }), + autocomplete: jsonCompletion({ mode: conf.mode }), }), ], }); diff --git a/src/__tests__/json-completion.spec.ts b/src/__tests__/json-completion.spec.ts index b5109a8..e3f03ba 100644 --- a/src/__tests__/json-completion.spec.ts +++ b/src/__tests__/json-completion.spec.ts @@ -1,6 +1,6 @@ import { describe, it } from "vitest"; -import { expectCompletion } from "./__helpers__/completion"; +import { expectCompletion } from "./__helpers__/completion.js"; describe("jsonCompletion", () => { it("should return completion data for simple types", async () => { diff --git a/src/__tests__/json-hover.spec.ts b/src/__tests__/json-hover.spec.ts index 9cdbc6a..a0af00d 100644 --- a/src/__tests__/json-hover.spec.ts +++ b/src/__tests__/json-hover.spec.ts @@ -4,20 +4,24 @@ expect.extend(matchers); import "vitest-dom/extend-expect"; import { JSONSchema7 } from "json-schema"; -import { FoundCursorData, JSONHover } from "../json-hover"; +import { FoundCursorData, JSONHover } from "../json-hover.js"; import { json } from "@codemirror/lang-json"; import { EditorView } from "@codemirror/view"; -import { testSchema, testSchema2 } from "./__fixtures__/schemas"; +import { testSchema, testSchema2 } from "./__fixtures__/schemas.js"; import { Draft } from "json-schema-library"; +import { stateExtensions } from "../state.js"; const getHoverData = ( jsonString: string, pos: number, schema?: JSONSchema7 ) => { - const view = new EditorView({ doc: jsonString, extensions: [json()] }); - return new JSONHover(schema || testSchema).getDataForCursor(view, pos, 1); + const view = new EditorView({ + doc: jsonString, + extensions: [json(), stateExtensions(schema || testSchema)], + }); + return new JSONHover().getDataForCursor(view, pos, 1); }; const getHoverResult = async ( @@ -25,12 +29,11 @@ const getHoverResult = async ( pos: number, schema?: JSONSchema7 ) => { - const view = new EditorView({ doc: jsonString, extensions: [json()] }); - const hoverResult = await new JSONHover(schema || testSchema).doHover( - view, - pos, - 1 - ); + const view = new EditorView({ + doc: jsonString, + extensions: [json(), stateExtensions(schema || testSchema)], + }); + const hoverResult = await new JSONHover().doHover(view, pos, 1); return hoverResult; }; @@ -39,10 +42,13 @@ const getHoverTexts = async ( pos: number, schema?: JSONSchema7 ) => { - const view = new EditorView({ doc: jsonString, extensions: [json()] }); - const hover = new JSONHover(schema || testSchema); + const view = new EditorView({ + doc: jsonString, + extensions: [json(), stateExtensions(schema || testSchema)], + }); + const hover = new JSONHover(); const data = hover.getDataForCursor(view, pos, 1) as FoundCursorData; - const hoverResult = hover.getHoverTexts(data, hover.schema as Draft); + const hoverResult = hover.getHoverTexts(data, schema as Draft); return hoverResult; }; diff --git a/src/__tests__/json-validation.spec.ts b/src/__tests__/json-validation.spec.ts index afcfa32..4f0cf4d 100644 --- a/src/__tests__/json-validation.spec.ts +++ b/src/__tests__/json-validation.spec.ts @@ -5,11 +5,15 @@ import { describe, it, expect } from "vitest"; import { json } from "@codemirror/lang-json"; import { EditorView } from "@codemirror/view"; -import { testSchema, testSchema2 } from "./__fixtures__/schemas"; +import { testSchema, testSchema2 } from "./__fixtures__/schemas.js"; +import { stateExtensions } from "../state"; const getErrors = (jsonString: string, schema?: JSONSchema7) => { - const view = new EditorView({ doc: jsonString, extensions: [json()] }); - return new JSONValidation(schema || testSchema).doValidation(view); + const view = new EditorView({ + doc: jsonString, + extensions: [json(), stateExtensions(schema ?? testSchema)], + }); + return new JSONValidation().doValidation(view); }; const expectErrors = ( jsonString: string, diff --git a/src/bundled.ts b/src/bundled.ts index eaa944b..0199aef 100644 --- a/src/bundled.ts +++ b/src/bundled.ts @@ -1,9 +1,10 @@ import { JSONSchema7 } from "json-schema"; import { json, jsonLanguage, jsonParseLinter } from "@codemirror/lang-json"; import { hoverTooltip } from "@codemirror/view"; -import { jsonCompletion } from "./json-completion"; -import { jsonSchemaLinter } from "./json-validation"; -import { jsonSchemaHover } from "./json-hover"; +import { jsonCompletion } from "./json-completion.js"; +import { handleRefresh, jsonSchemaLinter } from "./json-validation.js"; +import { jsonSchemaHover } from "./json-hover.js"; +import { stateExtensions } from "./state.js"; import { linter } from "@codemirror/lint"; @@ -15,10 +16,13 @@ export function jsonSchema(schema: JSONSchema7) { return [ json(), linter(jsonParseLinter()), - linter(jsonSchemaLinter(schema)), + linter(jsonSchemaLinter(), { + needsRefresh: handleRefresh, + }), jsonLanguage.data.of({ - autocomplete: jsonCompletion(schema), + autocomplete: jsonCompletion(), }), - hoverTooltip(jsonSchemaHover(schema)), + hoverTooltip(jsonSchemaHover()), + stateExtensions(), ]; } diff --git a/src/index.ts b/src/index.ts index a65971e..d458446 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,24 +1,26 @@ -export { jsonCompletion } from "./json-completion"; +export { jsonCompletion } from "./json-completion.js"; export { jsonSchemaLinter, type JSONValidationOptions, -} from "./json-validation"; +} from "./json-validation.js"; export { jsonSchemaHover, type HoverOptions, type FoundCursorData, type CursorData, -} from "./json-hover"; +} from "./json-hover.js"; -export { jsonSchema } from "./bundled"; +export { jsonSchema } from "./bundled.js"; export type { JSONPointersMap, JSONPointerData, JSONPartialPointerData, -} from "./types"; +} from "./types.js"; -export * from "./utils/parseJSONDocument"; -export * from "./utils/jsonPointers"; +export * from "./utils/parseJSONDocument.js"; +export * from "./utils/jsonPointers.js"; + +export * from "./state.js"; diff --git a/src/json-completion.ts b/src/json-completion.ts index fb56f3c..400a228 100644 --- a/src/json-completion.ts +++ b/src/json-completion.ts @@ -7,7 +7,7 @@ import { import { syntaxTree } from "@codemirror/language"; import { SyntaxNode } from "@lezer/common"; import { JSONSchema7, JSONSchema7Definition } from "json-schema"; -import { debug } from "./utils/debug"; +import { debug } from "./utils/debug.js"; import { findNodeIndexInArrayNode, getChildValueNode, @@ -16,11 +16,13 @@ import { isPrimitiveValueNode, stripSurroundingQuotes, getNodeAtPosition, -} from "./utils/node"; +} from "./utils/node.js"; import { Draft07, JsonError } from "json-schema-library"; -import { jsonPointerForPosition } from "./utils/jsonPointers"; -import { TOKENS } from "./constants"; -import getSchema from "./utils/schema-lib/getSchema"; +import { jsonPointerForPosition } from "./utils/jsonPointers.js"; +import { TOKENS } from "./constants.js"; +import { getJSONSchema } from "./state.js"; +import getSchema from "./utils/schema-lib/getSchema.js"; +import { EditorState } from "@codemirror/state"; function json5PropertyInsertSnippet(rawWord: string, value: string) { if (rawWord.startsWith('"')) { @@ -53,12 +55,16 @@ type JSONCompletionOptions = { }; export class JSONCompletion { - public constructor( - private schema: JSONSchema7, - private opts: JSONCompletionOptions - ) {} - + private schema: JSONSchema7 | null = null; + constructor(private opts: JSONCompletionOptions) {} public doComplete(ctx: CompletionContext) { + this.schema = getJSONSchema(ctx.state)!; + if (!this.schema) { + // todo: should we even do anything without schema + // without taking over the existing mode responsibilties? + return []; + } + const result: CompletionResult = { from: ctx.pos, to: ctx.pos, @@ -293,7 +299,7 @@ export class JSONCompletion { ) { // expand schema property if it is a reference propertySchema = propertySchema - ? this.expandSchemaProperty(propertySchema, this.schema) + ? this.expandSchemaProperty(propertySchema, this.schema!) : propertySchema; const isJSON5 = this.opts?.mode === "json5"; @@ -681,7 +687,7 @@ export class JSONCompletion { schema: JSONSchema7, ctx: CompletionContext ): JSONSchema7Definition[] { - const draft = new Draft07(this.schema); + const draft = new Draft07(this.schema!); let pointer = jsonPointerForPosition(ctx.state, ctx.pos); let subSchema = getSchema(draft, pointer); // if we don't have a schema for the current pointer, try the parent pointer @@ -803,11 +809,8 @@ export class JSONCompletion { * provides a JSON schema enabled autocomplete extension for codemirror * @group Codemirror Extensions */ -export function jsonCompletion( - schema: JSONSchema7, - opts: JSONCompletionOptions = {} -) { - const completion = new JSONCompletion(schema, opts); +export function jsonCompletion(opts: JSONCompletionOptions = {}) { + const completion = new JSONCompletion(opts); return function jsonDoCompletion(ctx: CompletionContext) { return completion.doComplete(ctx); }; @@ -818,10 +821,9 @@ export function jsonCompletion( * @group Codemirror Extensions */ export function json5Completion( - schema: JSONSchema7, opts: Omit = {} ) { - const completion = new JSONCompletion(schema, { ...opts, mode: "json5" }); + const completion = new JSONCompletion({ ...opts, mode: "json5" }); return function jsonDoCompletion(ctx: CompletionContext) { return completion.doComplete(ctx); }; diff --git a/src/json-hover.ts b/src/json-hover.ts index df924ba..7bc65ca 100644 --- a/src/json-hover.ts +++ b/src/json-hover.ts @@ -2,12 +2,13 @@ import { type EditorView, Tooltip } from "@codemirror/view"; import { type Draft, Draft04, JsonSchema } from "json-schema-library"; import type { JSONSchema7 } from "json-schema"; -import { JSONMode, jsonPointerForPosition } from "./utils/jsonPointers"; -import { joinWithOr } from "./utils/formatting"; -import getSchema from "./utils/schema-lib/getSchema"; -import { debug } from "./utils/debug"; -import { Side } from "./types"; -import { el } from "./utils/dom"; +import { JSONMode, jsonPointerForPosition } from "./utils/jsonPointers.js"; +import { joinWithOr } from "./utils/formatting.js"; +import getSchema from "./utils/schema-lib/getSchema.js"; +import { debug } from "./utils/debug.js"; +import { Side } from "./types.js"; +import { el } from "./utils/dom.js"; +import { getJSONSchema } from "./state.js"; export type CursorData = { schema?: JsonSchema; pointer: string }; @@ -35,8 +36,8 @@ export type HoverOptions = { * provides a JSON schema enabled tooltip extension for codemirror * @group Codemirror Extensions */ -export function jsonSchemaHover(schema: JSONSchema7, options?: HoverOptions) { - const hover = new JSONHover(schema, options); +export function jsonSchemaHover(options?: HoverOptions) { + const hover = new JSONHover(options); return async function jsonDoHover(view: EditorView, pos: number, side: Side) { return hover.doHover(view, pos, side); }; @@ -55,9 +56,8 @@ function formatComplexType( } export class JSONHover { - private schema: Draft; - public constructor(schema: JSONSchema7, private opts?: HoverOptions) { - this.schema = new Draft04(schema); + private schema: Draft | null = null; + public constructor(private opts?: HoverOptions) { this.opts = { parser: JSON.parse, ...this.opts, @@ -68,6 +68,14 @@ export class JSONHover { pos: number, side: Side ): CursorData | null { + const schema = getJSONSchema(view.state)!; + if (!schema) { + // todo: should we even do anything without schema + // without taking over the existing mode responsibilties? + return null; + } + this.schema = new Draft04(schema); + const pointer = jsonPointerForPosition(view.state, pos, side); let data = undefined; @@ -153,7 +161,7 @@ export class JSONHover { const getHoverTexts = this.opts?.getHoverTexts ?? this.getHoverTexts; const hoverTexts = getHoverTexts( cursorData as FoundCursorData, - this.schema + this.schema! ); // allow users to override the hover const formatter = this.opts?.formatHover ?? this.formatMessage; diff --git a/src/json-validation.ts b/src/json-validation.ts index bd84391..5b079c9 100644 --- a/src/json-validation.ts +++ b/src/json-validation.ts @@ -1,11 +1,12 @@ -import type { EditorView } from "@codemirror/view"; -import type { Diagnostic } from "@codemirror/lint"; +import type { EditorView, ViewUpdate } from "@codemirror/view"; +import { type Diagnostic, linter } from "@codemirror/lint"; import type { JSONSchema7 } from "json-schema"; import { Draft04, type Draft, type JsonError } from "json-schema-library"; -import { joinWithOr } from "./utils/formatting"; -import { JSONPointerData } from "./types"; -import { parseJSONDocumentState } from "./utils/parseJSONDocument"; -import { RequiredPick } from "./types"; +import { joinWithOr } from "./utils/formatting.js"; +import { JSONPointerData } from "./types.js"; +import { parseJSONDocumentState } from "./utils/parseJSONDocument.js"; +import { RequiredPick } from "./types.js"; +import { getJSONSchema, schemaStateField } from "./state.js"; // return an object path that matches with the json-source-map pointer const getErrorPath = (error: JsonError): string => { @@ -28,24 +29,29 @@ export type JSONValidationOptions = { type JSONValidationSettings = RequiredPick; +export const handleRefresh = (vu: ViewUpdate) => { + console.log("handleRefresh"); + return ( + vu.startState.field(schemaStateField) !== vu.state.field(schemaStateField) + ); +}; + /** * Helper for simpler class instantiaton * @group Codemirror Extensions */ -export function jsonSchemaLinter( - schema: JSONSchema7, - options?: JSONValidationOptions -) { - const validation = new JSONValidation(schema, options); - return function jsonDoValidation(view: EditorView) { +export function jsonSchemaLinter(options?: JSONValidationOptions) { + const validation = new JSONValidation(options); + return (view: EditorView) => { return validation.doValidation(view); }; } export class JSONValidation { - private schema: Draft; + private schema: Draft | null = null; + private options: JSONValidationSettings; - public constructor(schema: JSONSchema7, options?: JSONValidationOptions) { + public constructor(options?: JSONValidationOptions) { this.options = { jsonParser: parseJSONDocumentState, ...options, @@ -56,10 +62,9 @@ export class JSONValidation { // backwards compatibility // // ajv did not support draft 4, so I used json-schema-library - this.schema = new Draft04(schema); } private get schemaTitle() { - return this.schema.getSchema().title ?? "json-schema"; + return this.schema!.getSchema().title ?? "json-schema"; } // rewrite the error message to be more human readable @@ -84,6 +89,12 @@ export class JSONValidation { // validate using view as the linter extension signature requires public doValidation(view: EditorView) { + const schema = getJSONSchema(view.state); + if (!schema) { + return []; + } + this.schema = new Draft04(schema); + if (!this.schema) return []; const text = view.state.doc.toString(); diff --git a/src/json5-bundled.ts b/src/json5-bundled.ts index e9d4c4b..bf1037c 100644 --- a/src/json5-bundled.ts +++ b/src/json5-bundled.ts @@ -1,11 +1,13 @@ import { JSONSchema7 } from "json-schema"; import { json5, json5Language, json5ParseLinter } from "codemirror-json5"; import { hoverTooltip } from "@codemirror/view"; -import { json5Completion } from "./json-completion"; -import { json5SchemaLinter } from "./json5-validation"; -import { json5SchemaHover } from "./json5-hover"; +import { json5Completion } from "./json-completion.js"; +import { json5SchemaLinter } from "./json5-validation.js"; +import { json5SchemaHover } from "./json5-hover.js"; import { linter } from "@codemirror/lint"; +import { stateExtensions } from "./state.js"; +import { handleRefresh } from "./json-validation.js"; /** * Full featured cm6 extension for json5, including `codemirror-json5` @@ -15,10 +17,13 @@ export function json5Schema(schema: JSONSchema7) { return [ json5(), linter(json5ParseLinter()), - linter(json5SchemaLinter(schema)), + linter(json5SchemaLinter(), { + needsRefresh: handleRefresh, + }), json5Language.data.of({ autocomplete: json5Completion(schema), }), hoverTooltip(json5SchemaHover(schema)), + stateExtensions(schema), ]; } diff --git a/src/json5-hover.ts b/src/json5-hover.ts index 9a40022..d8bd695 100644 --- a/src/json5-hover.ts +++ b/src/json5-hover.ts @@ -1,8 +1,8 @@ import { type EditorView } from "@codemirror/view"; -import { type HoverOptions, JSONHover } from "./json-hover"; +import { type HoverOptions, JSONHover } from "./json-hover.js"; import { type JSONSchema7 } from "json-schema"; import json5 from "json5"; -import { Side } from "./types"; +import { Side } from "./types.js"; export type JSON5HoverOptions = Exclude; @@ -14,7 +14,7 @@ export function json5SchemaHover( schema: JSONSchema7, options?: JSON5HoverOptions ) { - const hover = new JSONHover(schema, { + const hover = new JSONHover({ ...options, parser: json5.parse, }); diff --git a/src/json5-validation.ts b/src/json5-validation.ts index ea29e35..9316211 100644 --- a/src/json5-validation.ts +++ b/src/json5-validation.ts @@ -1,21 +1,22 @@ import { EditorView } from "@codemirror/view"; -import { JSONValidation, type JSONValidationOptions } from "./json-validation"; -import type { JSONSchema7 } from "json-schema"; -import { parseJSON5DocumentState } from "./utils/parseJSON5Document"; +import { linter } from "@codemirror/lint"; +import { + JSONValidation, + handleRefresh, + type JSONValidationOptions, +} from "./json-validation.js"; +import { parseJSON5DocumentState } from "./utils/parseJSON5Document.js"; /** * Instantiates a JSONValidation instance with the JSON5 mode * @group Codemirror Extensions */ -export function json5SchemaLinter( - schema: JSONSchema7, - options?: JSONValidationOptions -) { - const validation = new JSONValidation(schema, { +export function json5SchemaLinter(options?: JSONValidationOptions) { + const validation = new JSONValidation({ jsonParser: parseJSON5DocumentState, ...options, }); - return function json5DoLint(view: EditorView) { + return (view: EditorView) => { return validation.doValidation(view); }; } diff --git a/src/json5.ts b/src/json5.ts index 087e896..8695360 100644 --- a/src/json5.ts +++ b/src/json5.ts @@ -1,11 +1,11 @@ // json5 -export { json5SchemaLinter } from "./json5-validation"; -export { json5SchemaHover } from "./json5-hover"; -export { json5Completion } from "./json-completion"; +export { json5SchemaLinter } from "./json5-validation.js"; +export { json5SchemaHover } from "./json5-hover.js"; +export { json5Completion } from "./json-completion.js"; /** * @group Bundled Codemirror Extensions */ -export { json5Schema } from "./json5-bundled"; +export { json5Schema } from "./json5-bundled.js"; -export * from "./utils/parseJSON5Document"; +export * from "./utils/parseJSON5Document.js"; diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 0000000..1ae71f3 --- /dev/null +++ b/src/state.ts @@ -0,0 +1,31 @@ +import { type EditorState, StateEffect, StateField } from "@codemirror/state"; +import type { EditorView } from "@codemirror/view"; +import type { JSONSchema7 } from "json-schema"; +const schemaEffect = StateEffect.define(); + +export const schemaStateField = StateField.define({ + create() {}, + update(schema, tr) { + for (const e of tr.effects) { + if (e.is(schemaEffect)) { + return e.value; + } + } + + return schema; + }, +}); + +export const updateSchema = (view: EditorView, schema?: JSONSchema7) => { + view.dispatch({ + effects: schemaEffect.of(schema), + }); +}; + +export const getJSONSchema = (state: EditorState) => { + return state.field(schemaStateField); +}; + +export const stateExtensions = (schema?: JSONSchema7) => [ + schemaStateField.init(() => schema), +]; diff --git a/src/utils/__tests__/jsonPointers.spec.ts b/src/utils/__tests__/jsonPointers.spec.ts index 9e9b7dc..83ba11d 100644 --- a/src/utils/__tests__/jsonPointers.spec.ts +++ b/src/utils/__tests__/jsonPointers.spec.ts @@ -2,11 +2,14 @@ import { describe, it, expect } from "vitest"; import { json } from "@codemirror/lang-json"; import { json5 } from "codemirror-json5"; -import { getJsonPointers, jsonPointerForPosition } from "../jsonPointers"; +import { getJsonPointers, jsonPointerForPosition } from "../jsonPointers.js"; import { EditorState } from "@codemirror/state"; const getPointer = (jsonString: string, pos: number) => { - const state = EditorState.create({ doc: jsonString, extensions: [json()] }); + const state = EditorState.create({ + doc: jsonString, + extensions: [json()], + }); return jsonPointerForPosition(state, pos, 1); }; diff --git a/src/utils/__tests__/parseJSONDocument.spec.ts b/src/utils/__tests__/parseJSONDocument.spec.ts index 79d9aab..cc44376 100644 --- a/src/utils/__tests__/parseJSONDocument.spec.ts +++ b/src/utils/__tests__/parseJSONDocument.spec.ts @@ -1,7 +1,7 @@ import { it, describe, expect } from "vitest"; -import { parseJSONDocument } from "../parseJSONDocument"; -import { parseJSON5Document } from "../parseJSON5Document"; +import { parseJSONDocument } from "../parseJSONDocument.js"; +import { parseJSON5Document } from "../parseJSON5Document.js"; describe("parseJSONDocument", () => { it("should return a map of all pointers for a json4 document", () => { diff --git a/src/utils/jsonPointers.ts b/src/utils/jsonPointers.ts index 82016c0..f611b98 100644 --- a/src/utils/jsonPointers.ts +++ b/src/utils/jsonPointers.ts @@ -1,9 +1,9 @@ import { syntaxTree } from "@codemirror/language"; import { EditorState, Text } from "@codemirror/state"; import { SyntaxNode, SyntaxNodeRef } from "@lezer/common"; -import { JSONPointersMap, Side } from "../types"; -import { TOKENS } from "../constants"; -import { findNodeIndexInArrayNode, getWord, isValueNode } from "./node"; +import { JSONPointersMap, Side } from "../types.js"; +import { TOKENS } from "../constants.js"; +import { findNodeIndexInArrayNode, getWord, isValueNode } from "./node.js"; export type JSONMode = "json4" | "json5"; diff --git a/src/utils/node.ts b/src/utils/node.ts index d554633..8db32fc 100644 --- a/src/utils/node.ts +++ b/src/utils/node.ts @@ -1,8 +1,8 @@ import { SyntaxNode } from "@lezer/common"; -import { COMPLEX_TYPES, TOKENS, PRIMITIVE_TYPES } from "../constants"; +import { COMPLEX_TYPES, TOKENS, PRIMITIVE_TYPES } from "../constants.js"; import { EditorState, Text } from "@codemirror/state"; import { syntaxTree } from "@codemirror/language"; -import { Side } from "../types"; +import { Side } from "../types.js"; export const getNodeAtPosition = ( state: EditorState, diff --git a/src/utils/parseJSON5Document.ts b/src/utils/parseJSON5Document.ts index f076f98..e3cc1ac 100644 --- a/src/utils/parseJSON5Document.ts +++ b/src/utils/parseJSON5Document.ts @@ -5,7 +5,7 @@ import { json5 as json5mode } from "codemirror-json5"; import json5 from "json5"; import { EditorState } from "@codemirror/state"; -import { getJsonPointers } from "./jsonPointers"; +import { getJsonPointers } from "./jsonPointers.js"; /** * Return parsed data and json5 pointers for a given codemirror EditorState diff --git a/src/utils/parseJSONDocument.ts b/src/utils/parseJSONDocument.ts index 4c87f5a..39f068a 100644 --- a/src/utils/parseJSONDocument.ts +++ b/src/utils/parseJSONDocument.ts @@ -1,6 +1,6 @@ import { json } from "@codemirror/lang-json"; import { EditorState } from "@codemirror/state"; -import { getJsonPointers } from "./jsonPointers"; +import { getJsonPointers } from "./jsonPointers.js"; /** * Return parsed data and json pointers for a given codemirror EditorState diff --git a/src/utils/schema-lib/README.md b/src/utils/schema-lib/README.md index f52ae25..72c83c0 100644 --- a/src/utils/schema-lib/README.md +++ b/src/utils/schema-lib/README.md @@ -1,3 +1,3 @@ ## Temporary -These methods are borrowed from `json-schema-library` until one of our proposals for `oneOf` is merged +These methods are borrowed from `json-schema-library` until we update to the latest version diff --git a/src/utils/schema-lib/getSchema.ts b/src/utils/schema-lib/getSchema.ts index 60e0c5f..c1bb401 100644 --- a/src/utils/schema-lib/getSchema.ts +++ b/src/utils/schema-lib/getSchema.ts @@ -7,7 +7,7 @@ import { type JsonPointer, } from "json-schema-library"; -import step from "./step"; +import step from "./step.js"; const emptyObject = {}; diff --git a/src/utils/schema-lib/step.ts b/src/utils/schema-lib/step.ts index bbf5493..45edfa4 100644 --- a/src/utils/schema-lib/step.ts +++ b/src/utils/schema-lib/step.ts @@ -9,15 +9,10 @@ import { } from "json-schema-library"; // @ts-expect-error -import errors from "json-schema-library/dist/module/lib/validation/errors"; +import errors from "json-schema-library/dist/module/lib/validation/errors.js"; // @ts-expect-error -import createSchemaOf from "json-schema-library/dist/module/lib/createSchemaOf"; - -// import getTypeOf from "./getTypeOf"; -// import errors from "./validation/errors"; -// import { JsonSchema, JsonPointer, JsonError, isJsonError } from "./types"; -// import { Draft } from "./draft"; +import createSchemaOf from "json-schema-library/dist/module/lib/createSchemaOf.js"; type StepFunction = ( draft: Draft, diff --git a/tsconfig.json b/tsconfig.json index 66424ec..c79c22b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,13 +6,14 @@ "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "moduleResolution": "node", + "moduleResolution": "Node", "lib": ["ESNext", "DOM"], - "isolatedModules": true, "declaration": true, "resolveJsonModule": true, - "removeComments": false + "removeComments": false, + "isolatedModules": true }, + "compileOnSave": true, "include": ["src/**/*"], "exclude": ["node_modules", "**/__tests__/**"] }