├── .eslintrc.json
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── JSONataLanguage.code-workspace
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── src
├── extension.ts
├── kernel
│ ├── functions.ts
│ ├── kernel.ts
│ └── loader
│ │ ├── json.ts
│ │ └── xml.ts
├── language
│ ├── CompletionProvider.ts
│ ├── diagnostics.ts
│ ├── formatter.ts
│ └── functions.ts
└── notebook
│ ├── notebookKernel.ts
│ └── notebookSerializer.ts
├── syntaxes
├── jsonata-configuration.json
└── jsonata.tmLanguage.json
├── test
├── cos.jsonata
├── order.jsonata
├── packagejson.jsonata-book
├── selector.jsonata
└── test.jsonata
├── tsconfig.json
└── webpack.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "extends": [
5 | "airbnb-base"
6 | ],
7 | "parserOptions": {
8 | "ecmaVersion": 6,
9 | "sourceType": "module"
10 | },
11 | "plugins": [
12 | "@typescript-eslint"
13 | ],
14 | "rules": {
15 | "@typescript-eslint/naming-convention": "warn",
16 | "@typescript-eslint/semi": "warn",
17 | "curly": "warn",
18 | "eqeqeq": "warn",
19 | "no-throw-literal": "warn",
20 | "semi": "off",
21 | "import/no-unresolved": "off",
22 | "import/extensions": "off"
23 | },
24 | "ignorePatterns": [
25 | "out",
26 | "dist",
27 | "**/*.d.ts"
28 | ]
29 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: NodeJS with Webpack
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [14.x, 16.x, 18.x]
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 |
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 |
25 | - name: Build
26 | run: |
27 | npm install
28 | npm run package
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | dist
3 | node_modules
4 | .vscode-test/
5 | .vscode-test-web/
6 | *.vsix
7 | test/*.tmp
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See http://go.microsoft.com/fwlink/?LinkId=827846
3 | // for the documentation about the extensions.json format
4 | "recommendations": [
5 | "dbaeumer.vscode-eslint",
6 | "eamodio.tsl-problem-matcher"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceFolder}"
14 | ],
15 | "outFiles": [
16 | "${workspaceFolder}/dist/**/*.js"
17 | ],
18 | "preLaunchTask": "${defaultBuildTask}"
19 | },
20 | {
21 | "name": "Extension Tests",
22 | "type": "extensionHost",
23 | "request": "launch",
24 | "args": [
25 | "--extensionDevelopmentPath=${workspaceFolder}",
26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
27 | ],
28 | "outFiles": [
29 | "${workspaceFolder}/out/test/**/*.js"
30 | ],
31 | "preLaunchTask": "npm: test-watch"
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "out": false, // set this to true to hide the "out" folder with the compiled JS files
5 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files
6 | },
7 | "search.exclude": {
8 | "out": true, // set this to false to include "out" folder in search results
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts
12 | "typescript.tsc.autoDetect": "off"
13 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": [
10 | "$ts-webpack-watch",
11 | "$tslint-webpack-watch"
12 | ],
13 | "isBackground": true,
14 | "presentation": {
15 | "reveal": "never"
16 | },
17 | "group": {
18 | "kind": "build",
19 | "isDefault": true
20 | }
21 | },
22 | {
23 | "type": "npm",
24 | "script": "test-watch",
25 | "problemMatcher": "$tsc-watch",
26 | "isBackground": true,
27 | "presentation": {
28 | "reveal": "never"
29 | },
30 | "group": "build"
31 | }
32 | ]
33 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | .vscode-test-web/**
4 | out/**
5 | node_modules/**
6 | src/**
7 | .gitignore
8 | .yarnrc
9 | vsc-extension-quickstart.md
10 | **/tsconfig.json
11 | **/.eslintrc.json
12 | **/*.map
13 | **/*.ts
14 | *.code-workspace
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to the "vscode-language-jsonata" extension will be documented in this file.
4 |
5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6 |
7 | ## [Unreleased]
8 | - Support Error Diagnostics in JSONata files and notebooks
9 |
10 | ## [1.0.0]
11 | - Support block comments (#2)
12 | - Update JSONata to 2.0.3
13 | - Fix parsing string quotes (#5)
14 | - Change of evaluation behavior (Promises are evaluated during JSONata evaluation)
15 | - Implement new functions:
16 | - $parseString()
17 | - $loadUrl()
18 | - $readFile()
19 | - $readUrl()
20 | - $eval()
21 | - $import()
22 | - $writeFile()
23 |
24 | ## [0.4.0]
25 | - Update JSONata to version 2.0.2
26 |
27 | ## [0.3.1]
28 | - Fix issue where VS Code does not recognize the mime type `application/json` as notebook renderer anymore
29 |
30 | ## [0.3.0]
31 |
32 | - Support importing XML files in `$loadFile()`
33 | ## [0.2.1]
34 |
35 | - Better error handling for loading files with `$loadFile()`
36 | ## [0.2.0]
37 |
38 | - Enable web extension (currently only syntax highlighting works - there is no renderer for the JSON output)
39 | - Code Completion for defined functions and vars
40 |
41 | ## [0.1.1]
42 |
43 | - Add test file
44 | - Extend Readme
45 | ## [0.1.0]
46 |
47 | - Initial release
48 | - Supports the grammar from try.jsonata.org
49 | - Supports notebooks
--------------------------------------------------------------------------------
/JSONataLanguage.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ]
7 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT license
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vscode-language-jsonata
2 |
3 | This extension brings the JSONata query engine to Visual Studio Code.
4 |
5 | ## Features
6 |
7 | This extension enables JSONata language support to Visual Studio Code. The extension is activated for files with the *.jsonata extension.
8 |
9 | We also support JSONata Notebooks. They are automatically activated for files with the *.jsonata-book extension.
10 |
11 | Within notebooks several additional functions are available:
12 |
13 | - `$parseString($content[, $type])` takes up to two arguments. It parses `$content` into an JSON object. This happens based on the variable `$type` with a JSON or an XML parser. JSON is the default parser.
14 | - `$loadFile($file[, $type])` takes up to two arguments. `$file` represents the filename to be loaded. With the optional argument `$type` one can specify how this file should be loaded. At the moment only `json` and `xml` can be chosen whereas json is the standard if `$type` is missing.
15 | - `$loadUrl($url[, $type])` works just like `$loadFile()` but with URLs instead of files.
16 |
17 | - `$readFile($file)` reads a file and returns the result as a string.
18 | - `$writeFile($file, $content)` writes the data of `$content` into the file `$file`. The content can be an object and is stringified as a JSON string. The indentation is set to `2`.
19 | - `$readUrl($url)` reads a url and returns the result as a string.
20 |
21 | - `$eval($content)` evaluates a string using the JSONata compiler.
22 | - `$import($file)` imports a file and evaluates it. This is useful for defining often used functions in a separate file and then include it in a notebook. If several functions shall be imported from a single file, then export them as a object. This could look like this:
23 | ```json
24 | {
25 | $hello := function() {
26 | "hello"
27 | };
28 | $world := function() {
29 | "world"
30 | }
31 | }
32 | ```
33 | In the notebook you can then use this use this code:
34 | ```json
35 | (
36 | $api = $import("test.jsonata");
37 | $api.hello();
38 | $api.world()
39 | )
40 | ```
41 |
42 |
43 | Each code cell of the notebook can access the result of the most recent executed cell by using `$ans`.
44 |
45 | A good documentation for the JSONata language can be found [here](https://docs.jsonata.org/overview.html).
46 |
47 |
48 |
49 | ## Requirements
50 |
51 | None
52 |
53 | ## Extension Settings
54 |
55 | None
56 |
57 | ## Known Issues
58 |
59 | - None
60 |
61 | ## Release Notes
62 |
63 | ## 1.0.0
64 | - Support block comments (#2)
65 | - Fix parsing string quotes (#5)
66 | - Change of evaluation behavior (Promises are evaluated during JSONata evaluation)
67 | - Implement new functions:
68 | - $parseString()
69 | - $loadUrl()
70 | - $readFile()
71 | - $readUrl()
72 | - $eval()
73 | - $import()
74 | - $writeFile()
75 |
76 | ## 0.4.0
77 | - Update JSONata to version 2.0.2
78 |
79 | ## 0.3.1
80 | - Fix issue where VS Code does not recognize the mime type `application/json` as notebook renderer anymore
81 |
82 | ## 0.3.0
83 |
84 | - Support importing XML files in `$loadFile()`
85 |
86 | ## 0.2.1
87 |
88 | - Better error handling for loading files with `$loadFile()`
89 |
90 | ## 0.2.0
91 |
92 | - Enable web extension (currently only syntax highlighting works - there is no renderer for the JSON output)
93 | - Code Completion for defined functions and vars
94 |
95 | ### 0.1.0
96 |
97 | - Initial release
98 | - Supports the grammar from try.jsonata.org
99 | - Supports notebooks
100 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-language-jsonata",
3 | "displayName": "VSCode Language JSONata",
4 | "description": "Language support for JSONata",
5 | "version": "1.0.0",
6 | "engines": {
7 | "vscode": "^1.64.0"
8 | },
9 | "repository": {
10 | "url": "https://github.com/bigbug/vscode-language-jsonata"
11 | },
12 | "author": {
13 | "name": "@bigbug",
14 | "url": "https://github.com/bigbug"
15 | },
16 | "publisher": "bigbug",
17 | "categories": [
18 | "Other"
19 | ],
20 | "activationEvents": [
21 | "onNotebook:jsonata-book",
22 | "onLanguage:jsonata"
23 | ],
24 | "main": "./dist/extension.js",
25 | "browser": "./dist/extension.js",
26 | "contributes": {
27 | "languages": [
28 | {
29 | "id": "jsonata",
30 | "extensions": [
31 | ".jsonata"
32 | ],
33 | "configuration": "./syntaxes/jsonata-configuration.json"
34 | }
35 | ],
36 | "grammars": [
37 | {
38 | "language": "jsonata",
39 | "scopeName": "source.jsonata",
40 | "path": "./syntaxes/jsonata.tmLanguage.json"
41 | }
42 | ],
43 | "notebooks": [
44 | {
45 | "id": "jsonata-book",
46 | "type": "jsonata-book",
47 | "displayName": "JSONata Book",
48 | "selector": [
49 | {
50 | "filenamePattern": "*.jsonata-book"
51 | }
52 | ]
53 | }
54 | ]
55 | },
56 | "scripts": {
57 | "vscode:prepublish": "npm run package",
58 | "compile": "webpack",
59 | "watch": "webpack --watch",
60 | "open-in-browser": "vscode-test-web --extensionDevelopmentPath=. .",
61 | "package": "webpack --mode production --devtool hidden-source-map",
62 | "test-compile": "tsc -p . --outDir out",
63 | "pretest": "npm run test-compile && npm run compile && npm run lint",
64 | "lint": "eslint src --ext ts",
65 | "test": "node ./out/test/runTest.js"
66 | },
67 | "devDependencies": {
68 | "@types/glob": "^7.1.4",
69 | "@types/jsonata": "^1.5.1",
70 | "@types/mocha": "^9.0.0",
71 | "@types/node": "14.x",
72 | "@types/vscode": "^1.64.0",
73 | "@typescript-eslint/eslint-plugin": "^4.31.1",
74 | "@typescript-eslint/parser": "^4.31.1",
75 | "@vscode/test-electron": "^1.6.2",
76 | "@vscode/test-web": "*",
77 | "eslint": "^7.32.0",
78 | "eslint-config-airbnb-base": "^15.0.0",
79 | "glob": "^7.1.7",
80 | "mocha": "^9.1.1",
81 | "ts-loader": "^9.2.5",
82 | "typescript": "^4.4.3",
83 | "webpack": "^5.52.1",
84 | "webpack-cli": "^4.8.0"
85 | },
86 | "dependencies": {
87 | "@types/lodash": "^4.14.178",
88 | "json-stringify-safe": "^5.0.1",
89 | "jsonata": "^2.0.3",
90 | "lodash": "^4.17.21",
91 | "node-fetch": "^3.3.2",
92 | "vscode-uri": "^3.0.3",
93 | "xml2js": "^0.6.2"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | // The module 'vscode' contains the VS Code extensibility API
2 | // Import the module and reference it with the alias vscode in your code below
3 | import * as vscode from 'vscode';
4 | import CompletionProvider from './language/CompletionProvider';
5 | import NotebookKernel from './notebook/notebookKernel';
6 | import NotebookSerializer from './notebook/notebookSerializer';
7 | import subscribeToDocumentChanges from './language/diagnostics';
8 | import JSONataDocumentFormatter from './language/formatter';
9 |
10 | // this method is called when your extension is activated
11 | // your extension is activated the very first time the command is executed
12 | export function activate(context: vscode.ExtensionContext) {
13 | context.subscriptions.push(new NotebookKernel());
14 | context.subscriptions.push(vscode.workspace.registerNotebookSerializer('jsonata-book', new NotebookSerializer(), {
15 | transientOutputs: false,
16 | transientCellMetadata: {
17 | inputCollapsed: true,
18 | outputCollapsed: true,
19 | },
20 | }));
21 |
22 | context.subscriptions.push(vscode.languages.registerCompletionItemProvider(
23 | ['jsonata'],
24 | new CompletionProvider(),
25 | '$',
26 | ));
27 |
28 | const jsonataDiagnostics = vscode.languages.createDiagnosticCollection('jsonata');
29 | context.subscriptions.push(jsonataDiagnostics);
30 |
31 | subscribeToDocumentChanges(context, jsonataDiagnostics);
32 | context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(
33 | ['jsonata'],
34 | new JSONataDocumentFormatter(),
35 | ));
36 | }
37 |
38 | // this method is called when your extension is deactivated
39 | export function deactivate() {}
40 |
--------------------------------------------------------------------------------
/src/kernel/functions.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-promise-reject-errors */
2 | // eslint-disable-next-line max-classes-per-file
3 | import * as vscode from 'vscode';
4 | import { Utils } from 'vscode-uri';
5 | import fetch from 'node-fetch';
6 | import loadJSON from './loader/json';
7 | import loadXML from './loader/xml';
8 |
9 | const jsonata = require('jsonata');
10 |
11 | // NEEDED Declaration to silence errors
12 | declare class TextDecoder {
13 | decode(data: Uint8Array): string;
14 | }
15 |
16 | declare class TextEncoder {
17 | encode(data: string): Uint8Array;
18 | }
19 |
20 | export function parseString(content: string, type?: string) {
21 | if (!type || type === 'json') {
22 | return loadJSON(content);
23 | } if (type === 'xml') {
24 | return loadXML(content);
25 | }
26 | return Promise.reject('unknown file handler!');
27 | }
28 |
29 | export function readFile(filename: string) {
30 | if (!vscode.workspace.workspaceFolders) { return Promise.reject('No workspace loaded!'); }
31 | const folderUri = vscode.workspace.workspaceFolders[0].uri;
32 | const fileUri = Utils.joinPath(folderUri, filename);
33 |
34 | return vscode.workspace.fs.readFile(fileUri)
35 | .then((data) => {
36 | const string = new TextDecoder().decode(data);
37 | return Promise.resolve(string);
38 | }) as Promise;
39 | }
40 |
41 | export function writeFile(filename: string, content: any) {
42 | if (!vscode.workspace.workspaceFolders) { return Promise.reject('No workspace loaded!'); }
43 | const folderUri = vscode.workspace.workspaceFolders[0].uri;
44 | const fileUri = Utils.joinPath(folderUri, filename);
45 |
46 | const data = JSON.stringify(content, undefined, 2);
47 | return vscode.workspace.fs.writeFile(fileUri, new TextEncoder().encode(data))
48 | .then(() => Promise.resolve(undefined)) as Promise;
49 | }
50 |
51 | export function readUrl(url: string) {
52 | return fetch(url).then((res) => res.text());
53 | }
54 |
55 | export function loadFile(filename: string, type?: string) {
56 | return readFile(filename)
57 | .then((content) => parseString(content, type));
58 | }
59 |
60 | export function evaluate(content: string, data: unknown, bindings: {[name:string] : unknown}) {
61 | const jsonataObject = jsonata(content);
62 | return jsonataObject.evaluate(data, bindings);
63 | }
64 |
65 | export function importFile(filename: string, data: unknown, bindings: {[name:string] : unknown}) {
66 | return readFile(filename)
67 | .then((content) => evaluate(content, data, bindings));
68 | }
69 |
70 | export function loadUrl(filename: string, type?: string) {
71 | return readUrl(filename).then((content) => parseString(content, type));
72 | }
73 |
--------------------------------------------------------------------------------
/src/kernel/kernel.ts:
--------------------------------------------------------------------------------
1 | import jsonata = require('jsonata');
2 | import {
3 | evaluate, importFile, loadFile, loadUrl, parseString, readUrl, readFile, writeFile,
4 | } from './functions';
5 |
6 | export default class JSONataKernel {
7 | private data : unknown = undefined;
8 |
9 | private bindings : {[name:string] : unknown} = {};
10 |
11 | public async restart() {
12 | this.data = undefined;
13 | this.bindings = {};
14 | }
15 |
16 | private wrapLoader(func: (...args: any[]) => Promise) :
17 | (...args: any[]) => Promise {
18 | return (...args: any[]) => func(...args).then((res: unknown) => {
19 | this.data = res;
20 | return res;
21 | });
22 | }
23 |
24 | private registerFunctions(obj: jsonata.Expression) {
25 | const funcs : {
26 | name: string,
27 | pointer: (...args: any[]) => Promise | PromiseLike,
28 | parameters: string,
29 | }[] = [
30 | {
31 | name: 'parseString',
32 | pointer: parseString,
33 | parameters: '',
34 | },
35 | {
36 | name: 'loadFile',
37 | pointer: this.wrapLoader(loadFile),
38 | parameters: '',
39 | },
40 | {
41 | name: 'loadUrl',
42 | pointer: this.wrapLoader(loadUrl),
43 | parameters: '',
44 | },
45 | {
46 | name: 'readFile',
47 | pointer: readFile,
48 | parameters: '',
49 | },
50 | {
51 | name: 'writeFile',
52 | pointer: writeFile,
53 | parameters: '',
54 | },
55 | {
56 | name: 'readUrl',
57 | pointer: readUrl,
58 | parameters: '',
59 | },
60 | {
61 | name: 'eval',
62 | pointer: evaluate,
63 | parameters: '',
64 | },
65 | {
66 | name: 'import',
67 | pointer: importFile,
68 | parameters: '',
69 | },
70 | ];
71 | funcs.forEach(
72 | (a) => obj.registerFunction(a.name, (...b: any[]) => a.pointer(...b), a.parameters),
73 | );
74 | }
75 |
76 | public async run(code: string) {
77 | const obj = jsonata(code);
78 | this.registerFunctions(obj);
79 | const result = await obj.evaluate(this.data, this.bindings);
80 | const match = code.trim().match(/^\$(\w+)\s*:=/);
81 | if (match) {
82 | this.bindings[match[1]] = result;
83 | }
84 | this.bindings.ans = result;
85 | return result;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/kernel/loader/json.ts:
--------------------------------------------------------------------------------
1 | export default function loadJSON(fileString: string) : Promise {
2 | try {
3 | return Promise.resolve(JSON.parse(fileString));
4 | } catch(e) {
5 | return Promise.reject(e);
6 | }
7 | }
--------------------------------------------------------------------------------
/src/kernel/loader/xml.ts:
--------------------------------------------------------------------------------
1 | const xml2js = require('xml2js');
2 | export default function loadXML(fileString: string) : Promise {
3 | let parser = new xml2js.Parser(/* options */);
4 | return parser.parseStringPromise(fileString);
5 | }
--------------------------------------------------------------------------------
/src/language/CompletionProvider.ts:
--------------------------------------------------------------------------------
1 | import { uniq } from 'lodash';
2 | import {
3 | CompletionItem,
4 | CompletionItemKind,
5 | CompletionItemProvider, NotebookCell, Position, SnippetString, TextDocument,
6 | } from 'vscode';
7 | import functions from './functions';
8 |
9 | export default class CompletionProvider implements CompletionItemProvider {
10 | // eslint-disable-next-line class-methods-use-this
11 | provideCompletionItems(
12 | document: TextDocument,
13 | position: Position,
14 | // token: CancellationToken,
15 | // context: CompletionContext
16 | ) : CompletionItem[] | undefined {
17 | const line = document.lineAt(position.line).text.substring(0, position.character);
18 |
19 | const reg = [
20 | {
21 | regex: /\$$/,
22 | // eslint-disable-next-line no-unused-vars
23 | func: (_a: any) => functions.split(',').map((f) => {
24 | const item = new CompletionItem(`$${f}()`, CompletionItemKind.Function);
25 | item.insertText = new SnippetString(`${f}($1)`);
26 | return item;
27 | }),
28 | },
29 | {
30 | regex: /\$$/,
31 | // eslint-disable-next-line no-unused-vars
32 | func: (_a: any) => {
33 | let text = document.getText();
34 | if ((document as any).notebook) {
35 | const cells : NotebookCell[] = (document as any).notebook.getCells();
36 | text = cells.map((cell) => cell.document.getText()).join(';\n');
37 | }
38 | const matches = [...text.matchAll(/\$(\w+)\s*:=\s*(function\s*\(|λ\s*\()?/g)];
39 |
40 | const functionMatches = uniq(matches.filter((i) => i[2]).map((i) => i[1]));
41 | const varMatches = uniq(matches.filter((i) => !i[2]).map((i) => i[1]));
42 | return [
43 | ...functionMatches.map((f) => {
44 | const item = new CompletionItem(`$${f}()`, CompletionItemKind.Function);
45 | item.insertText = new SnippetString(`${f}($1)`);
46 | return item;
47 | }),
48 | ...varMatches.map((f) => {
49 | const item = new CompletionItem(`$${f}`, CompletionItemKind.Function);
50 | item.insertText = new SnippetString(f);
51 | return item;
52 | }),
53 | ];
54 | },
55 | },
56 | ];
57 |
58 | let suggestions: CompletionItem[] = [];
59 |
60 | reg.forEach((el) => {
61 | const result = line.match(el.regex);
62 | if (result) {
63 | suggestions = suggestions.concat(el.func(result));
64 | }
65 | });
66 |
67 | return suggestions;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/language/diagnostics.ts:
--------------------------------------------------------------------------------
1 | import jsonata = require('jsonata');
2 | import { isNumber, isObject } from 'lodash';
3 | import * as vscode from 'vscode';
4 |
5 | /**
6 | * Analyzes the text document for problems.
7 | * This demo diagnostic problem provider finds all mentions of 'emoji'.
8 | * @param doc text document to analyze
9 | * @param jsonataDiagnostics diagnostic collection
10 | */
11 | export function refreshDiagnostics(
12 | doc: vscode.TextDocument,
13 | jsonataDiagnostics: vscode.DiagnosticCollection,
14 | ): void {
15 | const diagnostics: vscode.Diagnostic[] = [];
16 |
17 | const code = doc.getText();
18 |
19 | try {
20 | jsonata(code);
21 | } catch (e: any) {
22 | // @ts-ignore
23 | if (!e || !isObject(e) || !e.position || !isNumber(e.position)) {
24 | diagnostics.push(new vscode.Diagnostic(
25 | new vscode.Range(new vscode.Position(0, 0), new vscode.Position(999999, 999999)),
26 | 'Unknown JSONata error!',
27 | vscode.DiagnosticSeverity.Error,
28 | ));
29 | } else {
30 | // @ts-ignore
31 | const { position, message } = e;
32 | const lines = code.slice(0, position).split('\n');
33 | const line = lines.length - 1;
34 | diagnostics.push(new vscode.Diagnostic(
35 | new vscode.Range(
36 | new vscode.Position(line, lines[lines.length - 1].length),
37 | new vscode.Position(line, 99999),
38 | ),
39 | message,
40 | vscode.DiagnosticSeverity.Error,
41 | ));
42 | }
43 | diagnostics.push();
44 | }
45 | jsonataDiagnostics.set(doc.uri, diagnostics);
46 | }
47 |
48 | export default function subscribeToDocumentChanges(
49 | context: vscode.ExtensionContext,
50 | jsonataDiagnostics: vscode.DiagnosticCollection,
51 | ): void {
52 | if (vscode.window.activeTextEditor) {
53 | refreshDiagnostics(vscode.window.activeTextEditor.document, jsonataDiagnostics);
54 | }
55 | context.subscriptions.push(
56 | vscode.window.onDidChangeActiveTextEditor((editor) => {
57 | if (editor) {
58 | refreshDiagnostics(editor.document, jsonataDiagnostics);
59 | }
60 | }),
61 | );
62 |
63 | context.subscriptions.push(
64 | vscode.workspace.onDidChangeTextDocument(
65 | (e) => refreshDiagnostics(e.document, jsonataDiagnostics),
66 | ),
67 | );
68 |
69 | vscode.workspace.onDidChangeNotebookDocument(
70 | (e) => {
71 | e.notebook.getCells().forEach((cell) => {
72 | refreshDiagnostics(cell.document, jsonataDiagnostics);
73 | });
74 | },
75 | );
76 |
77 | context.subscriptions.push(
78 | vscode.workspace.onDidCloseTextDocument(
79 | (doc) => jsonataDiagnostics.delete(doc.uri),
80 | ),
81 | );
82 | }
83 |
--------------------------------------------------------------------------------
/src/language/formatter.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line max-classes-per-file
2 | import jsonata = require('jsonata');
3 | import {
4 | DocumentFormattingEditProvider, Position, ProviderResult, Range, TextDocument, TextEdit, window,
5 | } from 'vscode';
6 |
7 | class Formatter {
8 | private indent = 0;
9 |
10 | private indentStep = 2;
11 |
12 | private formattedCode: string = '';
13 |
14 | constructor(code: string) {
15 | const obj = jsonata(code).ast();
16 | this.evaluate(obj);
17 |
18 | if (this.strip(code) !== this.strip(this.formattedCode)) {
19 | // window.showErrorMessage('Error on formatting! Input and output are different!');
20 | // throw new Error('Error on formatting! Input and output are different!');
21 | }
22 | }
23 |
24 | // eslint-disable-next-line class-methods-use-this
25 | private strip(code: string) {
26 | let res = code;
27 | res = res.replace(/[ \s\t\n]/g, '');
28 | return res;
29 | }
30 |
31 | private evaluate(obj: jsonata.ExprNode) {
32 | // this.rest(obj);
33 | // return;
34 | if (obj.type === 'unary' && obj.value === '{') {
35 | this.evaluteObj(obj);
36 | } else if (obj.type === 'unary' && obj.value === '[') {
37 | this.evaluateArray(obj);
38 | } else if (obj.type === 'unary' && obj.value === '-') {
39 | this.evaluateMinus(obj);
40 | } else if (obj.type === 'string') {
41 | this.evaluateString(obj);
42 | } else if (obj.type === 'number') {
43 | this.evaluateNumber(obj);
44 | } else if (obj.type === 'lambda') {
45 | this.evaluateLambda(obj);
46 | } else if (obj.type === 'variable') {
47 | this.evaluateVariable(obj);
48 | } else if (obj.type === 'binary') {
49 | this.evaluateBinary(obj);
50 | } else if (obj.type === 'name') {
51 | this.evaluateName(obj);
52 | } else if (obj.type === 'path') {
53 | this.evaluatePath(obj);
54 | } else if (obj.type === 'block') {
55 | this.evaluateBlock(obj);
56 | } else if (obj.type === 'wildcard' || obj.type === 'descendant') {
57 | this.evaluateWildcard(obj);
58 | } else if (obj.type === 'parent') {
59 | this.evaluateParent(obj);
60 | } else if (obj.type === 'apply') {
61 | this.evaluateApply(obj);
62 | } else if (obj.type === 'bind') {
63 | this.evaluateBind(obj);
64 | } else if (obj.type === 'condition') {
65 | this.evaluateCondition(obj);
66 | } else if (obj.type === 'function') {
67 | this.evaluateFunction(obj);
68 | } else if (obj.type === 'regex') {
69 | this.evaluateRegex(obj);
70 | } else if (obj.type === 'filter') {
71 | this.evaluateFilter(obj);
72 | } else {
73 | this.rest(obj);
74 | }
75 | }
76 |
77 | private rest(obj: jsonata.ExprNode) {
78 | this.p(JSON.stringify(obj, undefined, 4));
79 | }
80 |
81 | private p(code: string) {
82 | this.formattedCode += code.replace('\n', `\n${' '.repeat(this.indent)}`);
83 | }
84 |
85 | private i() {
86 | this.indent += this.indentStep;
87 | }
88 |
89 | private d() {
90 | this.indent -= this.indentStep;
91 | }
92 |
93 | private evaluteObj(obj: jsonata.ExprNode) {
94 | this.p('{');
95 | this.i();
96 | obj.lhs?.forEach((e, i, a) => {
97 | this.p('\n');
98 | // @ts-ignore
99 | this.evaluate(e[0]);
100 | this.p(': ');
101 | // @ts-ignore
102 | this.evaluate(e[1]);
103 | if (i + 1 !== a.length) this.p(',');
104 | });
105 | this.d();
106 | this.p('\n');
107 | this.p('}');
108 | }
109 |
110 | private evaluateArray(obj:jsonata.ExprNode) {
111 | if (obj.expressions?.length === 1) {
112 | this.p('[');
113 | this.evaluate(obj.expressions[0]);
114 | this.p(']');
115 | return;
116 | }
117 | this.i();
118 | this.p('[');
119 | obj.expressions?.forEach((e, i, a) => {
120 | this.p('\n');
121 | this.evaluate(e);
122 | if (i + 1 !== a.length) this.p(',');
123 | });
124 | this.d();
125 | this.p('\n]');
126 | }
127 |
128 | private evaluateBlock(obj: jsonata.ExprNode) {
129 | if (obj.expressions?.length === 1) {
130 | this.p('(');
131 | this.evaluate(obj.expressions[0]);
132 | this.p(')');
133 | return;
134 | }
135 | this.i();
136 | this.p('(\n');
137 | obj.expressions?.forEach((e, i, a) => {
138 | this.evaluate(e);
139 | if (i + 1 !== a.length) this.p(';\n');
140 | });
141 | this.d();
142 | this.p('\n)');
143 | }
144 |
145 | private evaluateCondition(obj: jsonata.ExprNode) {
146 | // @ts-ignore
147 | this.evaluate(obj.condition);
148 | this.i();
149 | this.p('\n? ');
150 | // @ts-ignore
151 | this.evaluate(obj.then);
152 | this.p('\n: ');
153 | // @ts-ignore
154 | this.evaluate(obj.else);
155 | this.d();
156 | }
157 |
158 | private evaluateBind(obj: jsonata.ExprNode) {
159 | // @ts-ignore
160 | this.evaluate(obj.lhs);
161 | this.p(` ${obj.value} `);
162 | // @ts-ignore
163 | this.evaluate(obj.rhs);
164 | }
165 |
166 | private evaluateString(obj: jsonata.ExprNode) {
167 | this.p(`"${obj.value}"`);
168 | }
169 |
170 | private evaluateMinus(obj: jsonata.ExprNode) {
171 | this.p('-');
172 | // @ts-ignore
173 | this.evaluate(obj.expression);
174 | }
175 |
176 | private evaluateNumber(obj: jsonata.ExprNode) {
177 | this.p(`${obj.value}`);
178 | }
179 |
180 | private evaluateArguments(obj: jsonata.ExprNode) {
181 | obj.arguments?.forEach((arg, index, a) => {
182 | this.evaluate(arg);
183 | if (index + 1 !== a.length) {
184 | this.p(', ');
185 | }
186 | });
187 | }
188 |
189 | private evaluateLambda(obj: jsonata.ExprNode) {
190 | // @ts-ignore
191 | if (Object.keys(obj).includes('thunk') && obj.thunk) {
192 | // @ts-ignore
193 | this.evaluate(obj.body);
194 | return;
195 | }
196 | this.p('function(');
197 | this.evaluateArguments(obj);
198 | this.i();
199 | this.p(') {\n');
200 | // @ts-ignore
201 | this.evaluate(obj.body);
202 | this.d();
203 | this.p('\n}');
204 | }
205 |
206 | private evaluateFunction(obj: jsonata.ExprNode) {
207 | // @ts-ignore
208 | this.evaluate(obj.procedure);
209 | this.p('(');
210 | this.evaluateArguments(obj);
211 | this.p(')');
212 | }
213 |
214 | private evaluateVariable(obj: jsonata.ExprNode) {
215 | this.p(`$${obj.value}`);
216 | }
217 |
218 | private evaluateWildcard(obj: jsonata.ExprNode) {
219 | this.p(obj.value);
220 | }
221 |
222 | // eslint-disable-next-line no-unused-vars
223 | private evaluateParent(obj: jsonata.ExprNode) {
224 | this.p('%');
225 | }
226 |
227 | private evaluateRegex(obj: jsonata.ExprNode) {
228 | this.p(obj.value.toString());
229 | }
230 |
231 | private evaluateBinary(obj: jsonata.ExprNode) {
232 | // @ts-ignore
233 | this.evaluate(obj.lhs);
234 | this.p(` ${obj.value} `);
235 | // @ts-ignore
236 | this.evaluate(obj.rhs);
237 | }
238 |
239 | private evaluatePath(obj: jsonata.ExprNode) {
240 | let i = 0;
241 | // @ts-ignore
242 | if (obj.steps[0].type === 'variable' && obj.steps[0].value === '') {
243 | i = 1;
244 | this.p('$');
245 | }
246 | // @ts-ignore
247 | for (i; i < obj.steps?.length; i += 1) {
248 | if (i !== 0) this.p('.');
249 | // @ts-ignore
250 | this.evaluate(obj.steps[i]);
251 |
252 | // @ts-ignore
253 | if (obj.steps[i].stages) {
254 | // @ts-ignore
255 | obj.steps[i].stages?.forEach((e) => this.evaluate(e));
256 | }
257 | }
258 | // @ts-ignore
259 | if (obj.group) {
260 | // @ts-ignore
261 | this.evaluteObj(obj.group);
262 | }
263 | }
264 |
265 | private evaluateFilter(obj: jsonata.ExprNode) {
266 | this.p('[');
267 | // @ts-ignore
268 | this.evaluate(obj.expr);
269 | this.p(']');
270 | }
271 |
272 | private evaluateApply(obj:jsonata.ExprNode) {
273 | // @ts-ignore
274 | this.evaluate(obj.lhs);
275 | this.p(` ${obj.value} `);
276 | // @ts-ignore
277 | this.evaluate(obj.rhs);
278 | }
279 |
280 | private evaluateName(obj: jsonata.ExprNode) {
281 | const value = obj.value as string;
282 | if (value.includes(' ')) {
283 | this.p(`\`${value}\``);
284 | return;
285 | }
286 | this.p(value);
287 | }
288 |
289 | public code() {
290 | return this.formattedCode;
291 | }
292 | }
293 |
294 | export default class JSONataDocumentFormatter implements DocumentFormattingEditProvider {
295 | // eslint-disable-next-line class-methods-use-this
296 | provideDocumentFormattingEdits(
297 | document: TextDocument,
298 | // options: FormattingOptions,
299 | // token: CancellationToken,
300 | ): ProviderResult {
301 | try {
302 | const code = document.getText();
303 | const formatted = new Formatter(code).code();
304 | console.log(formatted);
305 |
306 | const edit: TextEdit[] = [];
307 | edit.push(new TextEdit(
308 | new Range(
309 | new Position(0, 0),
310 | new Position(document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length),
311 | ),
312 | formatted,
313 | ));
314 | return edit;
315 | } catch (e: any) {
316 | console.log(e);
317 | // (parser error) don't bubble up as a pot. unhandled thenable promise;
318 | // explicitly return "no change" instead.
319 | // show error message
320 | window.showErrorMessage(`${e.name} (@${e.location.start.line}:${e.location.start.column}): ${e.message}`);
321 | return undefined;
322 | }
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/src/language/functions.ts:
--------------------------------------------------------------------------------
1 | const functions = 'sum,count,max,min,average,string,substring,substringBefore,substringAfter,lowercase,uppercase,length,trim,pad,match,contains,replace,split,join,formatNumber,formatBase,number,floor,ceil,round,abs,sqrt,power,random,boolean,not,map,zip,filter,single,foldLeft,sift,keys,lookup,append,exists,spread,merge,reverse,each,error,assert,type,sort,shuffle,distinct,base64encode,base64decode,encodeUrlComponent,encodeUrl,decodeUrlComponent,decodeUrl';
2 | export default functions;
3 |
--------------------------------------------------------------------------------
/src/notebook/notebookKernel.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import JSONataKernel from '../kernel/kernel';
3 |
4 | const stringify = require('json-stringify-safe');
5 |
6 | // const MIME_TYPE = "x-application/jsonata";
7 |
8 | export default class NotebookKernel implements vscode.Disposable {
9 | readonly id = 'jsonata-book-kernel';
10 |
11 | readonly notebookType = 'jsonata-book';
12 |
13 | readonly label = 'JSONata Book';
14 |
15 | readonly supportedLanguages = ['jsonata'];
16 |
17 | private readonly controller: vscode.NotebookController;
18 |
19 | private executionOrder = 0;
20 |
21 | private kernel: JSONataKernel;
22 |
23 | constructor() {
24 | this.kernel = new JSONataKernel();
25 | this.controller = vscode.notebooks.createNotebookController(
26 | this.id,
27 | this.notebookType,
28 | this.label,
29 | );
30 |
31 | this.controller.supportedLanguages = this.supportedLanguages;
32 | this.controller.supportsExecutionOrder = false;
33 | this.controller.description = 'A notebook for making JSONata queries.';
34 | this.controller.executeHandler = this.executeAll.bind(this);
35 | }
36 |
37 | dispose(): void {
38 | this.controller.dispose();
39 | }
40 |
41 | public async restartKernel() {
42 | await vscode.commands.executeCommand('notebook.clearAllCellsOutputs');
43 | this.kernel.restart();
44 | }
45 |
46 | private executeAll(
47 | cells: vscode.NotebookCell[],
48 | // eslint-disable-next-line no-unused-vars
49 | _notebook: vscode.NotebookDocument,
50 | // eslint-disable-next-line no-unused-vars
51 | _controller: vscode.NotebookController,
52 | ): void {
53 | let run = Promise.resolve();
54 | // eslint-disable-next-line no-restricted-syntax
55 | for (const cell of cells) {
56 | run = run.then(() => this.doExecution(cell));
57 | }
58 | }
59 |
60 | private async doExecution(cell: vscode.NotebookCell): Promise {
61 | const execution = this.controller.createNotebookCellExecution(cell);
62 | // eslint-disable-next-line no-plusplus
63 | execution.executionOrder = ++this.executionOrder;
64 | execution.start(Date.now());
65 |
66 | const query = cell.document.getText();
67 | try {
68 | const result = await this.kernel.run(query);
69 | execution.replaceOutput([new vscode.NotebookCellOutput([
70 | vscode.NotebookCellOutputItem.json(result, 'text/x-json'),
71 | ])]);
72 |
73 | execution.end(true, Date.now());
74 | return Promise.resolve();
75 | } catch (e) {
76 | execution.replaceOutput([
77 | new vscode.NotebookCellOutput([
78 | vscode.NotebookCellOutputItem.error({
79 | name: (e instanceof Error && e.name) || 'error',
80 | message: (e instanceof Error && e.message) || stringify(e, undefined, 4),
81 | }),
82 | ]),
83 | ]);
84 | execution.end(false, Date.now());
85 | return Promise.reject();
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/notebook/notebookSerializer.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-classes-per-file */
2 | import * as vscode from 'vscode';
3 |
4 | // NEEDED Declaration to silence errors
5 | declare class TextDecoder {
6 | decode(data: Uint8Array): string;
7 | }
8 |
9 | declare class TextEncoder {
10 | encode(data: string): Uint8Array;
11 | }
12 |
13 | const stringify = require('json-stringify-safe');
14 |
15 | interface RawCellOutput {
16 | mime: string;
17 | value: any;
18 | }
19 | interface RawNotebookCell {
20 | language: string;
21 | value: string;
22 | kind: vscode.NotebookCellKind;
23 | editable?: boolean;
24 | outputs: RawCellOutput[];
25 | }
26 |
27 | export default class NotebookSerializer implements vscode.NotebookSerializer {
28 | // eslint-disable-next-line class-methods-use-this
29 | async deserializeNotebook(
30 | content: Uint8Array,
31 | // eslint-disable-next-line no-unused-vars
32 | _token: vscode.CancellationToken,
33 | ): Promise {
34 | const contents = new TextDecoder().decode(content); // convert to String to make JSON object
35 |
36 | // Read file contents
37 | let raw: RawNotebookCell[];
38 | try {
39 | raw = JSON.parse(contents);
40 | } catch {
41 | raw = [];
42 | }
43 |
44 | function convertRawOutputToBytes(cell: RawNotebookCell) {
45 | const result: vscode.NotebookCellOutputItem[] = [];
46 |
47 | // eslint-disable-next-line no-restricted-syntax
48 | for (const output of cell.outputs) {
49 | const data = new TextEncoder().encode(stringify(output.value));
50 | result.push(new vscode.NotebookCellOutputItem(data, output.mime));
51 | }
52 |
53 | return result;
54 | }
55 |
56 | // Create array of Notebook cells for the VS Code API from file contents
57 | const cells = raw.map((item) => new vscode.NotebookCellData(
58 | item.kind,
59 | item.value,
60 | item.language,
61 | ));
62 |
63 | for (let i = 0; i < cells.length; i += 1) {
64 | const cell = cells[i];
65 | cell.outputs = [];
66 | if (raw[i].outputs) {
67 | cell.outputs = [new vscode.NotebookCellOutput(convertRawOutputToBytes(raw[i]))];
68 | }
69 | }
70 |
71 | // Pass read and formatted Notebook Data to VS Code to display Notebook with saved cells
72 | return new vscode.NotebookData(
73 | cells,
74 | );
75 | }
76 |
77 | // eslint-disable-next-line class-methods-use-this
78 | async serializeNotebook(
79 | data: vscode.NotebookData,
80 | // eslint-disable-next-line no-unused-vars
81 | _token: vscode.CancellationToken,
82 | ): Promise {
83 | // function to take output renderer data to a format to save to the file
84 | function asRawOutput(cell: vscode.NotebookCellData): RawCellOutput[] {
85 | const result: RawCellOutput[] = [];
86 | // eslint-disable-next-line no-restricted-syntax
87 | for (const output of cell.outputs ?? []) {
88 | // eslint-disable-next-line no-restricted-syntax
89 | for (const item of output.items) {
90 | let outputContents = '';
91 | outputContents = new TextDecoder().decode(item.data);
92 |
93 | try {
94 | const outputData = JSON.parse(outputContents);
95 | result.push({ mime: item.mime, value: outputData });
96 | } catch {
97 | result.push({ mime: item.mime, value: outputContents });
98 | }
99 | }
100 | }
101 | return result;
102 | }
103 |
104 | // Map the Notebook data into the format we want to save the Notebook data as
105 |
106 | const contents: RawNotebookCell[] = [];
107 |
108 | // eslint-disable-next-line no-restricted-syntax
109 | for (const cell of data.cells) {
110 | contents.push({
111 | kind: cell.kind,
112 | language: cell.languageId,
113 | value: cell.value,
114 | outputs: asRawOutput(cell),
115 | });
116 | }
117 |
118 | // Give a string of all the data to save and VS Code will handle the rest
119 | return new TextEncoder().encode(stringify(contents));
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/syntaxes/jsonata-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
3 | "comments": {
4 | // symbol used for single line comment. Remove this entry if your language does not support line comments
5 | "lineComment": "//",
6 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments
7 | "blockComment": [
8 | "/*",
9 | "*/"
10 | ]
11 | },
12 | "brackets": [
13 | [
14 | "(",
15 | ")"
16 | ],
17 | [
18 | "[",
19 | "]"
20 | ],
21 | [
22 | "{",
23 | "}"
24 | ]
25 | ],
26 | "autoClosingPairs": [
27 | {
28 | "open": "(",
29 | "close": ")"
30 | },
31 | {
32 | "open": "[",
33 | "close": "]"
34 | },
35 | {
36 | "open": "{",
37 | "close": "}"
38 | },
39 | {
40 | "open": "\"",
41 | "close": "\""
42 | },
43 | {
44 | "open": "'",
45 | "close": "'"
46 | },
47 | {
48 | "open": "`",
49 | "close": "`"
50 | }
51 | ],
52 | "surroundingPairs": [
53 | {
54 | "open": "(",
55 | "close": ")"
56 | },
57 | {
58 | "open": "[",
59 | "close": "]"
60 | },
61 | {
62 | "open": "{",
63 | "close": "}"
64 | },
65 | {
66 | "open": "\"",
67 | "close": "\""
68 | },
69 | {
70 | "open": "'",
71 | "close": "'"
72 | },
73 | {
74 | "open": "`",
75 | "close": "`"
76 | }
77 | ],
78 | "indentationRules": {
79 | "decreaseIndentPattern": "/^((?!.*?\\/\\*).*\\*\\/)?\\s*[}\\])].*$/",
80 | "increaseIndentPattern": "/^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$/"
81 | },
82 | "insertSpaces": true,
83 | "tabSize": 2
84 | }
--------------------------------------------------------------------------------
/syntaxes/jsonata.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
3 | "scopeName": "source.jsonata",
4 | "foldingStartMarker": "\\{\\s*$",
5 | "foldingStopMarker": "^\\s*\\}",
6 | "patterns": [
7 | {
8 | "include": "#value"
9 | }
10 | ],
11 | "repository": {
12 | "expression": {
13 | "name": "meta.selector.jsonata",
14 | "patterns": [
15 | {
16 | "match": "(\\$|\\.)",
17 | "name": "keyword.other.selector.jsonata"
18 | },
19 | {
20 | "match": "(\\+|-|\\*|/|&)",
21 | "name": "keyword.other.math.jsonata"
22 | }
23 | ]
24 | },
25 | "array": {
26 | "begin": "\\[",
27 | "beginCaptures": {
28 | "0": {
29 | "name": "punctuation.definition.array.begin.jsonata"
30 | }
31 | },
32 | "end": "\\]",
33 | "endCaptures": {
34 | "0": {
35 | "name": "punctuation.definition.array.end.jsonata"
36 | }
37 | },
38 | "name": "meta.structure.array.jsonata",
39 | "patterns": [
40 | {
41 | "include": "#value"
42 | },
43 | {
44 | "match": ",",
45 | "name": "punctuation.separator.array.jsonata"
46 | },
47 | {
48 | "match": "[^\\s\\]]",
49 | "name": "invalid.illegal.expected-array-separator.jsonata"
50 | }
51 | ]
52 | },
53 | "comments": {
54 | "patterns": [
55 | {
56 | "begin": "/\\*\\*(?!/)",
57 | "captures": {
58 | "0": {
59 | "name": "punctuation.definition.comment.jsonata"
60 | }
61 | },
62 | "end": "\\*/",
63 | "name": "comment.block.documentation.jsonata"
64 | },
65 | {
66 | "begin": "/\\*",
67 | "captures": {
68 | "0": {
69 | "name": "punctuation.definition.comment.jsonata"
70 | }
71 | },
72 | "end": "\\*/",
73 | "name": "comment.block.jsonata"
74 | },
75 | {
76 | "captures": {
77 | "1": {
78 | "name": "punctuation.definition.comment.jsonata"
79 | }
80 | },
81 | "match": "(//).*$\\n?",
82 | "name": "comment.line.double-slash.js"
83 | }
84 | ]
85 | },
86 | "constant": {
87 | "match": "\\b(?:true|false|null)\\b",
88 | "name": "constant.language.jsonata"
89 | },
90 | "number": {
91 | "match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional",
92 | "name": "constant.numeric.jsonata"
93 | },
94 | "object": {
95 | "begin": "\\{",
96 | "beginCaptures": {
97 | "0": {
98 | "name": "punctuation.definition.dictionary.begin.jsonata"
99 | }
100 | },
101 | "end": "\\}",
102 | "endCaptures": {
103 | "0": {
104 | "name": "punctuation.definition.dictionary.end.jsonata"
105 | }
106 | },
107 | "name": "meta.structure.dictionary.jsonata",
108 | "patterns": [
109 | {
110 | "comment": "the JSON object key",
111 | "include": "#objectkey"
112 | },
113 | {
114 | "include": "#comments"
115 | },
116 | {
117 | "begin": ":",
118 | "beginCaptures": {
119 | "0": {
120 | "name": "punctuation.separator.dictionary.key-value.jsonata"
121 | }
122 | },
123 | "end": "(,)|(?=\\})",
124 | "endCaptures": {
125 | "1": {
126 | "name": "punctuation.separator.dictionary.pair.jsonata"
127 | }
128 | },
129 | "name": "meta.structure.dictionary.value.jsonata",
130 | "patterns": [
131 | {
132 | "comment": "the JSON object value",
133 | "include": "#value"
134 | }
135 | ]
136 | },
137 | {
138 | "match": "[^\\s\\}]",
139 | "name": "invalid.illegal.expected-dictionary-separator.jsonata"
140 | }
141 | ]
142 | },
143 | "string": {
144 | "begin": "\"",
145 | "beginCaptures": {
146 | "0": {
147 | "name": "punctuation.definition.string.begin.jsonata"
148 | }
149 | },
150 | "end": "\"",
151 | "endCaptures": {
152 | "0": {
153 | "name": "punctuation.definition.string.end.jsonata"
154 | }
155 | },
156 | "name": "string.quoted.double.jsonata",
157 | "patterns": [
158 | {
159 | "include": "#stringcontent"
160 | }
161 | ]
162 | },
163 | "objectkey": {
164 | "begin": "\"",
165 | "beginCaptures": {
166 | "0": {
167 | "name": "punctuation.support.type.property-name.begin.jsonata"
168 | }
169 | },
170 | "end": "\"",
171 | "endCaptures": {
172 | "0": {
173 | "name": "punctuation.support.type.property-name.end.jsonata"
174 | }
175 | },
176 | "name": "string.jsonata support.type.property-name.jsonata",
177 | "patterns": [
178 | {
179 | "include": "#stringcontent"
180 | },
181 | {
182 | "include": "#expression"
183 | }
184 | ]
185 | },
186 | "stringcontent": {
187 | "patterns": [
188 | {
189 | "match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits",
190 | "name": "constant.character.escape.jsonata"
191 | },
192 | {
193 | "match": "\\\\.",
194 | "name": "invalid.illegal.unrecognized-string-escape.jsonata"
195 | }
196 | ]
197 | },
198 | "value": {
199 | "patterns": [
200 | {
201 | "include": "#comments"
202 | },
203 | {
204 | "include": "#constant"
205 | },
206 | {
207 | "include": "#number"
208 | },
209 | {
210 | "include": "#string"
211 | },
212 | {
213 | "include": "#array"
214 | },
215 | {
216 | "include": "#object"
217 | },
218 | {
219 | "include": "#expression"
220 | }
221 | ]
222 | }
223 | }
224 | }
--------------------------------------------------------------------------------
/test/cos.jsonata:
--------------------------------------------------------------------------------
1 | $cos := function($x){ /* Derive cosine by expanding Maclaurin series */
2 | $x > $pi ? $cos($x - 2 * $pi) : $x < -$pi ? $cos($x + 2 * $pi) :
3 | $sum([0..12].($power(-1, $) * $power($x, 2*$) / $factorial(2*$)))
4 | }
--------------------------------------------------------------------------------
/test/order.jsonata:
--------------------------------------------------------------------------------
1 | Order[OrderID = "order104"].Product.{
2 | "Account": $AccName(),
3 | "SKU-" & $string(ProductID): $.`Product Name`
4 | }
--------------------------------------------------------------------------------
/test/packagejson.jsonata-book:
--------------------------------------------------------------------------------
1 | [{"kind":2,"language":"jsonata","value":"$loadFile(\"../package.json\")","outputs":[{"mime":"text/x-json","value":{"name":"vscode-language-jsonata","displayName":"VSCode Language JSONata","description":"Language support for JSONata","version":"1.0.0","engines":{"vscode":"^1.64.0"},"repository":{"url":"https://github.com/bigbug/vscode-language-jsonata"},"author":{"name":"@bigbug","url":"https://github.com/bigbug"},"publisher":"bigbug","categories":["Other"],"activationEvents":["onNotebook:jsonata-book","onLanguage:jsonata"],"main":"./dist/extension.js","browser":"./dist/extension.js","contributes":{"languages":[{"id":"jsonata","extensions":[".jsonata"],"configuration":"./syntaxes/jsonata-configuration.json"}],"grammars":[{"language":"jsonata","scopeName":"source.jsonata","path":"./syntaxes/jsonata.tmLanguage.json"}],"notebooks":[{"id":"jsonata-book","type":"jsonata-book","displayName":"JSONata Book","selector":[{"filenamePattern":"*.jsonata-book"}]}]},"scripts":{"vscode:prepublish":"npm run package","compile":"webpack","watch":"webpack --watch","open-in-browser":"vscode-test-web --extensionDevelopmentPath=. .","package":"webpack --mode production --devtool hidden-source-map","test-compile":"tsc -p . --outDir out","pretest":"npm run test-compile && npm run compile && npm run lint","lint":"eslint src --ext ts","test":"node ./out/test/runTest.js"},"devDependencies":{"@types/glob":"^7.1.4","@types/jsonata":"^1.5.1","@types/mocha":"^9.0.0","@types/node":"14.x","@types/vscode":"^1.64.0","@typescript-eslint/eslint-plugin":"^4.31.1","@typescript-eslint/parser":"^4.31.1","@vscode/test-electron":"^1.6.2","@vscode/test-web":"*","eslint":"^7.32.0","glob":"^7.1.7","mocha":"^9.1.1","ts-loader":"^9.2.5","typescript":"^4.4.3","webpack":"^5.52.1","webpack-cli":"^4.8.0"},"dependencies":{"@types/lodash":"^4.14.178","json-stringify-safe":"^5.0.1","jsonata":"^2.0.3","lodash":"^4.17.21","node-fetch":"^3.3.2","vscode-uri":"^3.0.3","xml2js":"^0.4.23"}}}]},{"kind":2,"language":"jsonata","value":"**.vscode","outputs":[{"mime":"text/x-json","value":"^1.64.0"}]},{"kind":2,"language":"jsonata","value":"$","outputs":[{"mime":"text/x-json","value":{"name":"vscode-language-jsonata","displayName":"VSCode Language JSONata","description":"Language support for JSONata","version":"1.0.0","engines":{"vscode":"^1.64.0"},"repository":{"url":"https://github.com/bigbug/vscode-language-jsonata"},"author":{"name":"@bigbug","url":"https://github.com/bigbug"},"publisher":"bigbug","categories":["Other"],"activationEvents":["onNotebook:jsonata-book","onLanguage:jsonata"],"main":"./dist/extension.js","browser":"./dist/extension.js","contributes":{"languages":[{"id":"jsonata","extensions":[".jsonata"],"configuration":"./syntaxes/jsonata-configuration.json"}],"grammars":[{"language":"jsonata","scopeName":"source.jsonata","path":"./syntaxes/jsonata.tmLanguage.json"}],"notebooks":[{"id":"jsonata-book","type":"jsonata-book","displayName":"JSONata Book","selector":[{"filenamePattern":"*.jsonata-book"}]}]},"scripts":{"vscode:prepublish":"npm run package","compile":"webpack","watch":"webpack --watch","open-in-browser":"vscode-test-web --extensionDevelopmentPath=. .","package":"webpack --mode production --devtool hidden-source-map","test-compile":"tsc -p . --outDir out","pretest":"npm run test-compile && npm run compile && npm run lint","lint":"eslint src --ext ts","test":"node ./out/test/runTest.js"},"devDependencies":{"@types/glob":"^7.1.4","@types/jsonata":"^1.5.1","@types/mocha":"^9.0.0","@types/node":"14.x","@types/vscode":"^1.64.0","@typescript-eslint/eslint-plugin":"^4.31.1","@typescript-eslint/parser":"^4.31.1","@vscode/test-electron":"^1.6.2","@vscode/test-web":"*","eslint":"^7.32.0","glob":"^7.1.7","mocha":"^9.1.1","ts-loader":"^9.2.5","typescript":"^4.4.3","webpack":"^5.52.1","webpack-cli":"^4.8.0"},"dependencies":{"@types/lodash":"^4.14.178","json-stringify-safe":"^5.0.1","jsonata":"^2.0.3","lodash":"^4.17.21","node-fetch":"^3.3.2","vscode-uri":"^3.0.3","xml2js":"^0.4.23"}}}]},{"kind":2,"language":"jsonata","value":"$.author","outputs":[{"mime":"text/x-json","value":{"name":"@bigbug","url":"https://github.com/bigbug"}}]},{"kind":2,"language":"jsonata","value":"$ans.name","outputs":[{"mime":"text/x-json","value":"@bigbug"}]},{"kind":2,"language":"jsonata","value":"$eval('{\n \"hello\": function() {\n \"world\"\n },\n \"echo\": function($t) {\n \"echo \" & $t\n }\n}')","outputs":[{"mime":"text/x-json","value":{"hello":{"_jsonata_lambda":true,"input":{},"environment":{"timestamp":"2023-12-26T23:08:57.126Z","async":false,"isParallelCall":true,"global":{"ancestry":[null]}},"arguments":[],"body":{"value":"world","type":"string","position":37}},"echo":{"_jsonata_lambda":true,"input":{},"environment":{"timestamp":"2023-12-26T23:08:57.126Z","async":false,"isParallelCall":true,"global":{"ancestry":[null]}},"arguments":[{"value":"t","type":"variable","position":64}],"body":{"type":"binary","value":"&","position":81,"lhs":{"value":"echo ","type":"string","position":79},"rhs":{"value":"t","type":"variable","position":84}}}}}]},{"kind":2,"language":"jsonata","value":"$test := $import(\"test.jsonata\")","outputs":[{"mime":"text/x-json","value":{"hello":{"_jsonata_lambda":true,"input":{},"environment":{"timestamp":"2023-12-26T23:08:57.297Z","async":false,"isParallelCall":false,"global":{"ancestry":[null]}},"arguments":[],"body":{"type":"binary","value":"&","position":45,"lhs":{"type":"binary","value":"&","position":39,"lhs":{"value":"hello","type":"string","position":37},"rhs":{"value":" ","type":"string","position":43}},"rhs":{"value":"world","type":"string","position":53}}}}}]},{"kind":2,"language":"jsonata","value":"$test.hello(\"Test\")","outputs":[{"mime":"text/x-json","value":"hello world"}]},{"kind":2,"language":"jsonata","value":"(\n $abc := $loadUrl(\"https://raw.githubusercontent.com/jsonata-js/jsonata/master/package.json\").repository;\n $writeFile(\"githubdata.json.tmp\", $abc)\n)","outputs":[{"mime":"text/x-json","value":"undefined"}]}]
--------------------------------------------------------------------------------
/test/selector.jsonata:
--------------------------------------------------------------------------------
1 | Account.Order.Product{
2 | `Product Name`: {
3 | "Price": Price,
4 | "Qty": Quantity
5 | }
6 | }
--------------------------------------------------------------------------------
/test/test.jsonata:
--------------------------------------------------------------------------------
1 | {
2 | "hello": function() {
3 | (
4 | "hello" & " " & "world";
5 | %.%.a.def;
6 | [
7 | Address,
8 | Other.Alternative.Address
9 | ].City;
10 | Product.(Price * Quantity);
11 | Account.Order.Product {
12 | `Product Name`: {"Price": Price, "Qty": Quantity}
13 | };
14 | $sum(Account.Order.Product.(Price*Quantity));
15 | $cos := function($x){ /* Derive cosine by expanding Maclaurin series */
16 | $x > $pi ? $cos($x - 2 * $pi) : $x < -$pi ? $cos($x + 2 * $pi) :
17 | $sum([0..12].($power(-1, $) * $power($x, 2*$) / $factorial(2*$)))
18 | };
19 | $twice := function($f) { function($x){ $f($f($x)) } };
20 | Account.(
21 | $AccName := function() { $.'Account Name' };
22 |
23 | Order[OrderID = 'order104'].Product.{
24 | 'Account': $AccName(),
25 | 'SKU-' & $string(ProductID): $.'Product Name'
26 | }
27 | );
28 | $matcher := /[a-z]*an[a-z]*/i;
29 | library.loans@$l.books@$b[$l.isbn=$b.isbn].{
30 | 'title': $b.title,
31 | 'customer': $l.customer
32 | };
33 | [1..$count(Items)].("Item " & $);
34 | "world" in ["hello", "world"];
35 | library.books[price < 10 or section="diy"].title;
36 | Customer.Email ~> $substringAfter("@") ~> $substringBefore(".") ~> $uppercase();
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2020",
5 | "lib": [
6 | "ES2020"
7 | ],
8 | "rootDir": "src",
9 | "strict": true /* enable all strict type-checking options */
10 | /* Additional Checks */
11 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
12 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
13 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
14 | },
15 | "exclude": [
16 | "node_modules",
17 | ".vscode-test"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 |
3 | 'use strict';
4 |
5 | const path = require('path');
6 |
7 | //@ts-check
8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/
9 |
10 | /** @type WebpackConfig */
11 | const extensionConfig = {
12 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
14 |
15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
16 | output: {
17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
18 | path: path.resolve(__dirname, 'dist'),
19 | filename: 'extension.js',
20 | libraryTarget: 'commonjs2'
21 | },
22 | externals: {
23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
24 | // modules added here also need to be added in the .vsceignore file
25 | },
26 | resolve: {
27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
28 | extensions: ['.ts', '.js']
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.ts$/,
34 | exclude: /node_modules/,
35 | use: [
36 | {
37 | loader: 'ts-loader'
38 | }
39 | ]
40 | }
41 | ]
42 | },
43 | devtool: 'nosources-source-map'
44 | };
45 | module.exports = [ extensionConfig ];
--------------------------------------------------------------------------------