├── .ignore ├── docs └── images │ ├── logo.png │ ├── screen_shot01.gif │ ├── screen_shot02.gif │ └── logo.svg ├── commitlint.config.js ├── scripts └── e2e.sh ├── .prettierrc.js ├── .gitignore ├── .vscode ├── settings.json ├── extensions.json ├── tasks.json └── launch.json ├── .pre-commit-config.yaml ├── client ├── tsconfig.json ├── package.json ├── src │ ├── test │ │ ├── runTest.ts │ │ ├── index.ts │ │ ├── helper.ts │ │ ├── diagnostics.test.ts │ │ └── completion.test.ts │ └── extension.ts ├── testFixture │ └── junos.conf └── package-lock.json ├── server ├── tsconfig.json ├── package.json ├── src │ ├── junos.d.ts │ ├── zone-address-book.ts │ ├── ip.ts │ ├── session.ts │ ├── server.ts │ ├── completion.ts │ ├── parser.ts │ ├── validation.ts │ └── definition.ts └── package-lock.json ├── tsconfig.json ├── language-configuration.json ├── .vscodeignore ├── eslint.config.mjs ├── LICENSE.txt ├── README.md ├── package.json ├── CHANGELOG.md └── syntaxes └── junos.tmGrammar.json /.ignore: -------------------------------------------------------------------------------- 1 | /server/src/junos.js 2 | 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeout/vscode-junos/HEAD/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/screen_shot01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeout/vscode-junos/HEAD/docs/images/screen_shot01.gif -------------------------------------------------------------------------------- /docs/images/screen_shot02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeout/vscode-junos/HEAD/docs/images/screen_shot02.gif -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | extends: ["@commitlint/config-conventional"], 4 | rules: { 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /scripts/e2e.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export CODE_TESTS_PATH="$(pwd)/client/out/test" 4 | export CODE_TESTS_WORKSPACE="$(pwd)/client/testFixture" 5 | 6 | node "$(pwd)/client/out/test/runTest" -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | semi: true, 4 | trailingComma: "all", 5 | singleQuote: false, 6 | printWidth: 120, 7 | tabWidth: 2, 8 | useTabs: false, 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | client/server 4 | .vscode-test 5 | 6 | # typescript 7 | *.tsbuildinfo 8 | 9 | # editors 10 | .idea 11 | 12 | # packaging 13 | /others 14 | /pkgs 15 | 16 | # assets 17 | /docs/images/mov 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.insertSpaces": false, 3 | "tslint.enable": true, 4 | "typescript.tsc.autoDetect": "off", 5 | "typescript.preferences.quoteStyle": "single", 6 | "editor.codeActionsOnSave": { 7 | "source.fixAll.eslint": "explicit" 8 | } 9 | } -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook 3 | rev: v9.13.0 4 | hooks: 5 | - id: commitlint 6 | stages: [commit-msg] 7 | additional_dependencies: ['@commitlint/config-conventional'] 8 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "outDir": "out", 7 | "rootDir": "src", 8 | "sourceMap": true 9 | }, 10 | "include": ["src"], 11 | "exclude": ["node_modules", ".vscode-test"] 12 | } 13 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["es2020"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "strict": true, 9 | "outDir": "out", 10 | "rootDir": "src" 11 | }, 12 | "include": ["src"], 13 | "exclude": ["node_modules", ".vscode-test"] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint" 8 | ] 9 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": ["es2020"], 6 | "outDir": "out", 7 | "rootDir": "src", 8 | "sourceMap": true 9 | }, 10 | "include": [ 11 | "src" 12 | ], 13 | "exclude": [ 14 | "node_modules", 15 | ".vscode-test" 16 | ], 17 | "references": [ 18 | { "path": "./client" }, 19 | { "path": "./server" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "junos-lsp-server", 3 | "description": "Language server for Junos configuration", 4 | "author": "Shintaro Kojima", 5 | "license": "MIT", 6 | "version": "0.5.2", 7 | "engines": { 8 | "node": "*" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/codeout/vscode-junos" 13 | }, 14 | "dependencies": { 15 | "vscode-languageserver": "^9.0.1", 16 | "vscode-languageserver-textdocument": "^1.0.12" 17 | }, 18 | "scripts": {} 19 | } 20 | -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "#", 4 | "blockComment": [ "/*", "*/" ] 5 | }, 6 | "brackets": [ 7 | ["{", "}"], 8 | ["[", "]"], 9 | ["(", ")"] 10 | ], 11 | "autoClosingPairs": [ 12 | ["{", "}"], 13 | ["[", "]"], 14 | ["(", ")"], 15 | ["\"", "\""], 16 | ["'", "'"] 17 | ], 18 | "surroundingPairs": [ 19 | ["{", "}"], 20 | ["[", "]"], 21 | ["(", ")"], 22 | ["\"", "\""], 23 | ["'", "'"] 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /server/src/junos.d.ts: -------------------------------------------------------------------------------- 1 | // Only the "set groups" has the value false, like: 2 | // "groups(arg) | Configuration groups": opts?.groups !== false && { ... } 3 | type SchemaObject = { [key: string]: SchemaObject | null | false }; 4 | 5 | export class Enumeration { 6 | list: string[]; 7 | } 8 | 9 | export class Repeatable { 10 | list: SchemaObject; 11 | } 12 | 13 | export class Sequence { 14 | list: SchemaObject[]; 15 | 16 | get(depth: number): SchemaObject; 17 | } 18 | 19 | export class JunosSchema { 20 | configuration(): SchemaObject; 21 | } 22 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "junos-lsp-client", 3 | "description": "VSCode part of a language server", 4 | "author": "Shintaro Kojima", 5 | "license": "MIT", 6 | "version": "0.5.2", 7 | "publisher": "vscode", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/codeout/vscode-junos" 11 | }, 12 | "engines": { 13 | "vscode": "^1.75.0" 14 | }, 15 | "dependencies": { 16 | "glob": "^11.0.3", 17 | "vscode-languageclient": "^9.0.1" 18 | }, 19 | "devDependencies": { 20 | "@types/vscode": "^1.104.0", 21 | "@vscode/test-electron": "^2.5.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": [ 13 | "$tsc" 14 | ] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "watch", 19 | "isBackground": true, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "presentation": { 25 | "panel": "dedicated", 26 | "reveal": "never" 27 | }, 28 | "problemMatcher": [ 29 | "$tsc-watch" 30 | ] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | **/*.ts 3 | **/*.map 4 | .gitignore 5 | **/tsconfig.json 6 | **/tsconfig.base.json 7 | **/tsconfig.tsbuildinfo 8 | contributing.md 9 | .travis.yml 10 | client/node_modules/** 11 | !client/node_modules/vscode-jsonrpc/** 12 | !client/node_modules/vscode-languageclient/** 13 | !client/node_modules/vscode-languageserver-protocol/** 14 | !client/node_modules/vscode-languageserver-types/** 15 | !client/node_modules/{minimatch,brace-expansion,concat-map,balanced-match}/** 16 | !client/node_modules/{semver,lru-cache,yallist}/** 17 | 18 | # docs 19 | docs/images/ 20 | !docs/images/logo.png 21 | 22 | # development 23 | commitlint.config.js 24 | eslint.config.mjs 25 | .gitmodules 26 | .idea/** 27 | .ignore 28 | .pre-commit-config.yaml 29 | *.js 30 | *.conf 31 | others/** 32 | pkgs/** 33 | -------------------------------------------------------------------------------- /client/src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import {runTests} from "@vscode/test-electron"; 2 | import * as path from "path"; 3 | 4 | async function main() { 5 | try { 6 | // The folder containing the Extension Manifest package.json 7 | // Passed to `--extensionDevelopmentPath` 8 | const extensionDevelopmentPath = path.resolve(__dirname, "../../../"); 9 | 10 | // The path to test runner 11 | // Passed to --extensionTestsPath 12 | const extensionTestsPath = path.resolve(__dirname, "./index"); 13 | 14 | // Download VS Code, unzip it and run the integration test 15 | await runTests({extensionDevelopmentPath, extensionTestsPath}); 16 | } catch { 17 | console.error("Failed to run tests"); 18 | process.exit(1); 19 | } 20 | } 21 | 22 | main(); 23 | -------------------------------------------------------------------------------- /server/src/zone-address-book.ts: -------------------------------------------------------------------------------- 1 | export class ZoneAddressBookStore { 2 | private readonly store: { 3 | [uri: string]: { 4 | [logicalSystem: string]: { 5 | [zone: string]: Set; 6 | }; 7 | }; 8 | }; 9 | 10 | constructor() { 11 | this.store = {}; 12 | } 13 | 14 | set(uri: string, logicalSystem: string, zone: string, addressBook: string) { 15 | // initialize 16 | this.store[uri] ||= {}; 17 | this.store[uri][logicalSystem] ||= {}; 18 | this.store[uri][logicalSystem][zone] ||= new Set(); 19 | 20 | this.store[uri][logicalSystem][zone].add(addressBook); 21 | } 22 | 23 | get(uri: string, logicalSystem: string, zone: string) { 24 | return this.store[uri]?.[logicalSystem]?.[zone] || new Set(["global"]); 25 | } 26 | 27 | clear(uri: string) { 28 | if (!this.store[uri]) { 29 | return; 30 | } 31 | 32 | this.store[uri] = {}; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/src/test/index.ts: -------------------------------------------------------------------------------- 1 | import { glob } from "glob"; 2 | import * as Mocha from "mocha"; 3 | import * as path from "path"; 4 | 5 | export function run() { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: "tdd", 9 | color: true, 10 | }); 11 | mocha.timeout(100000); 12 | 13 | const testsRoot = __dirname; 14 | 15 | return glob.glob("**.test.js", { cwd: testsRoot }).then(async (files) => { 16 | // Add files to the test suite 17 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 18 | 19 | try { 20 | // Run the mocha test 21 | await new Promise((resolve, reject) => { 22 | mocha.run((failures) => { 23 | if (failures > 0) { 24 | reject(`${failures} tests failed.`); 25 | } else { 26 | resolve(); 27 | } 28 | }); 29 | }); 30 | } catch (err) { 31 | console.error(err); 32 | throw err; 33 | } 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /server/src/ip.ts: -------------------------------------------------------------------------------- 1 | export function isIpv4Prefix(prefix?: string) { 2 | return !!prefix?.match( 3 | /^(?:(?:0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?)\.){3}(?:0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?)(?:\/(?:[0-2]?\d|3[0-2]))?$/, 4 | ); 5 | } 6 | 7 | export function isIpv6Prefix(prefix?: string) { 8 | return !!prefix?.match( 9 | /^(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?|(?:[0-9A-Fa-f]{1,4}:){6,6}(\d+)\.(\d+)\.(\d+)\.(\d+)|(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::((?:[0-9A-Fa-f]{1,4}:)*)(\d+)\.(\d+)\.(\d+)\.(\d+)|[Ff][Ee]80(?::[0-9A-Fa-f]{1,4}){7}%[-0-9A-Za-z._~]+|[Ff][Ee]80:(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?|:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)?:[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+)(?:\/(?:[0-9]|[1-9]\d|1[01]\d|12[0-8]))?$/, 10 | ); 11 | } 12 | 13 | export function isIpPrefix(prefix?: string) { 14 | return isIpv4Prefix(prefix) || isIpv6Prefix(prefix); 15 | } 16 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import prettierPlugin from "eslint-config-prettier"; 3 | import simpleImportSortPlugin from "eslint-plugin-simple-import-sort"; 4 | import tsEslint from "typescript-eslint"; 5 | 6 | 7 | /** @type {import("eslint").Linter.FlatConfig[]} */ 8 | export default tsEslint.config( 9 | eslint.configs.recommended, 10 | ...tsEslint.configs.recommended, 11 | prettierPlugin, 12 | { 13 | plugins: { 14 | "simple-import-sort": simpleImportSortPlugin 15 | }, 16 | rules: { 17 | "@typescript-eslint/consistent-type-assertions": [ 18 | "error", 19 | { 20 | assertionStyle: "as" 21 | } 22 | ], 23 | "simple-import-sort/imports": "error", 24 | "simple-import-sort/exports": "error" 25 | }, 26 | }, 27 | { 28 | ignores: [ 29 | "server/src/junos.js", 30 | ".vscode-test", 31 | "node_modules/**", 32 | "client/node_modules/**", 33 | "client/out/**", 34 | "server/node_modules/**", 35 | "server/out/**", 36 | ] 37 | } 38 | ); 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-2025 Shintaro Kojima 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Junos Extension for Visual Studio Code 2 | 3 | Junos extension for Visual Studio Code is a language server that provides auto-completion, validation, and syntax highlighting for Junos. 4 | 5 | 6 | ## Features 7 | 8 | The language server has the following language features: 9 | 10 | * Completion 11 | * Syntax validation 12 | * Syntax highlighting 13 | 14 | ### Experimental Features 15 | 16 | ⚠️ These features may work, but still under development. The behavior might be changed ⚠️ 17 | 18 | * Go To Definition 19 | * `interface` 20 | * `prefix-list` 21 | * `policy-statement` 22 | * `community` 23 | * `as-path` 24 | * `as-path-group` 25 | * `firewall-filter` 26 | * NAT pool (`source-pool`, `destination-pool`, `dns-alg-pool`, `overload-pool`) 27 | 28 | 29 | ## Screen Shot 30 | 31 | ![Screen Shot](https://raw.githubusercontent.com/codeout/vscode-junos/refs/heads/main/docs/images/screen_shot01.gif) 32 | ![Screen Shot](https://raw.githubusercontent.com/codeout/vscode-junos/refs/heads/main/docs/images/screen_shot02.gif) 33 | 34 | 35 | ## Copyright and License 36 | 37 | Copyright (c) 2019-2025 Shintaro Kojima. Code released under the [MIT license](LICENSE.txt). 38 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "name": "Launch Client", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceRoot}", 12 | "${workspaceRoot}/junos.conf", 13 | "${workspaceRoot}/client/testFixture/junos.conf" 14 | ], 15 | "outFiles": [ 16 | "${workspaceRoot}/client/out/**/*.js", 17 | "${workspaceRoot}/server/out/**/*.js" 18 | ], 19 | "preLaunchTask": { 20 | "type": "npm", 21 | "script": "watch" 22 | } 23 | }, 24 | { 25 | "name": "Language Server E2E Test", 26 | "type": "extensionHost", 27 | "request": "launch", 28 | "runtimeExecutable": "${execPath}", 29 | "args": [ 30 | "--extensionDevelopmentPath=${workspaceRoot}", 31 | "--extensionTestsPath=${workspaceRoot}/client/out/test/index", 32 | "${workspaceRoot}/client/testFixture" 33 | ], 34 | "outFiles": [ 35 | "${workspaceRoot}/client/out/test/**/*.js" 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /server/src/session.ts: -------------------------------------------------------------------------------- 1 | import { _Connection } from "vscode-languageserver/lib/common/server"; // This is probably internal 2 | import { createConnection, ProposedFeatures, TextDocuments } from "vscode-languageserver/node"; 3 | import { TextDocument } from "vscode-languageserver-textdocument"; 4 | 5 | import { DefinitionStore } from "./definition"; 6 | import { createParser, Parser } from "./parser"; 7 | import { ZoneAddressBookStore } from "./zone-address-book"; 8 | 9 | export class Session { 10 | public readonly connection: _Connection; 11 | public readonly documents: TextDocuments; 12 | public readonly parser: Parser; 13 | public readonly definitions: DefinitionStore; 14 | public readonly zoneAddressBooks: ZoneAddressBookStore; 15 | 16 | constructor() { 17 | this.connection = createConnection(ProposedFeatures.all); 18 | this.documents = new TextDocuments(TextDocument); 19 | this.parser = createParser(); 20 | this.definitions = new DefinitionStore(); 21 | this.zoneAddressBooks = new ZoneAddressBookStore(); 22 | } 23 | 24 | listen() { 25 | this.documents.listen(this.connection); 26 | this.connection.listen(); 27 | } 28 | 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 30 | log(...obj: any[]) { 31 | this.connection.console.log(JSON.stringify(obj)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/test/helper.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as vscode from "vscode"; 3 | 4 | export let doc: vscode.TextDocument; 5 | export let editor: vscode.TextEditor; 6 | export let documentEol: string; 7 | export let platformEol: string; 8 | 9 | /** 10 | * Activates the vscode.lsp-sample extension 11 | */ 12 | export async function activate(docUri: vscode.Uri) { 13 | // The extensionId is `publisher.name` from package.json 14 | const ext = vscode.extensions.getExtension("codeout.vscode-junos")!; 15 | await ext.activate(); 16 | try { 17 | doc = await vscode.workspace.openTextDocument(docUri); 18 | editor = await vscode.window.showTextDocument(doc); 19 | await sleep(2000); // Wait for server activation 20 | } catch (e) { 21 | console.error(e); 22 | } 23 | } 24 | 25 | async function sleep(ms: number) { 26 | return new Promise((resolve) => setTimeout(resolve, ms)); 27 | } 28 | 29 | export const getDocPath = (p: string) => { 30 | return path.resolve(__dirname, "../../testFixture", p); 31 | }; 32 | export const getDocUri = (p: string) => { 33 | return vscode.Uri.file(getDocPath(p)); 34 | }; 35 | 36 | export async function setTestContent(content: string) { 37 | const all = new vscode.Range(doc.positionAt(0), doc.positionAt(doc.getText().length)); 38 | return editor.edit((eb) => eb.replace(all, content)); 39 | } 40 | -------------------------------------------------------------------------------- /client/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import { ExtensionContext, workspace } from "vscode"; 3 | import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from "vscode-languageclient/node"; 4 | 5 | let client: LanguageClient; 6 | 7 | export function activate(context: ExtensionContext) { 8 | // The server is implemented in node 9 | const serverModule = context.asAbsolutePath(path.join("server", "out", "server.js")); 10 | 11 | // If the extension is launched in debug mode then the debug server options are used 12 | // Otherwise the run options are used 13 | const serverOptions: ServerOptions = { 14 | run: { module: serverModule, transport: TransportKind.ipc }, 15 | debug: { 16 | module: serverModule, 17 | transport: TransportKind.ipc, 18 | }, 19 | }; 20 | 21 | // Options to control the language client 22 | const clientOptions: LanguageClientOptions = { 23 | // Register the server for plain text documents 24 | documentSelector: [{ scheme: "file", language: "junos" }], 25 | synchronize: { 26 | // Notify the server about file changes to '.clientrc files contained in the workspace 27 | fileEvents: workspace.createFileSystemWatcher("**/.clientrc"), 28 | }, 29 | }; 30 | 31 | // Create the language client and start the client. 32 | client = new LanguageClient("JunoslanguageServer", "Language Server for Junos", serverOptions, clientOptions); 33 | 34 | // Start the client. This will also launch the server 35 | client.start(); 36 | } 37 | 38 | export function deactivate() { 39 | if (!client) { 40 | return undefined; 41 | } 42 | return client.stop(); 43 | } 44 | -------------------------------------------------------------------------------- /server/src/server.ts: -------------------------------------------------------------------------------- 1 | import { DocumentDiagnosticReport, DocumentDiagnosticReportKind } from "vscode-languageserver"; 2 | import { TextDocumentSyncKind } from "vscode-languageserver/node"; 3 | 4 | import { completion, completionResolve } from "./completion"; 5 | import { definition, updateDefinitions } from "./definition"; 6 | import { Session } from "./session"; 7 | import { validateTextDocument } from "./validation"; 8 | 9 | const session = new Session(); 10 | 11 | session.connection.onInitialize(() => { 12 | return { 13 | capabilities: { 14 | textDocumentSync: TextDocumentSyncKind.Incremental, 15 | completionProvider: { 16 | resolveProvider: true, 17 | triggerCharacters: [" "], 18 | }, 19 | diagnosticProvider: { 20 | interFileDependencies: false, 21 | workspaceDiagnostics: false, 22 | }, 23 | definitionProvider: true, 24 | }, 25 | }; 26 | }); 27 | 28 | session.connection.onCompletion(completion(session)); 29 | session.connection.onCompletionResolve(completionResolve(session)); 30 | session.connection.onDefinition(definition(session)); 31 | 32 | session.connection.languages.diagnostics.on(async (params) => { 33 | const document = session.documents.get(params.textDocument.uri); 34 | if (document !== undefined) { 35 | return { 36 | kind: DocumentDiagnosticReportKind.Full, 37 | items: await validateTextDocument(session, document), 38 | } satisfies DocumentDiagnosticReport; 39 | } else { 40 | // We don't know the document. We can either try to read it from disk 41 | // or we don't report problems for it. 42 | return { 43 | kind: DocumentDiagnosticReportKind.Full, 44 | items: [], 45 | } satisfies DocumentDiagnosticReport; 46 | } 47 | }); 48 | 49 | session.documents.onDidChangeContent((change) => { 50 | updateDefinitions(session, change.document); 51 | validateTextDocument(session, change.document); 52 | }); 53 | 54 | session.listen(); 55 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "junos-lsp-server", 3 | "version": "0.5.2", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "junos-lsp-server", 9 | "version": "0.5.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "vscode-languageserver": "^9.0.1", 13 | "vscode-languageserver-textdocument": "^1.0.12" 14 | }, 15 | "engines": { 16 | "node": "*" 17 | } 18 | }, 19 | "node_modules/vscode-jsonrpc": { 20 | "version": "8.2.0", 21 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", 22 | "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">=14.0.0" 26 | } 27 | }, 28 | "node_modules/vscode-languageserver": { 29 | "version": "9.0.1", 30 | "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", 31 | "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", 32 | "license": "MIT", 33 | "dependencies": { 34 | "vscode-languageserver-protocol": "3.17.5" 35 | }, 36 | "bin": { 37 | "installServerIntoExtension": "bin/installServerIntoExtension" 38 | } 39 | }, 40 | "node_modules/vscode-languageserver-protocol": { 41 | "version": "3.17.5", 42 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", 43 | "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", 44 | "license": "MIT", 45 | "dependencies": { 46 | "vscode-jsonrpc": "8.2.0", 47 | "vscode-languageserver-types": "3.17.5" 48 | } 49 | }, 50 | "node_modules/vscode-languageserver-textdocument": { 51 | "version": "1.0.12", 52 | "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", 53 | "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", 54 | "license": "MIT" 55 | }, 56 | "node_modules/vscode-languageserver-types": { 57 | "version": "3.17.5", 58 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", 59 | "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", 60 | "license": "MIT" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-junos", 3 | "displayName": "Junos", 4 | "description": "Junos for Visual Studio Code", 5 | "author": "Shintaro Kojima", 6 | "license": "MIT", 7 | "version": "0.5.2", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/codeout/vscode-junos" 11 | }, 12 | "publisher": "codeout", 13 | "categories": [ 14 | "Languages", 15 | "Formatters" 16 | ], 17 | "keywords": [ 18 | "junos", 19 | "juniper" 20 | ], 21 | "engines": { 22 | "vscode": "^1.75.0" 23 | }, 24 | "activationEvents": [ 25 | "onLanguage:junos" 26 | ], 27 | "icon": "docs/images/logo.png", 28 | "main": "./client/out/extension", 29 | "contributes": { 30 | "languages": [ 31 | { 32 | "id": "junos", 33 | "aliases": [ 34 | "Junos", 35 | "junos" 36 | ], 37 | "extensions": [ 38 | ".conf", 39 | ".conf.1", 40 | ".conf.2", 41 | ".conf.3", 42 | ".conf.4", 43 | ".conf.5", 44 | ".conf.6", 45 | ".conf.7", 46 | ".conf.8", 47 | ".conf.9", 48 | ".conf.10", 49 | ".conf.11", 50 | ".conf.12", 51 | ".conf.13", 52 | ".conf.14", 53 | ".conf.15", 54 | ".conf.16", 55 | ".conf.17", 56 | ".conf.18", 57 | ".conf.19", 58 | ".conf.20", 59 | ".conf.21", 60 | ".conf.22", 61 | ".conf.23", 62 | ".conf.24", 63 | ".conf.25", 64 | ".conf.26", 65 | ".conf.27", 66 | ".conf.28", 67 | ".conf.29", 68 | ".conf.30", 69 | ".conf.31", 70 | ".conf.32", 71 | ".conf.33", 72 | ".conf.34", 73 | ".conf.35", 74 | ".conf.36", 75 | ".conf.37", 76 | ".conf.38", 77 | ".conf.39", 78 | ".conf.40", 79 | ".conf.41", 80 | ".conf.42", 81 | ".conf.43", 82 | ".conf.44", 83 | ".conf.45", 84 | ".conf.46", 85 | ".conf.47", 86 | ".conf.48", 87 | ".conf.49" 88 | ], 89 | "configuration": "./language-configuration.json" 90 | } 91 | ], 92 | "grammars": [ 93 | { 94 | "language": "junos", 95 | "scopeName": "text.junos", 96 | "path": "./syntaxes/junos.tmGrammar.json" 97 | } 98 | ] 99 | }, 100 | "scripts": { 101 | "vscode:prepublish": "npm run compile", 102 | "compile": "tsc -b", 103 | "watch": "tsc -b -w", 104 | "lint": "npx eslint .", 105 | "type-check": "tsc --pretty --noEmit --project server/tsconfig.json && tsc --pretty --noEmit --project client/tsconfig.json", 106 | "check": "npm run type-check && npm run lint", 107 | "postinstall": "cd client && npm install && cd ../server && npm install && cd ..", 108 | "update": "npm update && cd client && npm update && cd ../server && npm update && cd ..", 109 | "test": "sh ./scripts/e2e.sh", 110 | "clean": "rm -rf node_modules */node_modules */out" 111 | }, 112 | "devDependencies": { 113 | "@types/mocha": "^10.0.10", 114 | "@types/node": "^22.18.8", 115 | "eslint": "^9.36.0", 116 | "eslint-config-prettier": "^10.1.8", 117 | "eslint-plugin-prettier": "^5.5.4", 118 | "eslint-plugin-simple-import-sort": "^12.1.1", 119 | "mocha": "^11.7.4", 120 | "prettier": "3.6.2", 121 | "typescript": "^5.9.3", 122 | "typescript-eslint": "^8.45.0" 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /server/src/completion.ts: -------------------------------------------------------------------------------- 1 | import { CompletionItem, CompletionItemKind, TextDocumentPositionParams } from "vscode-languageserver"; 2 | 3 | import { prefixPattern } from "./parser"; 4 | import { Session } from "./session"; 5 | 6 | export function completion(session: Session) { 7 | return (textDocumentPosition: TextDocumentPositionParams) => { 8 | const uri = textDocumentPosition.textDocument.uri; 9 | const doc = session.documents.get(uri); 10 | if (!doc) { 11 | return []; 12 | } 13 | 14 | let line = doc.getText().split("\n")[textDocumentPosition.position.line]; 15 | if (!line.match(prefixPattern)) { 16 | return []; 17 | } 18 | 19 | line = line.replace(prefixPattern, ""); 20 | const keywords = session.parser.keywords(line); 21 | 22 | let m = line.match(/\s*logical-systems\s+(\S+)/); 23 | const logicalSystem = m?.[1] || "global"; 24 | 25 | // List defined symbols 26 | const rules = [ 27 | ["interface", /\s+interface\s+$/], 28 | ["prefix-list", /\s+from\s+(?:source-|destination-)?prefix-list\s+$/], 29 | ["policy-statement", /\s+(?:import|export)\s+$/], 30 | ["community", /\s+(?:from\s+community|then\s+community\s+(?:add|delete|set))\s+$/], 31 | ["as-path", /\s+from\s+as-path\s+$/], 32 | ["as-path-group", /\s+from\s+as-path-group\s+$/], 33 | ["firewall-filter", /\s+filter\s+(?:input|output|input-list|output-list)\s+$/], 34 | ["service-nat-pool", /\s+then\s+translated\s+(?:source-pool|destination-pool|dns-alg-pool|overload-pool)\s+$/], 35 | ["application", /\s+match\s+application\s+$/, true], 36 | [ 37 | (m) => `security-nat-pool:${m[1]}`, 38 | /\s+security\s+nat\s+(?:source|destination)\s+.*\s+then\s+(source|destination)-nat\s+pool\s+$/, 39 | true, 40 | ], 41 | ["address:global:global", /\s+nat\s+.*\s+match\s+(?:source|destination)-address(?:-name)?\s+$/], 42 | ["address:global:global", /\s+pool\s+\S+\s+address-name\s+$/], 43 | 44 | // global address books 45 | [(m) => `address:global:${m[1]}`, /security\s+address-book\s+(\S+)\s+address-set\s+\S+\s+address\s+$/], 46 | [(m) => `address-set:global:${m[1]}`, /security\s+address-book\s+(\S+)\s+address-set\s+\S+\s+address-set\s+$/], 47 | 48 | // zone-specific address books 49 | [ 50 | (m) => `address:${m[1]}:global`, 51 | /security\s+zones\s+security-zone\s+(\S+)\s+address-book\s+address-set\s+\S+\s+address\s+$/, 52 | ], 53 | [ 54 | (m) => `address-set:${m[1]}:global`, 55 | /security\s+zones\s+security-zone\s+(\S+)\s+address-book\s+address-set\s+\S+\s+address-set\s+$/, 56 | ], 57 | 58 | [ 59 | (m) => { 60 | const zone = m[3] === "source" ? m[1] : m[2]; 61 | const addressBooks = session.zoneAddressBooks.get(uri, logicalSystem, zone); 62 | return [...addressBooks] 63 | .map((a) => [`address:global:${a}`, `address-set:global:${a}`]) 64 | .flat() 65 | .concat([ 66 | "address:global:global", 67 | `address:${zone}:global`, 68 | "address-set:global:global", 69 | `address-set:${zone}:global`, 70 | ]); 71 | }, 72 | /\s+policies\s+from-zone\s+(\S+)\s+to-zone\s+(\S+)\s+.*\s+match\s+(source|destination)-address\s+$/, 73 | ], 74 | ] as Array<[string | ((arg: RegExpMatchArray) => string | string[]), RegExp, boolean]>; 75 | 76 | for (const [symbolType, pattern, keepWord] of rules) { 77 | m = line.match(pattern); 78 | if (m) { 79 | let types = typeof symbolType === "function" ? symbolType(m) : symbolType; 80 | if (!Array.isArray(types)) { 81 | types = [types]; 82 | } 83 | 84 | addReferences( 85 | Object.fromEntries( 86 | types.map((type) => Object.entries(session.definitions.getDefinitions(uri, logicalSystem, type))).flat(), 87 | ), 88 | keywords, 89 | keepWord, 90 | ); 91 | 92 | break; 93 | } 94 | } 95 | 96 | return keywords.map((keyword) => ({ 97 | label: keyword, 98 | kind: keyword === "word" ? CompletionItemKind.Value : CompletionItemKind.Text, 99 | data: `${line} ${keyword}`, 100 | })); 101 | }; 102 | } 103 | 104 | // replace "word" in `keywords` array with all definitions keys 105 | function addReferences(definitions: object, keywords: string[], keepWord = false) { 106 | const index = keywords.indexOf("word"); 107 | if (index < 0) { 108 | return; 109 | } 110 | 111 | if (!keepWord) { 112 | keywords.splice(index, 1); 113 | } 114 | 115 | keywords.unshift(...Object.keys(definitions)); 116 | } 117 | 118 | export function completionResolve(session: Session) { 119 | return (item: CompletionItem) => { 120 | item.detail = session.parser.description(item.data); 121 | return item; 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /docs/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 50 | 51 | 53 | 57 | 65 | junos 76 | > 88 | 92 | 96 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.5.2] - 2025-10-13 2 | 3 | ### Fixed 4 | 5 | * Change the extention logo 6 | 7 | 8 | ## [0.5.1] - 2025-10-02 9 | 10 | ### Added 11 | 12 | * Newly supported syntax 13 | * `chassis high-availability` 14 | 15 | 16 | ## [0.5.0] - 2025-07-14 17 | 18 | ### Added 19 | 20 | * Completion and validation support 21 | * `security zones security-zone xxx address-book xxx` 22 | * Completion support 23 | * `security nat (source|destination) ... then (source|destination)-nat pool` 24 | * `match application` 25 | 26 | ### Fixed 27 | 28 | * Rearrange syntax highlight 29 | * Remove dependency on https://github.com/woodjme/vscode-junos-syntax 30 | * Update color theme 31 | * Reset the config definitions on every file change so incomplete configurations aren't stored while typing 32 | * Ignore comment lines when searching for address book entries 33 | * Reference to global address book entries in `security policies ... match (source|destination)-address` 34 | * `security policies ... match (source|destination)-address (any|any-ipv4|any-ipv6)` was unexpectedly marked as invalid 35 | * `security nat ... match (source|destination)-address ` was unexpectedly marked as invalid 36 | 37 | 38 | ## [0.4.1] - 2025-02-03 39 | 40 | ### Added 41 | 42 | * Newly supported syntax 43 | * `system services web-management http` 44 | 45 | 46 | ## [0.4.0] - 2024-12-20 47 | 48 | ### Added 49 | 50 | * Recreate parser based on MX 22.4R3-S2.11 xsd 51 | * Newly supported syntax 52 | * "groups xxx when" 53 | * "snmp stats-cache-lifetime xxx" 54 | 55 | 56 | ## [0.3.6] - 2024-09-15 57 | 58 | ### Added 59 | 60 | * Update vSRX syntax to 22.4R1.10 61 | * `security` 62 | * `chassis cluster` 63 | * Completion and validation support 64 | * `security address-book xxx address-set xxx address` 65 | * `security address-book xxx address-set xxx address-set` 66 | | * `security nat xxx pool xxx address-name` 67 | | * `security nat ... match xxx-address` 68 | * `security policies ... match (source|destination)-address` 69 | 70 | ### Fixed 71 | 72 | * Syntax highlight 73 | * `address-book` 74 | * `address-set` / `address` 75 | 76 | 77 | ## [0.3.5] - 2024-06-07 78 | 79 | ### Fixed 80 | 81 | * Path to `.tmLanguage` for case-sensitive systems 82 | 83 | 84 | ## [0.3.4] - 2024-06-02 85 | 86 | ### Added 87 | 88 | * Newly supported syntax 89 | * `interfaces xxx unit xxx enable` 90 | 91 | ### Fixed 92 | 93 | * Parse of `set`, `delete`, `activate`, and `deactivate` at the end of a line 94 | 95 | 96 | ## [0.3.3] - 2024-05-19 97 | 98 | ### Added 99 | 100 | * Newly supported syntax 101 | * `apply-groups-except` 102 | * `interfaces xxx enable` 103 | 104 | 105 | ## [0.3.2] - 2024-03-29 106 | 107 | ### Added 108 | 109 | * Newly supported syntax 110 | * `set logical-systems` 111 | 112 | 113 | ## [0.3.1] - 2023-07-10 114 | 115 | ### Added 116 | 117 | * Newly supported syntax 118 | * `chassis cluster` 119 | * `set security log stream xxx transport` 120 | 121 | ### Fixed 122 | 123 | * `forwarding-options dhcp-relay server-group` was unexpectedly marked as invalid 124 | 125 | 126 | ## [0.3.0] - 2023-03-12 127 | 128 | ### Added 129 | 130 | * Upgrade language server and client to 8.1.0 131 | * Recreate parser based on MX 21.2R3-S2.9 xsd 132 | * Newly supported syntax 133 | * `protocols iccp peer xxx liveness-detection single-hop` 134 | * `poe` based on EX 18.1R3-S6.1 135 | 136 | ### Fixed 137 | 138 | * `interfaces xxx ether-options speed` was unexpectedly marked as invalid 139 | 140 | 141 | ## [0.2.6] - 2022-09-20 142 | 143 | ### Added 144 | 145 | * Faster interface speed up to 800g 146 | 147 | ### Fixed 148 | 149 | * `class-of-service interfaces all unit` to accept a unit number 150 | * `classifiers` and `rewrite-rules` to accept arbitrary strings in various hierarchy 151 | * `protocols mpls path xxx` to accept a next hop address 152 | * `match` instead of `dest-nat-rule-match`, `src-nat-rule-match` or `static-nat-rule-match` 153 | 154 | 155 | ## [0.2.5] - 2022-07-26 156 | 157 | ### Fixed 158 | 159 | * `interfaces interface-range xxx member-range` 160 | 161 | 162 | ## [0.2.4] - 2022-02-06 163 | 164 | ### Fixed 165 | 166 | * Syntax highlight of `fxp?` and ipv6 address assigned interfaces 167 | 168 | 169 | ## [0.2.3] - 2021-10-28 170 | 171 | ### Added 172 | 173 | * Completion and validation of `interface-range` 174 | 175 | 176 | ## [0.2.2] - 2021-10-18 177 | 178 | ### Fixed 179 | 180 | * `set snmp name` instead of `set snmp system-name` which is a JUNOS problem 181 | 182 | 183 | ## [0.2.1] - 2021-08-30 184 | 185 | ### Fixed 186 | 187 | * Line comment definition, that should be "#". 188 | 189 | 190 | ## [0.2.0] - 2021-05-05 191 | 192 | ### Added 193 | 194 | * Recreate parser based on MX 19.3R3-S1.3 xsd 195 | 196 | 197 | ## [0.1.6] - 2019-11-05 198 | 199 | ### Added 200 | 201 | * Newly supported syntax 202 | * `set security` 203 | 204 | 205 | ## [0.1.5] - 2019-11-04 206 | 207 | ### Added 208 | 209 | * Newly supported syntax 210 | * Completion and validation of NAT pool name (`source-pool`, `destination-pool`, `dns-alg-pool`, `overload-pool`) 211 | 212 | 213 | ## [0.1.4] - 2019-03-03 214 | 215 | ### Fixed 216 | 217 | * Unexpected vscode freeze due to URL pattern matching for syntax highlight 218 | 219 | 220 | ## [0.1.3] - 2019-01-18 221 | 222 | ### Added 223 | 224 | * Tests of: 225 | * Completion 226 | * Validation 227 | 228 | ### Fixed 229 | 230 | * Symbol definitions were flushed when another file is newly opened or edited 231 | 232 | 233 | ## [0.1.2] - 2019-01-15 234 | 235 | ### Added 236 | 237 | * Experimentally implemented 238 | * Go To Definition 239 | * `interface` 240 | * `prefix-list` 241 | * `policy-statement` 242 | * `community` 243 | * `as-path` 244 | * `as-path-group` 245 | * `firewall-filter` 246 | * Completion and validation based on defined symbols 247 | 248 | 249 | ## [0.1.1] - 2019-01-10 250 | 251 | ### Added 252 | 253 | * Newly supported syntax 254 | * `set system dump-on-panic` 255 | * `set protocols bgp minimum-hold-time xxx` 256 | 257 | ### Fixed 258 | 259 | * route-filter 260 | * Sequential statements 261 | 262 | 263 | ## [0.1.0] - 2019-01-07 264 | 265 | ### Added 266 | 267 | * Completion based on NETCONF's xsd generated from vMX 17.2R1.13. 268 | * Syntax validation 269 | * Syntax highlighting 270 | -------------------------------------------------------------------------------- /client/src/test/diagnostics.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as vscode from "vscode"; 3 | 4 | import { activate, getDocUri } from "./helper"; 5 | 6 | const offset = 38; // lines for completion tests 7 | 8 | suite("Should get diagnostics", () => { 9 | const docUri = getDocUri("junos.conf"); 10 | 11 | test("Diagnoses syntax", async () => { 12 | await testDiagnostics(docUri, [ 13 | { 14 | message: '"groups " is invalid', 15 | range: toRange(-6, 4, 11), 16 | severity: vscode.DiagnosticSeverity.Error, 17 | source: "ex", 18 | }, 19 | { 20 | message: '"foo-filter_" is not defined', 21 | range: toRange(3, 56, 67), 22 | severity: vscode.DiagnosticSeverity.Error, 23 | source: "ex", 24 | }, 25 | { 26 | message: '"inet_" is invalid', 27 | range: toRange(4, 38, 43), 28 | severity: vscode.DiagnosticSeverity.Error, 29 | source: "ex", 30 | }, 31 | { 32 | message: '"foo-statement_" is not defined', 33 | range: toRange(7, 41, 55), 34 | severity: vscode.DiagnosticSeverity.Error, 35 | source: "ex", 36 | }, 37 | { 38 | message: '"xe-0/0/0.1" is not defined', 39 | range: toRange(9, 29, 39), 40 | severity: vscode.DiagnosticSeverity.Error, 41 | source: "ex", 42 | }, 43 | { 44 | message: '"protocols" is invalid', 45 | range: toRange(11, 4, 13), 46 | severity: vscode.DiagnosticSeverity.Error, 47 | source: "ex", 48 | }, 49 | { 50 | message: '"foo-prefix_" is not defined', 51 | range: toRange(18, 67, 78), 52 | severity: vscode.DiagnosticSeverity.Error, 53 | source: "ex", 54 | }, 55 | { 56 | message: '"foo-community_" is not defined', 57 | range: toRange(20, 65, 79), 58 | severity: vscode.DiagnosticSeverity.Error, 59 | source: "ex", 60 | }, 61 | { 62 | message: '"foo-as-path_" is not defined', 63 | range: toRange(22, 63, 75), 64 | severity: vscode.DiagnosticSeverity.Error, 65 | source: "ex", 66 | }, 67 | { 68 | message: '"foo-as-path-group_" is not defined', 69 | range: toRange(24, 69, 87), 70 | severity: vscode.DiagnosticSeverity.Error, 71 | source: "ex", 72 | }, 73 | { 74 | message: '"foo-pool_" is not defined', 75 | range: toRange(29, 68, 77), 76 | severity: vscode.DiagnosticSeverity.Error, 77 | source: "ex", 78 | }, 79 | { 80 | message: '"foo-interface_" is not defined', 81 | range: toRange(33, 29, 43), 82 | severity: vscode.DiagnosticSeverity.Error, 83 | source: "ex", 84 | }, 85 | { 86 | message: '"bar-import" is not defined', 87 | range: toRange(38, 62, 72), 88 | severity: vscode.DiagnosticSeverity.Error, 89 | source: "ex", 90 | }, 91 | ...( 92 | [ 93 | [49, 81, 93, "foo-address_"], 94 | [51, 86, 98, "foo-address_"], 95 | [53, 86, 98, "foo-address_"], 96 | [55, 91, 103, "foo-address_"], 97 | [57, 51, 63, "foo-address_"], 98 | [61, 79, 90, "foo-address"], 99 | [63, 79, 91, "bar-address_"], 100 | [64, 83, 98, "foo-address-set"], 101 | [66, 83, 99, "bar-address-set_"], 102 | ] as Array<[number, number, number, string]> 103 | ).map(([line, sChar, eChar, address]) => ({ 104 | message: `"${address}" is not defined`, 105 | range: toRange(line, sChar, eChar), 106 | severity: vscode.DiagnosticSeverity.Error, 107 | source: "ex", 108 | })), 109 | 110 | // global address books 111 | ...( 112 | [ 113 | [79, 93, 104, "baz-address"], 114 | [83, 98, 109, "bar-address"], 115 | [89, 96, 107, "bar-address"], 116 | [91, 96, 107, "baz-address"], 117 | [95, 99, 110, "bar-address"], 118 | [97, 99, 110, "baz-address"], 119 | ] as Array<[number, number, number, string]> 120 | ) 121 | .map(([line, sChar, eChar, address]) => [ 122 | { 123 | message: `"${address}" is not defined`, 124 | range: toRange(line, sChar, eChar), 125 | severity: vscode.DiagnosticSeverity.Error, 126 | source: "ex", 127 | }, 128 | { 129 | message: `"${address}-set" is not defined`, 130 | range: toRange(line + 1, sChar, eChar + 4), 131 | severity: vscode.DiagnosticSeverity.Error, 132 | source: "ex", 133 | }, 134 | ]) 135 | .flat(), 136 | 137 | // zone-specific address books 138 | ...( 139 | [ 140 | [109, 97, 108, "baz-address"], 141 | [113, 102, 113, "bar-address"], 142 | [119, 97, 108, "bar-address"], 143 | [121, 97, 108, "baz-address"], 144 | [125, 102, 113, "bar-address"], 145 | [127, 102, 113, "baz-address"], 146 | ] as Array<[number, number, number, string]> 147 | ) 148 | .map(([line, sChar, eChar, address]) => [ 149 | { 150 | message: `"${address}" is not defined`, 151 | range: toRange(line, sChar, eChar), 152 | severity: vscode.DiagnosticSeverity.Error, 153 | source: "ex", 154 | }, 155 | { 156 | message: `"${address}-set" is not defined`, 157 | range: toRange(line + 1, sChar, eChar + 4), 158 | severity: vscode.DiagnosticSeverity.Error, 159 | source: "ex", 160 | }, 161 | ]) 162 | .flat(), 163 | ]); 164 | }); 165 | }); 166 | 167 | function toRange(line: number, sChar: number, eChar: number) { 168 | const start = new vscode.Position(line + offset, sChar); 169 | const end = new vscode.Position(line + offset, eChar); 170 | return new vscode.Range(start, end); 171 | } 172 | 173 | async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]) { 174 | await activate(docUri); 175 | 176 | const actualDiagnostics = vscode.languages.getDiagnostics(docUri); 177 | 178 | assert.equal(actualDiagnostics.length, expectedDiagnostics.length); 179 | 180 | expectedDiagnostics.forEach((expectedDiagnostic, i) => { 181 | const actualDiagnostic = actualDiagnostics[i]; 182 | assert.equal(actualDiagnostic.message, expectedDiagnostic.message); 183 | assert.deepEqual(actualDiagnostic.range, expectedDiagnostic.range); 184 | assert.equal(actualDiagnostic.severity, expectedDiagnostic.severity); 185 | }); 186 | } 187 | -------------------------------------------------------------------------------- /server/src/parser.ts: -------------------------------------------------------------------------------- 1 | import { Enumeration, JunosSchema, Repeatable, SchemaObject, Sequence } from "../src/junos"; // './junos' breaks debugging on vscode 2 | 3 | export const prefixPattern = /^[\t ]*(?:set|delete|activate|deactivate)/; 4 | 5 | export class Node { 6 | name: string; 7 | parent: Node | null; 8 | children: Node[] = []; 9 | description?: string; // Can be undefined according to CompletionItem.detail 10 | type: string | null = null; 11 | repeatable? = false; 12 | 13 | constructor( 14 | name: string, 15 | parent: Node | null, 16 | rawChildren: SchemaObject | null, 17 | description?: string, 18 | type?: string, 19 | ) { 20 | this.name = name; 21 | this.parent = parent; 22 | this.description = description; 23 | this.type = type || null; 24 | 25 | if (rawChildren !== null) { 26 | this.load(rawChildren); 27 | } 28 | } 29 | 30 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 31 | load(rawChildren: string[] | Enumeration | Repeatable | Sequence | SchemaObject | Function) { 32 | switch (typeof rawChildren) { 33 | case "object": 34 | if (Array.isArray(rawChildren)) { 35 | this.loadArray(rawChildren); 36 | } else if (rawChildren instanceof Enumeration) { 37 | this.loadArray(rawChildren.list); 38 | } else if (rawChildren instanceof Sequence) { 39 | this.loadSequence(rawChildren, 0); 40 | } else if (rawChildren instanceof Repeatable) { 41 | this.loadRepeatable(rawChildren); 42 | } else { 43 | this.loadObject(rawChildren); 44 | } 45 | break; 46 | case "string": 47 | this.loadString(rawChildren); 48 | break; 49 | case "function": 50 | this.load(rawChildren()); 51 | break; 52 | case "undefined": 53 | break; 54 | default: 55 | throw new Error("Not implemented"); 56 | } 57 | } 58 | 59 | private loadObject(obj: SchemaObject) { 60 | Object.keys(obj).forEach((key: string) => { 61 | const val = obj[key]; 62 | 63 | if (val === false) { 64 | // Only the "set groups" has the value false, like: 65 | // "groups(arg) | Configuration groups": opts?.groups !== false && { ... } 66 | // Just skip it. 67 | } else if (key.startsWith("arg")) { 68 | this.add(this.argNode(key, val)); 69 | } else if (key.startsWith("null_") && val) { 70 | this.addNullNode(val); 71 | } else if (typeof key === "string") { 72 | this.addStringNode(key, val); 73 | } else { 74 | throw new Error("Not implemented"); 75 | } 76 | }); 77 | } 78 | 79 | private loadArray(array: string[]) { 80 | array.forEach((child) => { 81 | this.addStringNode(child, null); 82 | }); 83 | } 84 | 85 | loadSequence(sequence: Sequence, depth: number) { 86 | const raw = sequence.get(depth); 87 | 88 | // This is actually complicated, might be buggy 89 | if (this.children.length === 0) { 90 | this.load(raw); 91 | this.children.forEach((child) => { 92 | child.loadSequence(sequence, depth + 1); 93 | }); 94 | } else { 95 | this.children.forEach((child) => { 96 | child.load(raw); 97 | child.children.forEach((grandChild) => { 98 | grandChild.loadSequence(sequence, depth + 1); 99 | }); 100 | }); 101 | } 102 | } 103 | 104 | private loadString(string: string) { 105 | switch (string) { 106 | case "arg": 107 | this.add(new Node("arg", this, null, undefined, "arg")); 108 | break; 109 | default: 110 | throw new Error("Not implemented"); 111 | } 112 | } 113 | 114 | loadRepeatable(repeatable: Repeatable) { 115 | this.load(repeatable.list); 116 | this.repeatable = true; 117 | } 118 | 119 | add(child: Node) { 120 | this.children.push(child); 121 | } 122 | 123 | addArrayString(args: string, description: string, rawChildren: SchemaObject | null) { 124 | args 125 | .split(/\s*\|\s*/) 126 | .filter((arg) => !arg.startsWith("$")) 127 | .forEach((arg) => { 128 | this.load({ [arg]: rawChildren }); 129 | }); 130 | } 131 | 132 | // This is a bit hacky, but migrates null node, which is originally nested choice element, 133 | // will be migrated to the parent. 134 | private addNullNode(children: SchemaObject) { 135 | this.load(children); 136 | } 137 | 138 | private addStringNode(key: string, rawChildren: SchemaObject | null) { 139 | const [name, description] = this.extractKey(key); 140 | const m = name.match(/(\S*)\((.*)\)/); 141 | 142 | if (!m) { 143 | this.add(new Node(name, this, rawChildren, description)); 144 | } else if (m[1]) { 145 | // eg: unit($junos-underlying-interface-unit|$junos-interface-unit|arg) 146 | this.add(this.argumentStringNode(m[1], m[2], description, rawChildren)); 147 | } else { 148 | // eg: (any-unicast|any-ipv4|any-ipv6|arg) 149 | this.addArrayString(m[2], description, rawChildren); 150 | } 151 | } 152 | 153 | private argNode(key: string, rawChildren: SchemaObject | null) { 154 | const [, description] = this.extractKey(key); 155 | // name may be "arg_1" 156 | return new Node("arg", this, rawChildren, description, "arg"); 157 | } 158 | 159 | private argumentStringNode(name: string, args: string, description: string, rawChildren: SchemaObject | null) { 160 | const node = new Node(name, this, null, description); 161 | 162 | node.addArrayString(args, description, rawChildren); 163 | return node; 164 | } 165 | 166 | private extractKey(key: string) { 167 | return key.split(" | "); 168 | } 169 | 170 | keywords() { 171 | return this.children.map((node) => node.name).sort(); 172 | } 173 | 174 | find(string: string) { 175 | return ( 176 | this.children.find((node) => node.name === string) || this.children.find((node) => node.type === "arg") || null 177 | ); 178 | } 179 | } 180 | 181 | export class Parser { 182 | readonly ast: Node; 183 | repeatableNode?: Node; 184 | 185 | constructor(ast: Node) { 186 | this.ast = ast; 187 | } 188 | 189 | parse(string: string) { 190 | let ast: Node | null | undefined = this.ast; 191 | string 192 | .trim() 193 | .split(/\s+/) 194 | .forEach((word) => { 195 | ast = ast?.find(word); 196 | 197 | // Remember repeatable node matched to the substring 198 | if (ast?.repeatable) { 199 | this.repeatableNode = ast; 200 | } 201 | 202 | // If it has no child, use the repeatable node instead 203 | if (ast?.children.length === 0) { 204 | ast = this.repeatableNode; 205 | } 206 | }); 207 | 208 | return ast; 209 | } 210 | 211 | keywords(string: string) { 212 | let ast: Node | null = this.ast; 213 | string = string.trim(); 214 | const defaultKeywords = ["apply-groups", "apply-groups-except"]; 215 | 216 | // Hack for "groups" statement 217 | if (string) { 218 | if (string.match(/^groups\s*$|\s*apply-groups(-except)?$/)) { 219 | return ["word"]; 220 | } 221 | string = string.replace(/apply-groups(-except)?\s+\S+$/, ""); 222 | } 223 | 224 | // Hack for implicit interface-range "all" 225 | if (string?.match(/\s+interface$/)) { 226 | defaultKeywords.push("all"); 227 | } 228 | 229 | if (string) { 230 | ast = this.parse(string); 231 | } 232 | 233 | const keywords = 234 | ast 235 | ?.keywords() 236 | .map((k) => (k === "arg" ? "word" : k)) // replace "arg" with "word" 237 | .concat(defaultKeywords) || []; 238 | 239 | return [...new Set(keywords)]; // uniq 240 | } 241 | 242 | description(string: string) { 243 | if (!string) { 244 | return; 245 | } 246 | 247 | return this.parse(string)?.description; 248 | } 249 | } 250 | 251 | export function createParser() { 252 | const schema = new JunosSchema(); 253 | const ast = new Node("configuration", null, schema.configuration()); 254 | return new Parser(ast); 255 | } 256 | -------------------------------------------------------------------------------- /server/src/validation.ts: -------------------------------------------------------------------------------- 1 | import { Diagnostic, DiagnosticSeverity, TextDocument } from "vscode-languageserver"; 2 | 3 | import { isIpPrefix } from "./ip"; 4 | import { prefixPattern } from "./parser"; 5 | import { Session } from "./session"; 6 | 7 | const maxNumberOfProblems = 1000; // Just a guard 8 | 9 | export async function validateTextDocument(session: Session, textDocument: TextDocument) { 10 | const text = textDocument.getText(); 11 | const pattern = new RegExp(`(${prefixPattern.source}[\\t ]+)(.*)`, "gm"); 12 | let m: RegExpExecArray | null; 13 | 14 | let problems = 0; 15 | const diagnostics: Diagnostic[] = []; 16 | while ((m = pattern.exec(text)) && problems < maxNumberOfProblems) { 17 | // Validate with AST based syntax 18 | const invalidPosition = validateLine(session, m[2]); 19 | if (typeof invalidPosition !== "undefined") { 20 | problems++; 21 | 22 | diagnostics.push( 23 | createDiagnostic( 24 | session, 25 | textDocument, 26 | m.index + m[1].length + invalidPosition, 27 | m.index + m[0].length, 28 | `"${m[2].slice(invalidPosition)}" is invalid`, 29 | ), 30 | ); 31 | } 32 | 33 | // Validate symbol reference 34 | const match = m; 35 | const rules = [ 36 | ["interface", "interface", ["all"], ["interface-range"]], 37 | ["prefix-list", "from\\s+(?:source-|destination-)?prefix-list"], 38 | ["policy-statement", "(?:import|export)"], 39 | ["community", "(?:from\\s+community|then\\s+community\\s+(?:add|delete|set))"], 40 | ["as-path", "from\\s+as-path"], 41 | ["as-path-group", "from\\s+as-path-group"], 42 | ["firewall-filter", "filter\\s+(?:input|output|input-list|output-list)"], 43 | ["service-nat-pool", "then\\s+translated\\s+(?:source-pool|destination-pool|dns-alg-pool|overload-pool)"], 44 | [ 45 | "address:global:global", 46 | "nat\\s+.*\\s+match\\s+(?:source|destination)-address(?:-name)?", 47 | (m) => (isIpPrefix(m[3]) ? [m[3]] : []), 48 | ], 49 | ["address:global:global", "pool\\s+\\S+\\s+address-name"], 50 | 51 | // global address books 52 | [(m) => `address:global:${m[3]}`, "(?<=security\\s+)address-book\\s+(\\S+)\\s+address-set\\s+\\S+\\s+address"], 53 | [ 54 | (m) => `address-set:global:${m[3]}`, 55 | "(?<=security\\s+)address-book\\s+(\\S+)\\s+address-set\\s+\\S+\\s+address-set", 56 | ], 57 | 58 | // zone-specific address books 59 | [ 60 | (m) => `address:${m[3]}:global`, 61 | "(?<=security\\s+)zones\\s+(\\S+)\\s+address-book\\s+address-set\\s+\\S+\\s+address", 62 | ], 63 | [ 64 | (m) => `address-set:${m[3]}:global`, 65 | "(?<=security\\s+)zones\\s+(\\S+)\\s+address-book\\s+address-set\\s+\\S+\\s+address-set", 66 | ], 67 | 68 | [ 69 | (m) => { 70 | const zone = m[5] === "source" ? m[3] : m[4]; 71 | const addressBooks = session.zoneAddressBooks.get(textDocument.uri, m.groups!.ls || "global", zone); 72 | return [...addressBooks] 73 | .map((a) => [`address:global:${a}`, `address-set:global:${a}`]) 74 | .flat() 75 | .concat([ 76 | "address:global:global", 77 | `address:${zone}:global`, 78 | "address-set:global:global", 79 | `address-set:${zone}:global`, 80 | ]); 81 | }, 82 | "from-zone\\s+(\\S+)\\s+to-zone\\s+(\\S+)\\s+.*\\s+match\\s+(source|destination)-address", 83 | ["any", "any-ipv4", "any-ipv6"], 84 | ], 85 | ] as Array< 86 | [ 87 | string | ((arg: RegExpMatchArray) => string | string[]), 88 | string, 89 | string[] | ((arg: RegExpMatchArray) => string[]), 90 | string[], 91 | ] 92 | >; 93 | 94 | // Type guards ignored in closure. See https://github.com/microsoft/TypeScript/issues/38755 95 | rules.forEach(([symbolType, pattern, allowList, denyList]) => { 96 | const invalidRange = validateReference( 97 | session, 98 | match[2], 99 | textDocument.uri, 100 | symbolType, 101 | pattern, 102 | allowList, 103 | denyList, 104 | ); 105 | if (typeof invalidRange !== "undefined") { 106 | problems++; 107 | 108 | diagnostics.push( 109 | createDiagnostic( 110 | session, 111 | textDocument, 112 | match.index + match[1].length + invalidRange[0], 113 | match.index + match[1].length + invalidRange[1], 114 | `"${match[2].slice(...invalidRange)}" is not defined`, 115 | ), 116 | ); 117 | } 118 | }); 119 | } 120 | 121 | return diagnostics; 122 | } 123 | 124 | function createDiagnostic(session: Session, textDocument: TextDocument, start: number, end: number, message: string) { 125 | return { 126 | severity: DiagnosticSeverity.Error, 127 | range: { 128 | start: textDocument.positionAt(start), 129 | end: textDocument.positionAt(end), 130 | }, 131 | message: message, 132 | }; 133 | } 134 | 135 | /** 136 | * Return undefined when validation is succeeded, or position where invalid statement starts 137 | * 138 | * @param session 139 | * @param line String to validate 140 | * @return number or undefined 141 | */ 142 | function validateLine(session: Session, line: string): number | undefined { 143 | const m = squashQuotedSpaces(line).match(/(?:(.*)\s+)?(\S+)/); 144 | if (!m) { 145 | return; 146 | } 147 | 148 | // There is an invalid keyword in the beginning like "set foo" 149 | if (!m[1]) { 150 | return 0; 151 | } 152 | 153 | const keywords = session.parser.keywords(m[1]); 154 | 155 | if ( 156 | keywords.includes("word") || // 'word' means wildcard 157 | keywords.includes(m[2]) 158 | ) { 159 | return; 160 | } 161 | 162 | const shorter = validateLine(session, m[1]); 163 | return typeof shorter === "undefined" ? m[1].length + 1 : shorter; 164 | } 165 | 166 | /** 167 | * Return undefined when validation is succeeded, or position range of invalid statement 168 | * 169 | * @param session 170 | * @param line String to validate 171 | * @param uri TextDocument's URI 172 | * @param symbolType string A key of session.definitions (eg: 'interface') 173 | * @param pattern string A line pattern to kick the validation 174 | * @param allowList string[] Keywords to pass the validation 175 | * @param denyList string[] Keywords to fail the validation 176 | * @return number[] or undefined [startPosition, endPosition] 177 | */ 178 | function validateReference( 179 | session: Session, 180 | line: string, 181 | uri: string, 182 | symbolType: string | ((arg: RegExpMatchArray) => string | string[]), 183 | pattern: string, 184 | allowList?: string[] | ((arg: RegExpMatchArray) => string[]), 185 | denyList?: string[], 186 | ) { 187 | const m = line.match(`^(?(?:logical-systems\\s+(?\\S+))?.*\\s${pattern}\\s+)(?\\S+)`); 188 | if (!m) { 189 | return; 190 | } 191 | 192 | const list = typeof allowList === "function" ? allowList(m) : allowList; 193 | if (list?.includes(m.groups!.arg)) { 194 | return; 195 | } 196 | 197 | const stmtLength = (m.index || 0) + m.groups!.stmt.length; 198 | const range = [stmtLength, stmtLength + m.groups!.arg.length]; 199 | 200 | if (denyList?.includes(m.groups!.arg)) { 201 | return range; 202 | } 203 | 204 | let types = typeof symbolType === "function" ? symbolType(m) : symbolType; 205 | if (!Array.isArray(types)) { 206 | types = [types]; 207 | } 208 | 209 | for (const type of types) { 210 | if (m.groups!.arg in session.definitions.getDefinitions(uri, m.groups!.ls || "global", type)) { 211 | return; 212 | } 213 | } 214 | 215 | return range; 216 | } 217 | 218 | /** 219 | * Replace quoted ' ' with '_' for easy tokenization 220 | * 221 | * @param string 222 | * @return string 223 | */ 224 | function squashQuotedSpaces(string: string) { 225 | const pattern = /"[^"]*"/g; 226 | let match: RegExpExecArray | null; 227 | let cursor = 0; 228 | let buffer = ""; 229 | 230 | while ((match = pattern.exec(string))) { 231 | buffer += string.slice(cursor, match.index); 232 | buffer += match[0].replace(/ /g, "_"); 233 | cursor += match.index + match[0].length; 234 | } 235 | buffer += string.slice(cursor); 236 | 237 | return buffer; 238 | } 239 | -------------------------------------------------------------------------------- /client/testFixture/junos.conf: -------------------------------------------------------------------------------- 1 | # completion 2 | 3 | set 4 | set interfaces xe-0/0/0 5 | set policy-options policy-statement foo-statement from prefix-list 6 | set protocols bgp group bgp-group import 7 | set policy-options policy-statement foo-statement from community 8 | set policy-options policy-statement foo-statement from as-path 9 | set policy-options policy-statement foo-statement from as-path-group 10 | set interfaces xe-0/0/1 unit 0 family inet filter input 11 | set services nat rule foo-rule term bar then translated source-pool 12 | set protocols lldp interface 13 | set logical-systems ls-1 protocols bgp group foo-group import 14 | set logical-systems ls-2 policy-options policy-statement bar-import from prefix-list 15 | set security nat source rule-set foo-rule-set rule foo-rule match source-address 16 | set security nat source rule-set foo-rule-set rule foo-rule match destination-address 17 | set security nat source rule-set foo-rule-set rule foo-rule match source-address-name 18 | set security nat source rule-set foo-rule-set rule foo-rule match destination-address-name 19 | set security nat source pool foo-pool address-name 20 | set security nat source rule-set foo-rule-set rule foo-rule then source-nat pool 21 | set security nat destination rule-set foo-rule-set rule foo-rule then destination-nat pool 22 | set security address-book foo-address-book address-set bar-address-set address 23 | set security address-book foo-address-book address-set bar-address-set address-set 24 | set security zones security-zone bar-zone address-book address-set bar-address-set address 25 | set security zones security-zone bar-zone address-book address-set bar-address-set address-set 26 | set security policies from-zone trust to-zone untrust policy foo match source-address 27 | set security policies from-zone trust to-zone untrust policy foo match destination-address 28 | set security policies from-zone foo-zone to-zone untrust policy foo-policy match source-address 29 | set security policies from-zone trust to-zone foo-zone policy foo-policy match destination-address 30 | 31 | set security policies from-zone trust to-zone foo-zone policy foo-policy match application 32 | 33 | set groups 34 | set groups foo 35 | set groups foo when chassis lcc0 36 | 37 | # validation 38 | 39 | set interfaces xe-0/0/0 description "white space" 40 | set interfaces xe-0/0/0 unit 0 family inet address 10.0.0.1/30 41 | set interfaces xe-0/0/1 unit 0 family inet filter input foo-filter 42 | set interfaces xe-0/0/1 unit 0 family inet filter input foo-filter_ 43 | set interfaces xe-0/0/1 unit 0 family inet_ 44 | 45 | set protocols bgp group bgp-group import foo-statement 46 | set protocols bgp group bgp-group import foo-statement_ 47 | set protocols mpls interface xe-0/0/0.0 48 | set protocols mpls interface xe-0/0/0.1 49 | set protocols mpls interface xe-0/0/1 50 | set protocols 51 | 52 | set policy-options prefix-list foo-prefix 10.0.0.0/8 53 | set policy-options community foo-community members 65000:100 54 | set policy-options as-path foo-as-path "65000+" 55 | set policy-options as-path-group foo-as-path-group as-path foo-as-path "65000+" 56 | set policy-options policy-statement foo-statement from prefix-list foo-prefix 57 | set policy-options policy-statement foo-statement from prefix-list foo-prefix_ 58 | set policy-options policy-statement foo-statement from community foo-community 59 | set policy-options policy-statement foo-statement from community foo-community_ 60 | set policy-options policy-statement foo-statement from as-path foo-as-path 61 | set policy-options policy-statement foo-statement from as-path foo-as-path_ 62 | set policy-options policy-statement foo-statement from as-path-group foo-as-path-group 63 | set policy-options policy-statement foo-statement from as-path-group foo-as-path-group_ 64 | set firewall filter foo-filter term foo-term then accept 65 | 66 | set services nat pool foo-pool address 10.0.0.0/16 67 | set services nat rule foo-rule term bar then translated source-pool foo-pool 68 | set services nat rule foo-rule term bar then translated source-pool foo-pool_ 69 | 70 | set interfaces interface-range foo-interface member xe-*/0/0 71 | set protocols lldp interface foo-interface disable 72 | set protocols lldp interface foo-interface_ disable 73 | 74 | set applications application tcp-app protocol tcp 75 | 76 | set logical-systems ls-1 protocols bgp group foo-group import foo-import 77 | set logical-systems ls-1 protocols bgp group foo-group import bar-import 78 | set logical-systems ls-1 policy-options prefix-list foo-prefix 10.0.0.0/16 79 | set logical-systems ls-1 policy-options policy-statement foo-import from prefix-list foo-prefix 80 | set logical-systems ls-1 policy-options policy-statement foo-import then next policy 81 | set logical-systems ls-2 policy-options prefix-list bar-prefix 10.1.0.0/16 82 | set logical-systems ls-2 policy-options policy-statement bar-import from prefix-list bar-prefix 83 | set logical-systems ls-2 policy-options policy-statement bar-import then next policy 84 | 85 | set security address-book global address foo-address 10.0.0.0/16 86 | set security address-book global address-set foo-address-set address foo-address 87 | set security nat source rule-set foo-rule-set rule foo-rule match source-address foo-address 88 | set security nat source rule-set foo-rule-set rule foo-rule match source-address foo-address_ 89 | set security nat source rule-set foo-rule-set rule foo-rule match destination-address foo-address 90 | set security nat source rule-set foo-rule-set rule foo-rule match destination-address foo-address_ 91 | set security nat source rule-set foo-rule-set rule foo-rule match source-address-name foo-address 92 | set security nat source rule-set foo-rule-set rule foo-rule match source-address-name foo-address_ 93 | set security nat source rule-set foo-rule-set rule foo-rule match destination-address-name foo-address 94 | set security nat source rule-set foo-rule-set rule foo-rule match destination-address-name foo-address_ 95 | set security nat source pool foo-pool address-name foo-address 96 | set security nat source pool foo-pool address-name foo-address_ 97 | set security nat destination pool bar-pool address 10.0.0.0/16 98 | 99 | set security address-book foo-address-book address bar-address 10.0.0.0/16 100 | set security address-book foo-address-book address-set bar-address-set address foo-address 101 | set security address-book foo-address-book address-set bar-address-set address bar-address 102 | set security address-book foo-address-book address-set bar-address-set address bar-address_ 103 | set security address-book foo-address-book address-set bar-address-set address-set foo-address-set 104 | set security address-book foo-address-book address-set bar-address-set address-set bar-address-set 105 | set security address-book foo-address-book address-set bar-address-set address-set bar-address-set_ 106 | 107 | set security address-book bar-address-book address bar-address 10.0.0.0/16 108 | set security address-book bar-address-book address-set bar-address-set address bar-address 109 | set security address-book bar-address-book attach zone trust 110 | set security address-book baz-address-book address baz-address 10.0.0.0/16 111 | set security address-book baz-address-book address-set baz-address-set address baz-address 112 | set security address-book baz-address-book attach zone untrust 113 | 114 | set security policies from-zone trust to-zone untrust policy foo-policy match source-address foo-address 115 | set security policies from-zone trust to-zone untrust policy foo-policy match source-address foo-address-set 116 | set security policies from-zone trust to-zone untrust policy foo-policy match source-address bar-address 117 | set security policies from-zone trust to-zone untrust policy foo-policy match source-address bar-address-set 118 | set security policies from-zone trust to-zone untrust policy foo-policy match source-address baz-address 119 | set security policies from-zone trust to-zone untrust policy foo-policy match source-address baz-address-set 120 | set security policies from-zone trust to-zone untrust policy foo-policy match destination-address foo-address 121 | set security policies from-zone trust to-zone untrust policy foo-policy match destination-address foo-address-set 122 | set security policies from-zone trust to-zone untrust policy foo-policy match destination-address bar-address 123 | set security policies from-zone trust to-zone untrust policy foo-policy match destination-address bar-address-set 124 | set security policies from-zone trust to-zone untrust policy foo-policy match destination-address baz-address 125 | set security policies from-zone trust to-zone untrust policy foo-policy match destination-address baz-address-set 126 | set security policies from-zone foo-zone to-zone untrust policy foo-policy match source-address foo-address 127 | set security policies from-zone foo-zone to-zone untrust policy foo-policy match source-address foo-address-set 128 | set security policies from-zone foo-zone to-zone untrust policy foo-policy match source-address bar-address 129 | set security policies from-zone foo-zone to-zone untrust policy foo-policy match source-address bar-address-set 130 | set security policies from-zone foo-zone to-zone untrust policy foo-policy match source-address baz-address 131 | set security policies from-zone foo-zone to-zone untrust policy foo-policy match source-address baz-address-set 132 | set security policies from-zone trust to-zone foo-zone policy foo-policy match destination-address foo-address 133 | set security policies from-zone trust to-zone foo-zone policy foo-policy match destination-address foo-address-set 134 | set security policies from-zone trust to-zone foo-zone policy foo-policy match destination-address bar-address 135 | set security policies from-zone trust to-zone foo-zone policy foo-policy match destination-address bar-address-set 136 | set security policies from-zone trust to-zone foo-zone policy foo-policy match destination-address baz-address 137 | set security policies from-zone trust to-zone foo-zone policy foo-policy match destination-address baz-address-set 138 | 139 | set security zones security-zone bar-zone address-book address bar-address 10.0.0.0/16 140 | set security zones security-zone bar-zone address-book address-set bar-address-set address bar-address 141 | set security zones security-zone baz-zone address-book address baz-address 10.0.0.0/16 142 | set security zones security-zone baz-zone address-book address-set baz-address-set address baz-address 143 | 144 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match source-address foo-address 145 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match source-address foo-address-set 146 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match source-address bar-address 147 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match source-address bar-address-set 148 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match source-address baz-address 149 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match source-address baz-address-set 150 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match destination-address foo-address 151 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match destination-address foo-address-set 152 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match destination-address bar-address 153 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match destination-address bar-address-set 154 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match destination-address baz-address 155 | set security policies from-zone bar-zone to-zone baz-zone policy foo-policy match destination-address baz-address-set 156 | set security policies from-zone foo-zone to-zone baz-zone policy foo-policy match source-address foo-address 157 | set security policies from-zone foo-zone to-zone baz-zone policy foo-policy match source-address foo-address-set 158 | set security policies from-zone foo-zone to-zone baz-zone policy foo-policy match source-address bar-address 159 | set security policies from-zone foo-zone to-zone baz-zone policy foo-policy match source-address bar-address-set 160 | set security policies from-zone foo-zone to-zone baz-zone policy foo-policy match source-address baz-address 161 | set security policies from-zone foo-zone to-zone baz-zone policy foo-policy match source-address baz-address-set 162 | set security policies from-zone bar-zone to-zone foo-zone policy foo-policy match destination-address foo-address 163 | set security policies from-zone bar-zone to-zone foo-zone policy foo-policy match destination-address foo-address-set 164 | set security policies from-zone bar-zone to-zone foo-zone policy foo-policy match destination-address bar-address 165 | set security policies from-zone bar-zone to-zone foo-zone policy foo-policy match destination-address bar-address-set 166 | set security policies from-zone bar-zone to-zone foo-zone policy foo-policy match destination-address baz-address 167 | set security policies from-zone bar-zone to-zone foo-zone policy foo-policy match destination-address baz-address-set 168 | -------------------------------------------------------------------------------- /syntaxes/junos.tmGrammar.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "text.junos", 3 | "name": "Junos", 4 | "uuid": "67ee2ed0-aeb6-45e4-95fb-0259da69792c", 5 | "fileTypes": [ 6 | "conf", 7 | "conf.1", 8 | "conf.2", 9 | "conf.3", 10 | "conf.4", 11 | "conf.5", 12 | "conf.6", 13 | "conf.7", 14 | "conf.8", 15 | "conf.9", 16 | "conf.10", 17 | "conf.11", 18 | "conf.12", 19 | "conf.13", 20 | "conf.14", 21 | "conf.15", 22 | "conf.16", 23 | "conf.17", 24 | "conf.18", 25 | "conf.19", 26 | "conf.20", 27 | "conf.21", 28 | "conf.22", 29 | "conf.23", 30 | "conf.24", 31 | "conf.25", 32 | "conf.26", 33 | "conf.27", 34 | "conf.28", 35 | "conf.29", 36 | "conf.30", 37 | "conf.31", 38 | "conf.32", 39 | "conf.33", 40 | "conf.34", 41 | "conf.35", 42 | "conf.36", 43 | "conf.37", 44 | "conf.38", 45 | "conf.39", 46 | "conf.40", 47 | "conf.41", 48 | "conf.42", 49 | "conf.43", 50 | "conf.44", 51 | "conf.45", 52 | "conf.46", 53 | "conf.47", 54 | "conf.48", 55 | "conf.49" 56 | ], 57 | "patterns": [ 58 | { 59 | "include": "#command" 60 | }, 61 | { 62 | "include": "#keyword" 63 | }, 64 | { 65 | "include": "#user_defined_name" 66 | }, 67 | { 68 | "include": "#comment" 69 | }, 70 | { 71 | "include": "#quote" 72 | }, 73 | { 74 | "include": "#value" 75 | } 76 | ], 77 | "repository": { 78 | "command": { 79 | "comment": "keywords captured without a major/minor section attached", 80 | "match": "(?<=^|\\s)(set|request|delete|edit|show|protect:?|inactive:|unprotect|deactivate|activate)\\s+([-\\w<>:\\./]+)", 81 | "captures": { 82 | "1": { 83 | "name": "keyword.control.junos" 84 | }, 85 | "2": { 86 | "name": "entity.other.attribute-name.junos" 87 | } 88 | } 89 | }, 90 | "keyword": { 91 | "patterns": [ 92 | { 93 | "include": "#section_and_argument_address_structured" 94 | }, 95 | { 96 | "include": "#section_and_argument_address" 97 | }, 98 | { 99 | "include": "#section_and_argument_structured" 100 | }, 101 | { 102 | "include": "#section_and_argument" 103 | }, 104 | { 105 | "include": "#accept" 106 | }, 107 | { 108 | "include": "#reject" 109 | } 110 | ] 111 | }, 112 | "section_and_argument_address_structured": { 113 | "comment": "User defined arbitrary addresses in structured format", 114 | "begin": "(?<=^|\\s)((?:source-|destination-|range)?address)\\s*{\\s*", 115 | "end": "{|;", 116 | "beginCaptures": { 117 | "1": { 118 | "name": "entity.other.attribute-name.scss.junos" 119 | } 120 | }, 121 | "patterns": [ 122 | { 123 | "include": "#value" 124 | }, 125 | { 126 | "match": "[-\\w<>:\\./]+", 127 | "name": "variable.language.junos" 128 | } 129 | ] 130 | }, 131 | "section_and_argument_address": { 132 | "comment": "User defined arbitrary addresses", 133 | "begin": "(?<=^|\\s)((?:source-|destination-|range-)?address)\\s", 134 | "_comment": "need '|$' to avoid matching multiline", 135 | "end": "(?=\\s|;|$)", 136 | "beginCaptures": { 137 | "1": { 138 | "name": "entity.other.attribute-name.scss.junos" 139 | } 140 | }, 141 | "patterns": [ 142 | { 143 | "include": "#value" 144 | }, 145 | { 146 | "match": "[-\\w<>:\\./]+", 147 | "name": "variable.language.junos" 148 | } 149 | ] 150 | }, 151 | "section_and_argument_structured": { 152 | "comment": "Stanza double-line capture for user defined arbitrary names (such as filters, policy names, prefix-lists, etc)", 153 | "begin": "(?<=^|\\s)(logical-systems|dynamic-profiles|jsrc-partition|partition|filter input|filter output|access-profile|dscp|dscp-ipv6|exp|ieee-802\\.1|ieee-802\\.1ad|inet-precedence|scheduler-map|scheduler-maps|input-traffic-control-profile-remaining|input-traffic-control-profile|traffic-control-profiles|output-traffic-control-profile-remaining|output-traffic-control-profile|output-forwarding-class-map|scheduler-map-chassis|fragmentation-maps|source-prefix-list|bridge-domains|group|mime-pattern|url-pattern|label-switched-path|admin-groups|custom-url-category|profile|url-whitelist|url-blacklist|ca-profile|idp-policy|active-policy|interface-set|interface-range|family|count|destination-prefix-list|schedulers|drop-profiles|forwarding-class|forwarding-class-map|import|export|instance|utm-policy|ids-option|next-hop-group|routing-instances|rule|rule-set|pool|zone|class|port-mirror-instance|from-zone|to-zone|apply-groups(?:-except)?|file|host-name|domain-name|path|domain-search|community delete|community add|community set|community|trap-group|policy|policy-statement|import-policy|instance-export|instance-import|vrf-import|vrf-export|import|export|keep-import|inter-area-prefix-import|inter-area-prefix-export|network-summary-export|network-summary-import|egress-policy|bootstrap-import|bootstrap-export|filter|prefix-list|proposal|address-set|address-book(?!\\s+address(?:-set)?)|(?:source-|destination-)?address-name|scheduler|rib-groups|groups|security-zone|term|application|application-set|vlans|gateway|user|policer|lsp|condition|as-path|as-path-group|service-set)\\s*{\\s*", 154 | "end": "{|;", 155 | "beginCaptures": { 156 | "1": { 157 | "name": "entity.other.attribute-name.scss.junos" 158 | } 159 | }, 160 | "patterns": [ 161 | { 162 | "match": "[-\\w<>:\\./]+", 163 | "name": "variable.language.junos" 164 | } 165 | ] 166 | }, 167 | "section_and_argument": { 168 | "comment": "User defined arbitrary names (such as filters, policy names, prefix-lists, etc)", 169 | "match": "(?<=^|\\s)(logical-systems|dynamic-profiles|jsrc-partition|partition|filter input|filter output|access-profile|dscp|dscp-ipv6|exp|ieee-802\\.1|ieee-802\\.1ad|inet-precedence|scheduler-map|scheduler-maps|input-traffic-control-profile-remaining|input-traffic-control-profile|traffic-control-profiles|output-traffic-control-profile-remaining|output-traffic-control-profile|output-forwarding-class-map|scheduler-map-chassis|fragmentation-maps|source-prefix-list|bridge-domains|group|mime-pattern|url-pattern|label-switched-path|admin-groups|custom-url-category|profile|url-whitelist|url-blacklist|ca-profile|idp-policy|active-policy|interface-set|interface-range|family|count|destination-prefix-list|schedulers|drop-profiles|forwarding-class|forwarding-class-map|import|export|instance|utm-policy|ids-option|next-hop-group|routing-instances|rule|rule-set|pool|zone|class|port-mirror-instance|from-zone|to-zone|apply-groups(?:-except)?|file|host-name|domain-name|path|domain-search|community delete|community add|community set|community|trap-group|policy|policy-statement|import-policy|instance-export|instance-import|vrf-import|vrf-export|import|export|keep-import|inter-area-prefix-import|inter-area-prefix-export|network-summary-export|network-summary-import|egress-policy|bootstrap-import|bootstrap-export|filter|prefix-list|proposal|address-set|address-book(?!\\s+address(?:-set)?)|(?:source-|destination-)?address-name|scheduler|rib-groups|groups|security-zone|term|application|application-set|vlans|gateway|user|policer|lsp|condition|as-path|as-path-group|service-set)\\s+([-\\w<>:\\./]+)", 170 | "captures": { 171 | "1": { 172 | "name": "entity.other.attribute-name.scss.junos" 173 | }, 174 | "2": { 175 | "name": "variable.language.junos" 176 | } 177 | } 178 | }, 179 | "accept": { 180 | "comment": "Policy/filter denial/rejection actions", 181 | "match": "(?<=^|\\s)(deny|discard|reject)(?=\\s|;|$)", 182 | "name": "invalid.illegal.junos" 183 | }, 184 | "reject": { 185 | "comment": "Policy/filter accept/permit actions", 186 | "match": "(?<=\\s)(accept|permit)(?=\\s|;|$)", 187 | "name": "variable.language.junos" 188 | }, 189 | "user_defined_name": { 190 | "patterns": [ 191 | { 192 | "include": "#interface_name" 193 | }, 194 | { 195 | "include": "#routing_table_name" 196 | } 197 | ] 198 | }, 199 | "interface_name": { 200 | "comment": "Interface names", 201 | "match": "(?<=^|\\s)((?:ge|et|so|fe|gr|xe|lt|vt|si|sp)-(?:\\d+|\\*)\\/(?:\\d+|\\*)\\/(?:\\d+|\\*)|(?:st|lo|me|vme|fxp|ae)\\d+|irb|vlan)(\\.\\d+)?(?=\\s|;|$)", 202 | "name": "string.value.junos" 203 | }, 204 | "routing_table_name": { 205 | "comment": "Routing table names", 206 | "match": "(?<=^|\\s)(?:[\\w-]+\\.)?(?:inet6?|mpls|inetflow|iso|bgp\\.l(?:2|3)vpn)\\.\\d+(?=\\s|;|$)", 207 | "name": "string.value.junos" 208 | }, 209 | "comment": { 210 | "patterns": [ 211 | { 212 | "include": "#comment_line" 213 | }, 214 | { 215 | "include": "#comment_block" 216 | } 217 | ] 218 | }, 219 | "comment_line": { 220 | "comment": "Line comment, anything following a hashtag (#)", 221 | "begin": "#", 222 | "end": "$\\n?", 223 | "name": "comment.line.number-sign.junos" 224 | }, 225 | "comment_block": { 226 | "comment": "Block Comment or annotation", 227 | "begin": "/\\*", 228 | "end": "\\*/", 229 | "name": "comment.block.junos" 230 | }, 231 | "quote": { 232 | "patterns": [ 233 | { 234 | "include": "#single_quote" 235 | }, 236 | { 237 | "include": "#double_quote" 238 | }, 239 | { 240 | "include": "#description" 241 | } 242 | ] 243 | }, 244 | "single_quote": { 245 | "comment": "Single quoted string", 246 | "begin": "'", 247 | "end": "'", 248 | "beginCaptures": { 249 | "0": { 250 | "name": "punctuation.definition.string.begin.junos" 251 | } 252 | }, 253 | "endCaptures": { 254 | "0": { 255 | "name": "punctuation.definition.string.end.junos" 256 | } 257 | }, 258 | "name": "string.quoted.single.junos" 259 | }, 260 | "double_quote": { 261 | "comment": "Double quoted string", 262 | "begin": "\"", 263 | "end": "\"", 264 | "beginCaptures": { 265 | "0": { 266 | "name": "punctuation.definition.string.begin.junos" 267 | } 268 | }, 269 | "endCaptures": { 270 | "0": { 271 | "name": "punctuation.definition.string.end.junos" 272 | } 273 | }, 274 | "name": "string.quoted.double.junos" 275 | }, 276 | "description": { 277 | "comment": "Descriptions should always look like strings, even if no quotations are needed", 278 | "match": "(?<=(?:^|\\s)description\\s+)([-\\w<>:\\./\\[\\]']+)", 279 | "name": "string.quoted.double.junos" 280 | }, 281 | "value": { 282 | "patterns": [ 283 | { 284 | "include": "#ipv4_address" 285 | }, 286 | { 287 | "include": "#ipv6_address" 288 | }, 289 | { 290 | "include": "#mac_address" 291 | }, 292 | { 293 | "include": "#range" 294 | }, 295 | { 296 | "include": "#number" 297 | } 298 | ] 299 | }, 300 | "ipv4_address": { 301 | "comment": "IPv4 addresses, with or without a mask", 302 | "match": "(?<=^|\\s)(?:(?:0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?)\\.){3}(?:0|1(?:[0-9][0-9]?)?|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?|[3-9][0-9]?)(?:\\/\\d{1,2})?(?=\\s|;|$)", 303 | "name": "constant.numeric.integer.junos" 304 | }, 305 | "ipv6_address": { 306 | "comment": "IPv6 Addresses. This will not fully validate the structure of the IP, so some invalid IPv6 addresses might be false positives. IPv6 addresses themselves should be validated by running commands against a Junos device.", 307 | "match": "(?<=^|\\s)(?:(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?|(?:[0-9A-Fa-f]{1,4}:){6,6}(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)|(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::((?:[0-9A-Fa-f]{1,4}:)*)(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)|[Ff][Ee]80(?::[0-9A-Fa-f]{1,4}){7}%[-0-9A-Za-z._~]+|[Ff][Ee]80:(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?::(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?|:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)?:[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+)(?:\\/\\d{1,3})?(?=\\s|;|$)", 308 | "name": "constant.numeric.integer.junos" 309 | }, 310 | "mac_address": { 311 | "comment": "MAC Addresses identified as a number", 312 | "match": "(?<=^|\\s)(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}(?=\\s|;|$)", 313 | "name": "constant.numeric.integer.junos" 314 | }, 315 | "range": { 316 | "comment": "Unit numbers, port numbers, etc", 317 | "match": "(?<=(?:^|\\s)(?:(?:peer-)?unit|queue(?:-num)?|(?:destination-|source-)port)\\s+)(\\d+(-\\d+)?)(?=\\s|;|$)", 318 | "name": "constant.numeric.integer.junos" 319 | }, 320 | "number": { 321 | "comment": "Other general numbers", 322 | "match": "(?<=^|\\s)(\\d+)(?=\\s|;|$)", 323 | "name": "constant.numeric.integer.junos" 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /server/src/definition.ts: -------------------------------------------------------------------------------- 1 | import { Location, Range, TextDocumentPositionParams } from "vscode-languageserver"; 2 | import { TextDocument } from "vscode-languageserver-textdocument"; 3 | 4 | import { prefixPattern } from "./parser"; 5 | import { Session } from "./session"; 6 | 7 | export class DefinitionStore { 8 | private readonly store: { 9 | [uri: string]: { 10 | [logicalSystem: string]: { 11 | [symbolType: string]: { 12 | [symbol: string]: Range[]; 13 | }; 14 | }; 15 | }; 16 | }; 17 | 18 | constructor() { 19 | this.store = {}; 20 | } 21 | 22 | set(uri: string, logicalSystem: string, symbolType: string, symbol: string, definition: Range) { 23 | // initialize 24 | this.store[uri] ||= {}; 25 | this.store[uri][logicalSystem] ||= {}; 26 | this.store[uri][logicalSystem][symbolType] ||= {}; 27 | this.store[uri][logicalSystem][symbolType][symbol] ||= []; 28 | 29 | this.store[uri][logicalSystem][symbolType][symbol].push(definition); 30 | } 31 | 32 | /** 33 | * Return definition, [] when a given symbol is not defined, undefined when the symbol is undefined. 34 | * NOTE: It's important to return undefined in the last case to chain findings. 35 | * 36 | * @param uri 37 | * @param symbolType 38 | * @param symbol 39 | */ 40 | get(uri: string, symbolType: string, symbol: PointedSymbol) { 41 | if (!symbol.symbol) { 42 | return; 43 | } 44 | 45 | return this.store[uri]?.[symbol.logicalSystem]?.[symbolType]?.[symbol.symbol] || []; 46 | } 47 | 48 | /** 49 | * Return all symbol definitions for the given symbolType. 50 | * 51 | * @param uri 52 | * @param logicalSystem 53 | * @param symbolType 54 | */ 55 | getDefinitions(uri: string, logicalSystem: string, symbolType: string) { 56 | return this.store[uri]?.[logicalSystem]?.[symbolType] || {}; 57 | } 58 | 59 | clear(uri: string) { 60 | if (!this.store[uri]) { 61 | return; 62 | } 63 | 64 | this.store[uri] = {}; 65 | } 66 | } 67 | 68 | export type PointedSymbol = { 69 | logicalSystem: string; 70 | symbol?: string; 71 | }; 72 | 73 | export function definition(session: Session) { 74 | return (textDocumentPosition: TextDocumentPositionParams) => { 75 | const doc = session.documents.get(textDocumentPosition.textDocument.uri); 76 | if (!doc) { 77 | return []; 78 | } 79 | 80 | const line = doc.getText().split("\n")[textDocumentPosition.position.line]; 81 | 82 | const definition = 83 | getInterfaceDefinition(session, line, textDocumentPosition) || 84 | getPrefixListDefinition(session, line, textDocumentPosition) || 85 | getPolicyStatementDefinition(session, line, textDocumentPosition) || 86 | getCommunityDefinition(session, line, textDocumentPosition) || 87 | getAsPathDefinition(session, line, textDocumentPosition) || 88 | getAsPathGroupDefinition(session, line, textDocumentPosition) || 89 | getFirewallFilterDefinition(session, line, textDocumentPosition) || 90 | getServiceNatPoolDefinition(session, line, textDocumentPosition) || 91 | getSecurityNatPoolDefinition(session, line, textDocumentPosition) || 92 | getNatAddressDefinition(session, line, textDocumentPosition) || 93 | getPoolAddressDefinition(session, line, textDocumentPosition) || 94 | getGlobalAddressSetAddressDefinition(session, line, textDocumentPosition) || 95 | getZoneSpecificAddressSetAddressDefinition(session, line, textDocumentPosition) || 96 | getPoliciesAddressDefinition(session, line, textDocumentPosition) || 97 | getApplicationDefinition(session, line, textDocumentPosition) || 98 | []; 99 | 100 | return definition.map((d) => Location.create(textDocumentPosition.textDocument.uri, d)); 101 | }; 102 | } 103 | 104 | /** 105 | * Return if the cursor is pointing the symbol which matches a given pattern, or undefined. 106 | * 107 | * @param line 108 | * @param position 109 | * @param pattern 110 | */ 111 | function getPointedSymbol(line: string, position: number, pattern: string) { 112 | const m = line.match( 113 | `(${prefixPattern.source}(?:\\s+logical-systems\\s+(\\S+))?(?:\\s+.*)?\\s+${pattern}\\s+)(\\S+)`, 114 | ); 115 | 116 | // Return nothing when the cursor doesn't point at the keyword 117 | if (!m || m[0].length < position || m[1].length > position) { 118 | return { logicalSystem: "global" }; 119 | } else { 120 | return { logicalSystem: m[2] || "global", symbol: m[3] }; 121 | } 122 | } 123 | 124 | function getInterfaceDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 125 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, "interface"); 126 | return session.definitions.get(textDocumentPosition.textDocument.uri, "interface", symbol); 127 | } 128 | 129 | function getPrefixListDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 130 | const symbol = getPointedSymbol( 131 | line, 132 | textDocumentPosition.position.character, 133 | "from\\s+(?:source-|destination-)?prefix-list", 134 | ); 135 | return session.definitions.get(textDocumentPosition.textDocument.uri, "prefix-list", symbol); 136 | } 137 | 138 | function getPolicyStatementDefinition( 139 | session: Session, 140 | line: string, 141 | textDocumentPosition: TextDocumentPositionParams, 142 | ) { 143 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, "(?:import|export)"); 144 | return session.definitions.get(textDocumentPosition.textDocument.uri, "policy-statement", symbol); 145 | } 146 | 147 | function getCommunityDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 148 | const symbol = getPointedSymbol( 149 | line, 150 | textDocumentPosition.position.character, 151 | "(?:from\\s+community|then\\s+community\\s+(?:add|delete|set))", 152 | ); 153 | return session.definitions.get(textDocumentPosition.textDocument.uri, "community", symbol); 154 | } 155 | 156 | function getAsPathDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 157 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, "from\\s+as-path"); 158 | return session.definitions.get(textDocumentPosition.textDocument.uri, "as-path", symbol); 159 | } 160 | 161 | function getAsPathGroupDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 162 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, "from\\s+as-path-group"); 163 | return session.definitions.get(textDocumentPosition.textDocument.uri, "as-path-group", symbol); 164 | } 165 | 166 | function getFirewallFilterDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 167 | const symbol = getPointedSymbol( 168 | line, 169 | textDocumentPosition.position.character, 170 | "filter\\s+(?:input|output|input-list|output-list)", 171 | ); 172 | return session.definitions.get(textDocumentPosition.textDocument.uri, "firewall-filter", symbol); 173 | } 174 | 175 | function getServiceNatPoolDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 176 | const symbol = getPointedSymbol( 177 | line, 178 | textDocumentPosition.position.character, 179 | "then\\s+translated\\s+(?:source-pool|destination-pool|dns-alg-pool|overload-pool)", 180 | ); 181 | return session.definitions.get(textDocumentPosition.textDocument.uri, "service-nat-pool", symbol); 182 | } 183 | 184 | function getSecurityNatPoolDefinition( 185 | session: Session, 186 | line: string, 187 | textDocumentPosition: TextDocumentPositionParams, 188 | ) { 189 | const m = line.match(/security\s+nat\s+(?:source|destination)\s+.*\s+then\s+(source|destination)-nat\s+pool/); 190 | if (!m) { 191 | return; 192 | } 193 | 194 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, "pool"); 195 | return session.definitions.get(textDocumentPosition.textDocument.uri, `security-nat-pool:${m[1]}`, symbol); 196 | } 197 | 198 | function getNatAddressDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 199 | const symbol = getPointedSymbol( 200 | line, 201 | textDocumentPosition.position.character, 202 | "nat\\s+.*\\s+match\\s+(?:source|destination)-address(?:-name)?", 203 | ); 204 | return session.definitions.get(textDocumentPosition.textDocument.uri, "address:global:global", symbol); 205 | } 206 | 207 | function getPoolAddressDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 208 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, "pool\\s+\\S+\\s+address-name"); 209 | return session.definitions.get(textDocumentPosition.textDocument.uri, "address:global:global", symbol); 210 | } 211 | 212 | function getGlobalAddressSetAddressDefinition( 213 | session: Session, 214 | line: string, 215 | textDocumentPosition: TextDocumentPositionParams, 216 | ) { 217 | const m = line.match(/security\s+address-book\s+(\S+)\s+address-set\s+\S+\s+(address(?:-set)?)/); 218 | if (!m) { 219 | return; 220 | } 221 | 222 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, `address-set\\s+\\S+\\s+${m[2]}`); 223 | return session.definitions.get(textDocumentPosition.textDocument.uri, `${m[2]}:global:${m[1]}`, symbol); 224 | } 225 | 226 | function getZoneSpecificAddressSetAddressDefinition( 227 | session: Session, 228 | line: string, 229 | textDocumentPosition: TextDocumentPositionParams, 230 | ) { 231 | const m = line.match( 232 | /security\s+zones\s+security-zone\s+(\S+)\s+address-book\s+address-set\s+\S+\s+(address(?:-set)?)/, 233 | ); 234 | if (!m) { 235 | return; 236 | } 237 | 238 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, `address-set\\s+\\S+\\s+${m[2]}`); 239 | return session.definitions.get(textDocumentPosition.textDocument.uri, `${m[2]}:${m[1]}:global`, symbol); 240 | } 241 | 242 | function getPoliciesAddressDefinition( 243 | session: Session, 244 | line: string, 245 | textDocumentPosition: TextDocumentPositionParams, 246 | ) { 247 | const m = line.match( 248 | /(?:logical-systems\s+(\S+))?.*\s+policies\s+from-zone\s+(\S+)\s+to-zone\s+(\S+)\s+.*\s+match\s+(source|destination)-address/, 249 | ); 250 | if (!m) { 251 | return; 252 | } 253 | 254 | const zone = m[4] === "source" ? m[2] : m[3]; 255 | const symbol = getPointedSymbol( 256 | line, 257 | textDocumentPosition.position.character, 258 | "policies\\s+.*\\s+match\\s+(?:source|destination)-address", 259 | ); 260 | 261 | const addressBooks = session.zoneAddressBooks.get(textDocumentPosition.textDocument.uri, m[1] || "global", zone); 262 | return [...addressBooks] 263 | .map((a) => [`address:global:${a}`, `address-set:global:${a}`]) 264 | .flat() 265 | .concat([ 266 | "address:global:global", 267 | `address:${zone}:global`, 268 | "address-set:global:global", 269 | `address-set:${zone}:global`, 270 | ]) 271 | .map((a) => session.definitions.get(textDocumentPosition.textDocument.uri, a, symbol)) 272 | .filter((i) => !!i) 273 | .flat(); 274 | } 275 | 276 | function getApplicationDefinition(session: Session, line: string, textDocumentPosition: TextDocumentPositionParams) { 277 | const symbol = getPointedSymbol(line, textDocumentPosition.position.character, "match\\s+application"); 278 | return session.definitions.get(textDocumentPosition.textDocument.uri, "application", symbol); 279 | } 280 | 281 | export function updateDefinitions(session: Session, textDocument: TextDocument) { 282 | session.definitions.clear(textDocument.uri); 283 | 284 | updateInterfaceDefinitions(session, textDocument); 285 | updatePrefixListDefinitions(session, textDocument); 286 | updatePolicyStatementDefinitions(session, textDocument); 287 | updateCommunityDefinitions(session, textDocument); 288 | updateAsPathDefinitions(session, textDocument); 289 | updateAsPathGroupDefinitions(session, textDocument); 290 | updateFirewallFilterDefinitions(session, textDocument); 291 | updateServiceNatPoolDefinitions(session, textDocument); 292 | updateSecurityNatPoolDefinitions(session, textDocument); 293 | updateAddressDefinitions(session, textDocument); 294 | updateApplicationDefinitions(session, textDocument); 295 | } 296 | 297 | function insertDefinitions( 298 | session: Session, 299 | textDocument: TextDocument, 300 | symbolType: string | ((arg: RegExpExecArray) => string), 301 | pattern: string, 302 | modifyFunction: (arg: RegExpExecArray) => string, 303 | ) { 304 | const text = textDocument.getText(); 305 | 306 | // FIXME: We should have implemented with named captures, but avoid them due to performance consideration 307 | const fullPattern = new RegExp(`(${prefixPattern.source}(?:\\s+logical-systems\\s+(\\S+))?\\s+${pattern}`, "gm"); 308 | let m: RegExpExecArray | null; 309 | 310 | while ((m = fullPattern.exec(text))) { 311 | const type = typeof symbolType === "function" ? symbolType(m) : symbolType; 312 | const symbol = modifyFunction(m); 313 | session.definitions.set(textDocument.uri, m[2] || "global", type, symbol, { 314 | start: textDocument.positionAt(m.index + m[1].length), 315 | end: textDocument.positionAt(m.index + m[0].length), 316 | }); 317 | } 318 | } 319 | 320 | function updateInterfaceDefinitions(session: Session, textDocument: TextDocument) { 321 | const type = "interface"; 322 | insertDefinitions(session, textDocument, type, "interfaces\\s+)((?!interface-range)\\S+)", (m) => m[3]); 323 | insertDefinitions(session, textDocument, type, "interfaces interface-range\\s+)(\\S+)", (m) => m[3]); 324 | insertDefinitions( 325 | session, 326 | textDocument, 327 | type, 328 | "interfaces\\s+)(\\S+)\\s+unit\\s+([0-9]+)", 329 | (m) => `${m[3]}.${m[4]}`, // "xe-0/0/0 unit 0" is referred as "xe-0/0/0.0" 330 | ); 331 | } 332 | 333 | function updatePrefixListDefinitions(session: Session, textDocument: TextDocument) { 334 | const type = "prefix-list"; 335 | insertDefinitions(session, textDocument, type, "policy-options\\s+prefix-list\\s+)(\\S+)", (m) => m[3]); 336 | } 337 | 338 | function updatePolicyStatementDefinitions(session: Session, textDocument: TextDocument) { 339 | const type = "policy-statement"; 340 | insertDefinitions(session, textDocument, type, "policy-options\\s+policy-statement\\s+)(\\S+)", (m) => m[3]); 341 | } 342 | 343 | function updateCommunityDefinitions(session: Session, textDocument: TextDocument) { 344 | const type = "community"; 345 | insertDefinitions(session, textDocument, type, "policy-options\\s+community\\s+)(\\S+)", (m) => m[3]); 346 | } 347 | 348 | function updateAsPathDefinitions(session: Session, textDocument: TextDocument) { 349 | const type = "as-path"; 350 | insertDefinitions(session, textDocument, type, "policy-options\\s+as-path\\s+)(\\S+)", (m) => m[3]); 351 | } 352 | 353 | function updateAsPathGroupDefinitions(session: Session, textDocument: TextDocument) { 354 | const type = "as-path-group"; 355 | insertDefinitions(session, textDocument, type, "policy-options\\s+as-path-group\\s+)(\\S+)", (m) => m[3]); 356 | } 357 | 358 | function updateFirewallFilterDefinitions(session: Session, textDocument: TextDocument) { 359 | const type = "firewall-filter"; 360 | insertDefinitions(session, textDocument, type, "firewall(?:\\s+family\\s+\\S+)?\\s+filter\\s+)(\\S+)", (m) => m[3]); 361 | } 362 | 363 | function updateServiceNatPoolDefinitions(session: Session, textDocument: TextDocument) { 364 | const type = "service-nat-pool"; 365 | insertDefinitions(session, textDocument, type, "services\\s+nat\\s+pool\\s+)(\\S+)", (m) => m[3]); 366 | } 367 | 368 | function updateSecurityNatPoolDefinitions(session: Session, textDocument: TextDocument) { 369 | insertDefinitions( 370 | session, 371 | textDocument, 372 | (m) => `security-nat-pool:${m[3]}`, 373 | "security\\s+nat\\s+(source|destination)\\s+pool\\s+)(\\S+)", 374 | (m) => m[4], 375 | ); 376 | } 377 | 378 | function updateAddressDefinitions(session: Session, textDocument: TextDocument) { 379 | session.zoneAddressBooks.clear(textDocument.uri); 380 | updateGlobalAddressDefinitions(session, textDocument); 381 | updateZoneSpecificAddressDefinitions(session, textDocument); 382 | } 383 | 384 | function updateGlobalAddressDefinitions(session: Session, textDocument: TextDocument) { 385 | const text = textDocument.getText(); 386 | 387 | for (const type of ["address", "address-set"]) { 388 | insertDefinitions( 389 | session, 390 | textDocument, 391 | // <"address" or "address-set">:: 392 | (m) => `${type}:global:${m[3]}`, 393 | `security\\s+address-book\\s+(\\S+)\\s+${type}\\s+)(\\S+)`, 394 | (m) => m[4], 395 | ); 396 | } 397 | 398 | // zone address book mapping 399 | const pattern = /^\s*set(?:\s+logical-systems\s+(\S+))?\s+.*\s+address-book\s+(\S+)\s+attach\s+zone\s+(\S+)/gm; 400 | let m: RegExpExecArray | null; 401 | 402 | while ((m = pattern.exec(text))) { 403 | session.zoneAddressBooks.set(textDocument.uri, m[1] || "global", m[3], m[2]); 404 | } 405 | } 406 | 407 | function updateZoneSpecificAddressDefinitions(session: Session, textDocument: TextDocument) { 408 | for (const type of ["address", "address-set"]) { 409 | insertDefinitions( 410 | session, 411 | textDocument, 412 | // <"address" or "address-set">:: 413 | (m) => `${type}:${m[3]}:global`, 414 | `security\\s+zones\\s+security-zone\\s+(\\S+)\\s+address-book\\s+${type}\\s+)(\\S+)`, 415 | (m) => m[4], 416 | ); 417 | } 418 | } 419 | 420 | function updateApplicationDefinitions(session: Session, textDocument: TextDocument) { 421 | const type = "application"; 422 | insertDefinitions(session, textDocument, type, "applications\\s+application\\s+)(\\S+)", (m) => m[3]); 423 | } 424 | -------------------------------------------------------------------------------- /client/src/test/completion.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as vscode from "vscode"; 3 | 4 | import { activate, getDocUri } from "./helper"; 5 | 6 | suite("Should do completion", () => { 7 | const docUri = getDocUri("junos.conf"); 8 | 9 | const rootItems = [ 10 | { label: "access", kind: vscode.CompletionItemKind.Text }, 11 | { label: "access-profile", kind: vscode.CompletionItemKind.Text }, 12 | { label: "accounting-options", kind: vscode.CompletionItemKind.Text }, 13 | { label: "applications", kind: vscode.CompletionItemKind.Text }, 14 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 15 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 16 | { label: "bridge-domains", kind: vscode.CompletionItemKind.Text }, 17 | { label: "chassis", kind: vscode.CompletionItemKind.Text }, 18 | { label: "class-of-service", kind: vscode.CompletionItemKind.Text }, 19 | { label: "diameter", kind: vscode.CompletionItemKind.Text }, 20 | { label: "dynamic-profiles", kind: vscode.CompletionItemKind.Text }, 21 | { label: "ethernet-switching-options", kind: vscode.CompletionItemKind.Text }, 22 | { label: "event-options", kind: vscode.CompletionItemKind.Text }, 23 | { label: "fabric", kind: vscode.CompletionItemKind.Text }, 24 | { label: "firewall", kind: vscode.CompletionItemKind.Text }, 25 | { label: "forwarding-options", kind: vscode.CompletionItemKind.Text }, 26 | { label: "ietf-interfaces:interfaces", kind: vscode.CompletionItemKind.Text }, 27 | { label: "interfaces", kind: vscode.CompletionItemKind.Text }, 28 | { label: "jnx-aug-openconfig-gribi-statistics:gribi-statistics", kind: vscode.CompletionItemKind.Text }, 29 | { label: "jnx-tel-RE-local-interfaces-stats:interfaces", kind: vscode.CompletionItemKind.Text }, 30 | { label: "jsrc", kind: vscode.CompletionItemKind.Text }, 31 | { label: "jsrc-partition", kind: vscode.CompletionItemKind.Text }, 32 | { label: "logical-systems", kind: vscode.CompletionItemKind.Text }, 33 | { label: "multi-chassis", kind: vscode.CompletionItemKind.Text }, 34 | { label: "multicast-snooping-options", kind: vscode.CompletionItemKind.Text }, 35 | { label: "openconfig-acl:acl", kind: vscode.CompletionItemKind.Text }, 36 | { label: "openconfig-bfd:bfd", kind: vscode.CompletionItemKind.Text }, 37 | { label: "openconfig-bgp:bgp", kind: vscode.CompletionItemKind.Text }, 38 | { label: "openconfig-interfaces:interfaces", kind: vscode.CompletionItemKind.Text }, 39 | { label: "openconfig-keychain:keychains", kind: vscode.CompletionItemKind.Text }, 40 | { label: "openconfig-lacp:lacp", kind: vscode.CompletionItemKind.Text }, 41 | { label: "openconfig-lldp:lldp", kind: vscode.CompletionItemKind.Text }, 42 | { label: "openconfig-local-routing:local-routes", kind: vscode.CompletionItemKind.Text }, 43 | { label: "openconfig-macsec:macsec", kind: vscode.CompletionItemKind.Text }, 44 | { label: "openconfig-messages:messages", kind: vscode.CompletionItemKind.Text }, 45 | { label: "openconfig-network-instance:network-instances", kind: vscode.CompletionItemKind.Text }, 46 | { label: "openconfig-platform:components", kind: vscode.CompletionItemKind.Text }, 47 | { label: "openconfig-probes:probes", kind: vscode.CompletionItemKind.Text }, 48 | { label: "openconfig-qos:qos", kind: vscode.CompletionItemKind.Text }, 49 | { label: "openconfig-routing-policy:routing-policy", kind: vscode.CompletionItemKind.Text }, 50 | { label: "openconfig-sampling-sflow:sampling", kind: vscode.CompletionItemKind.Text }, 51 | { label: "openconfig-system:system", kind: vscode.CompletionItemKind.Text }, 52 | { label: "openconfig-telemetry:telemetry-system", kind: vscode.CompletionItemKind.Text }, 53 | { label: "openconfig-terminal-device:terminal-device", kind: vscode.CompletionItemKind.Text }, 54 | { label: "poe", kind: vscode.CompletionItemKind.Text }, 55 | { label: "policy-options", kind: vscode.CompletionItemKind.Text }, 56 | { label: "protocols", kind: vscode.CompletionItemKind.Text }, 57 | { label: "rcsid", kind: vscode.CompletionItemKind.Text }, 58 | { label: "routing-instances", kind: vscode.CompletionItemKind.Text }, 59 | { label: "routing-options", kind: vscode.CompletionItemKind.Text }, 60 | { label: "security", kind: vscode.CompletionItemKind.Text }, 61 | { label: "services", kind: vscode.CompletionItemKind.Text }, 62 | { label: "session-limit-group", kind: vscode.CompletionItemKind.Text }, 63 | { label: "snmp", kind: vscode.CompletionItemKind.Text }, 64 | { label: "switch-options", kind: vscode.CompletionItemKind.Text }, 65 | { label: "system", kind: vscode.CompletionItemKind.Text }, 66 | { label: "tenants", kind: vscode.CompletionItemKind.Text }, 67 | { label: "unified-edge", kind: vscode.CompletionItemKind.Text }, 68 | { label: "version", kind: vscode.CompletionItemKind.Text }, 69 | { label: "virtual-chassis", kind: vscode.CompletionItemKind.Text }, 70 | { label: "vlans", kind: vscode.CompletionItemKind.Text }, 71 | { label: "vmhost", kind: vscode.CompletionItemKind.Text }, 72 | ]; 73 | 74 | test("Completes root config section", async () => { 75 | await testCompletion(docUri, new vscode.Position(2, 4), { 76 | items: [...rootItems, { label: "groups", kind: vscode.CompletionItemKind.Text }].sort((a, b) => 77 | a.label.localeCompare(b.label), 78 | ), 79 | }); 80 | }); 81 | 82 | test("Completes interfaces section", async () => { 83 | await testCompletion(docUri, new vscode.Position(3, 24), { 84 | items: [ 85 | { label: "accounting-profile", kind: vscode.CompletionItemKind.Text }, 86 | { label: "aggregated-ether-options", kind: vscode.CompletionItemKind.Text }, 87 | { label: "aggregated-inline-services-options", kind: vscode.CompletionItemKind.Text }, 88 | { label: "aggregated-sonet-options", kind: vscode.CompletionItemKind.Text }, 89 | { label: "anchor-point", kind: vscode.CompletionItemKind.Text }, 90 | { label: "anchoring-options", kind: vscode.CompletionItemKind.Text }, 91 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 92 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 93 | { label: "arp-l2-validate", kind: vscode.CompletionItemKind.Text }, 94 | { label: "atm-options", kind: vscode.CompletionItemKind.Text }, 95 | { label: "auto-configure", kind: vscode.CompletionItemKind.Text }, 96 | { label: "bypass-queueing-chip", kind: vscode.CompletionItemKind.Text }, 97 | { label: "cascade-port", kind: vscode.CompletionItemKind.Text }, 98 | { label: "cesopsn-options", kind: vscode.CompletionItemKind.Text }, 99 | { label: "clocking", kind: vscode.CompletionItemKind.Text }, 100 | { label: "container-options", kind: vscode.CompletionItemKind.Text }, 101 | { label: "damping", kind: vscode.CompletionItemKind.Text }, 102 | { label: "data-input", kind: vscode.CompletionItemKind.Text }, 103 | { label: "dce", kind: vscode.CompletionItemKind.Text }, 104 | { label: "description", kind: vscode.CompletionItemKind.Text }, 105 | { label: "disable", kind: vscode.CompletionItemKind.Text }, 106 | { label: "ds0-options", kind: vscode.CompletionItemKind.Text }, 107 | { label: "dsl-options", kind: vscode.CompletionItemKind.Text }, 108 | { label: "dsl-sfp-options", kind: vscode.CompletionItemKind.Text }, 109 | { label: "e1-options", kind: vscode.CompletionItemKind.Text }, 110 | { label: "e3-options", kind: vscode.CompletionItemKind.Text }, 111 | { label: "enable", kind: vscode.CompletionItemKind.Text }, 112 | { label: "encapsulation", kind: vscode.CompletionItemKind.Text }, 113 | { label: "es-options", kind: vscode.CompletionItemKind.Text }, 114 | { label: "esi", kind: vscode.CompletionItemKind.Text }, 115 | { label: "ether-options", kind: vscode.CompletionItemKind.Text }, 116 | { label: "ett-options", kind: vscode.CompletionItemKind.Text }, 117 | { label: "external-model-name", kind: vscode.CompletionItemKind.Text }, 118 | { label: "fabric-options", kind: vscode.CompletionItemKind.Text }, 119 | { label: "fastether-options", kind: vscode.CompletionItemKind.Text }, 120 | { label: "fibrechannel-options", kind: vscode.CompletionItemKind.Text }, 121 | { label: "flexible-vlan-tagging", kind: vscode.CompletionItemKind.Text }, 122 | { label: "forwarding-class-accounting", kind: vscode.CompletionItemKind.Text }, 123 | { label: "framing", kind: vscode.CompletionItemKind.Text }, 124 | { label: "ggsn-options", kind: vscode.CompletionItemKind.Text }, 125 | { label: "gigether-options", kind: vscode.CompletionItemKind.Text }, 126 | { label: "gratuitous-arp-reply", kind: vscode.CompletionItemKind.Text }, 127 | { label: "hierarchical-scheduler", kind: vscode.CompletionItemKind.Text }, 128 | { label: "hold-time", kind: vscode.CompletionItemKind.Text }, 129 | { label: "ima-group-options", kind: vscode.CompletionItemKind.Text }, 130 | { label: "ima-link-options", kind: vscode.CompletionItemKind.Text }, 131 | { label: "input-native-vlan-push", kind: vscode.CompletionItemKind.Text }, 132 | { label: "interface-mib", kind: vscode.CompletionItemKind.Text }, 133 | { label: "interface-transmit-statistics", kind: vscode.CompletionItemKind.Text }, 134 | { label: "keepalives", kind: vscode.CompletionItemKind.Text }, 135 | { label: "l2tp-maximum-session", kind: vscode.CompletionItemKind.Text }, 136 | { label: "layer2-policer", kind: vscode.CompletionItemKind.Text }, 137 | { label: "link-degrade-monitor", kind: vscode.CompletionItemKind.Text }, 138 | { label: "link-mode", kind: vscode.CompletionItemKind.Text }, 139 | { label: "lmi", kind: vscode.CompletionItemKind.Text }, 140 | { label: "load-balancing-options", kind: vscode.CompletionItemKind.Text }, 141 | { label: "logical-tunnel-options", kind: vscode.CompletionItemKind.Text }, 142 | { label: "lsq-failure-options", kind: vscode.CompletionItemKind.Text }, 143 | { label: "mac", kind: vscode.CompletionItemKind.Text }, 144 | { label: "media-type", kind: vscode.CompletionItemKind.Text }, 145 | { label: "metadata", kind: vscode.CompletionItemKind.Text }, 146 | { label: "mlfr-uni-nni-bundle-options", kind: vscode.CompletionItemKind.Text }, 147 | { label: "mtu", kind: vscode.CompletionItemKind.Text }, 148 | { label: "multi-chassis-protection", kind: vscode.CompletionItemKind.Text }, 149 | { label: "multicast-statistics", kind: vscode.CompletionItemKind.Text }, 150 | { label: "multiservice-options", kind: vscode.CompletionItemKind.Text }, 151 | { label: "native-vlan-id", kind: vscode.CompletionItemKind.Text }, 152 | { label: "no-bypass-queueing-chip", kind: vscode.CompletionItemKind.Text }, 153 | { label: "no-forwarding-viable", kind: vscode.CompletionItemKind.Text }, 154 | { label: "no-gratuitous-arp-reply", kind: vscode.CompletionItemKind.Text }, 155 | { label: "no-gratuitous-arp-request", kind: vscode.CompletionItemKind.Text }, 156 | { label: "no-interface-mib", kind: vscode.CompletionItemKind.Text }, 157 | { label: "no-keepalives", kind: vscode.CompletionItemKind.Text }, 158 | { label: "no-native-vlan-insert", kind: vscode.CompletionItemKind.Text }, 159 | { label: "no-no-gratuitous-arp-request", kind: vscode.CompletionItemKind.Text }, 160 | { label: "no-partition", kind: vscode.CompletionItemKind.Text }, 161 | { label: "no-per-unit-scheduler", kind: vscode.CompletionItemKind.Text }, 162 | { label: "no-pseudowire-down-on-core-isolation", kind: vscode.CompletionItemKind.Text }, 163 | { label: "no-traps", kind: vscode.CompletionItemKind.Text }, 164 | { label: "number-of-sub-ports", kind: vscode.CompletionItemKind.Text }, 165 | { label: "oam-on-svlan", kind: vscode.CompletionItemKind.Text }, 166 | { label: "och-options", kind: vscode.CompletionItemKind.Text }, 167 | { label: "odu-options", kind: vscode.CompletionItemKind.Text }, 168 | { label: "optics-options", kind: vscode.CompletionItemKind.Text }, 169 | { label: "otn-options", kind: vscode.CompletionItemKind.Text }, 170 | { label: "otu-options", kind: vscode.CompletionItemKind.Text }, 171 | { label: "p4rt", kind: vscode.CompletionItemKind.Text }, 172 | { label: "partition", kind: vscode.CompletionItemKind.Text }, 173 | { label: "passive-monitor-mode", kind: vscode.CompletionItemKind.Text }, 174 | { label: "per-unit-scheduler", kind: vscode.CompletionItemKind.Text }, 175 | { label: "port-mirror-instance", kind: vscode.CompletionItemKind.Text }, 176 | { label: "ppp-options", kind: vscode.CompletionItemKind.Text }, 177 | { label: "promiscuous-mode", kind: vscode.CompletionItemKind.Text }, 178 | { label: "radius-options", kind: vscode.CompletionItemKind.Text }, 179 | { label: "receive-bucket", kind: vscode.CompletionItemKind.Text }, 180 | { label: "redundancy-group", kind: vscode.CompletionItemKind.Text }, 181 | { label: "redundancy-options", kind: vscode.CompletionItemKind.Text }, 182 | { label: "redundant-ether-options", kind: vscode.CompletionItemKind.Text }, 183 | { label: "rpf-stats", kind: vscode.CompletionItemKind.Text }, 184 | { label: "satop-options", kind: vscode.CompletionItemKind.Text }, 185 | { label: "schedulers", kind: vscode.CompletionItemKind.Text }, 186 | { label: "serial-options", kind: vscode.CompletionItemKind.Text }, 187 | { label: "services-options", kind: vscode.CompletionItemKind.Text }, 188 | { label: "shared-interface", kind: vscode.CompletionItemKind.Text }, 189 | { label: "shared-scheduler", kind: vscode.CompletionItemKind.Text }, 190 | { label: "shdsl-options", kind: vscode.CompletionItemKind.Text }, 191 | { label: "sonet-options", kind: vscode.CompletionItemKind.Text }, 192 | { label: "speed", kind: vscode.CompletionItemKind.Text }, 193 | { label: "stacked-vlan-tagging", kind: vscode.CompletionItemKind.Text }, 194 | { label: "switch-options", kind: vscode.CompletionItemKind.Text }, 195 | { label: "t1-options", kind: vscode.CompletionItemKind.Text }, 196 | { label: "t3-options", kind: vscode.CompletionItemKind.Text }, 197 | { label: "targeted-options", kind: vscode.CompletionItemKind.Text }, 198 | { label: "tdm-options", kind: vscode.CompletionItemKind.Text }, 199 | { label: "traceoptions", kind: vscode.CompletionItemKind.Text }, 200 | { label: "transmit-bucket", kind: vscode.CompletionItemKind.Text }, 201 | { label: "traps", kind: vscode.CompletionItemKind.Text }, 202 | { label: "unidirectional", kind: vscode.CompletionItemKind.Text }, 203 | { label: "unit", kind: vscode.CompletionItemKind.Text }, 204 | { label: "unused", kind: vscode.CompletionItemKind.Text }, 205 | { label: "vdsl-options", kind: vscode.CompletionItemKind.Text }, 206 | { label: "vlan-offload", kind: vscode.CompletionItemKind.Text }, 207 | { label: "vlan-tagging", kind: vscode.CompletionItemKind.Text }, 208 | { label: "vlan-vci-tagging", kind: vscode.CompletionItemKind.Text }, 209 | ], 210 | }); 211 | }); 212 | 213 | test("Completes defined prefix-list", async () => { 214 | await testCompletion(docUri, new vscode.Position(4, 67), { 215 | items: [ 216 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 217 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 218 | { label: "foo-prefix", kind: vscode.CompletionItemKind.Text }, 219 | ], 220 | }); 221 | }); 222 | 223 | test("Completes defined policy-statement", async () => { 224 | await testCompletion(docUri, new vscode.Position(5, 41), { 225 | items: [ 226 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 227 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 228 | { label: "foo-statement", kind: vscode.CompletionItemKind.Text }, 229 | ], 230 | }); 231 | }); 232 | 233 | test("Completes defined community", async () => { 234 | await testCompletion(docUri, new vscode.Position(6, 65), { 235 | items: [ 236 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 237 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 238 | { label: "foo-community", kind: vscode.CompletionItemKind.Text }, 239 | ], 240 | }); 241 | }); 242 | 243 | test("Completes defined as-path", async () => { 244 | await testCompletion(docUri, new vscode.Position(7, 63), { 245 | items: [ 246 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 247 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 248 | { label: "foo-as-path", kind: vscode.CompletionItemKind.Text }, 249 | ], 250 | }); 251 | }); 252 | 253 | test("Completes defined as-path-group", async () => { 254 | await testCompletion(docUri, new vscode.Position(8, 69), { 255 | items: [ 256 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 257 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 258 | { label: "foo-as-path-group", kind: vscode.CompletionItemKind.Text }, 259 | ], 260 | }); 261 | }); 262 | 263 | test("Completes defined firewall filter", async () => { 264 | await testCompletion(docUri, new vscode.Position(9, 56), { 265 | items: [ 266 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 267 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 268 | { label: "foo-filter", kind: vscode.CompletionItemKind.Text }, 269 | { label: "precedence", kind: vscode.CompletionItemKind.Text }, 270 | { label: "shared-name", kind: vscode.CompletionItemKind.Text }, 271 | ], 272 | }); 273 | }); 274 | 275 | test("Completes defined nat pool", async () => { 276 | await testCompletion(docUri, new vscode.Position(10, 68), { 277 | items: [ 278 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 279 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 280 | { label: "foo-pool", kind: vscode.CompletionItemKind.Text }, 281 | ], 282 | }); 283 | }); 284 | 285 | test("Completes defined interface-range", async () => { 286 | await testCompletion(docUri, new vscode.Position(11, 29), { 287 | items: [ 288 | { label: "all", kind: vscode.CompletionItemKind.Text }, 289 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 290 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 291 | { label: "foo-interface", kind: vscode.CompletionItemKind.Text }, 292 | { label: "xe-0/0/0", kind: vscode.CompletionItemKind.Text }, 293 | { label: "xe-0/0/0.0", kind: vscode.CompletionItemKind.Text }, 294 | { label: "xe-0/0/1", kind: vscode.CompletionItemKind.Text }, 295 | { label: "xe-0/0/1.0", kind: vscode.CompletionItemKind.Text }, 296 | ], 297 | }); 298 | }); 299 | 300 | test("Completes defined policy-statement only in logical-systems", async () => { 301 | await testCompletion(docUri, new vscode.Position(12, 62), { 302 | items: [ 303 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 304 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 305 | { label: "foo-import", kind: vscode.CompletionItemKind.Text }, 306 | ], 307 | }); 308 | }); 309 | 310 | test("Completes defined prefix-list only in logical-systems", async () => { 311 | await testCompletion(docUri, new vscode.Position(13, 85), { 312 | items: [ 313 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 314 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 315 | { label: "bar-prefix", kind: vscode.CompletionItemKind.Text }, 316 | ], 317 | }); 318 | }); 319 | 320 | test("Completes defined global address-name in nat", async () => { 321 | for (const [line, character] of [ 322 | [14, 81], 323 | [15, 86], 324 | ]) { 325 | await testCompletion(docUri, new vscode.Position(line, character), { 326 | items: [ 327 | { label: "any-unicast", kind: vscode.CompletionItemKind.Text }, 328 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 329 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 330 | { label: "foo-address", kind: vscode.CompletionItemKind.Text }, 331 | ], 332 | }); 333 | } 334 | 335 | for (const [line, character] of [ 336 | [16, 86], 337 | [17, 91], 338 | [18, 51], 339 | ]) { 340 | await testCompletion(docUri, new vscode.Position(line, character), { 341 | items: [ 342 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 343 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 344 | { label: "foo-address", kind: vscode.CompletionItemKind.Text }, 345 | ], 346 | }); 347 | } 348 | }); 349 | 350 | test("Completes defined pool name in nat", async () => { 351 | // global address book 352 | for (const [line, character, pools] of [ 353 | [19, 81, ["foo-pool", "persistent-nat"]], 354 | [20, 91, ["bar-pool"]], 355 | ] as const) { 356 | await testCompletion(docUri, new vscode.Position(line, character), { 357 | items: [ 358 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 359 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 360 | ...pools.map((p: string) => ({ label: p, kind: vscode.CompletionItemKind.Text })), 361 | { label: "word", kind: vscode.CompletionItemKind.Value }, 362 | ], 363 | }); 364 | } 365 | }); 366 | 367 | test("Completes defined address-name in address-book", async () => { 368 | // global address book 369 | for (const [line, character, address] of [ 370 | [21, 79, "bar-address"], 371 | [22, 83, "bar-address-set"], 372 | ] as const) { 373 | await testCompletion(docUri, new vscode.Position(line, character), { 374 | items: [ 375 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 376 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 377 | { label: address, kind: vscode.CompletionItemKind.Text }, 378 | ], 379 | }); 380 | } 381 | 382 | // zone-specific address book 383 | for (const [line, character, address] of [ 384 | [23, 91, "bar-address"], 385 | [24, 95, "bar-address-set"], 386 | ] as const) { 387 | await testCompletion(docUri, new vscode.Position(line, character), { 388 | items: [ 389 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 390 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 391 | { label: address, kind: vscode.CompletionItemKind.Text }, 392 | ], 393 | }); 394 | } 395 | }); 396 | 397 | test("Completes defined address / address-set in security policies", async () => { 398 | for (const [line, character, address] of [ 399 | [25, 86, "bar-address"], 400 | [26, 91, "baz-address"], 401 | [27, 96], 402 | [28, 99], 403 | ] as const) { 404 | await testCompletion(docUri, new vscode.Position(line, character), { 405 | items: [ 406 | { label: "any", kind: vscode.CompletionItemKind.Text }, 407 | { label: "any-ipv4", kind: vscode.CompletionItemKind.Text }, 408 | { label: "any-ipv6", kind: vscode.CompletionItemKind.Text }, 409 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 410 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 411 | ...(address ? [{ label: address, kind: vscode.CompletionItemKind.Text }] : []), 412 | ...(address ? [{ label: `${address}-set`, kind: vscode.CompletionItemKind.Text }] : []), 413 | { label: "foo-address", kind: vscode.CompletionItemKind.Text }, 414 | { label: "foo-address-set", kind: vscode.CompletionItemKind.Text }, 415 | ], 416 | }); 417 | } 418 | }); 419 | 420 | test("Completes defined application in security policies", async () => { 421 | await testCompletion(docUri, new vscode.Position(30, 91), { 422 | items: [ 423 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 424 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 425 | { label: "junos-defaults", kind: vscode.CompletionItemKind.Text }, 426 | { label: "tcp-app", kind: vscode.CompletionItemKind.Text }, 427 | { label: "word", kind: vscode.CompletionItemKind.Value }, 428 | ], 429 | }); 430 | }); 431 | 432 | suite("Completes groups section", async () => { 433 | test("name", async () => { 434 | await testCompletion(docUri, new vscode.Position(32, 11), { 435 | items: [{ label: "word", kind: vscode.CompletionItemKind.Value }], 436 | }); 437 | }); 438 | 439 | test("after name", async () => { 440 | await testCompletion(docUri, new vscode.Position(33, 15), { 441 | items: [...rootItems, { label: "when", kind: vscode.CompletionItemKind.Text }].sort((a, b) => 442 | a.label.localeCompare(b.label), 443 | ), 444 | }); 445 | }); 446 | 447 | test("after when", async () => { 448 | await testCompletion(docUri, new vscode.Position(34, 33), { 449 | items: [ 450 | { label: "apply-groups", kind: vscode.CompletionItemKind.Text }, 451 | { label: "apply-groups-except", kind: vscode.CompletionItemKind.Text }, 452 | { label: "word", kind: vscode.CompletionItemKind.Value }, 453 | ], 454 | }); 455 | }); 456 | }); 457 | }); 458 | 459 | async function testCompletion( 460 | docUri: vscode.Uri, 461 | position: vscode.Position, 462 | expectedCompletionList: vscode.CompletionList, 463 | ) { 464 | await activate(docUri); 465 | 466 | // Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion 467 | const actualCompletionList = await vscode.commands.executeCommand( 468 | "vscode.executeCompletionItemProvider", 469 | docUri, 470 | position, 471 | ); 472 | 473 | // assert.deepEqual(actualCompletionList.items, expectedCompletionList.items); 474 | assert.equal(actualCompletionList.items.length, expectedCompletionList.items.length); 475 | expectedCompletionList.items.forEach((expectedItem, i) => { 476 | const actualItem = actualCompletionList.items[i]; 477 | assert.equal(actualItem.label, expectedItem.label); 478 | assert.equal(actualItem.kind, expectedItem.kind); 479 | }); 480 | } 481 | -------------------------------------------------------------------------------- /client/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "junos-lsp-client", 3 | "version": "0.5.2", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "junos-lsp-client", 9 | "version": "0.5.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "glob": "^11.0.3", 13 | "vscode-languageclient": "^9.0.1" 14 | }, 15 | "devDependencies": { 16 | "@types/vscode": "^1.104.0", 17 | "@vscode/test-electron": "^2.5.2" 18 | }, 19 | "engines": { 20 | "vscode": "^1.75.0" 21 | } 22 | }, 23 | "node_modules/@isaacs/balanced-match": { 24 | "version": "4.0.1", 25 | "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", 26 | "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", 27 | "license": "MIT", 28 | "engines": { 29 | "node": "20 || >=22" 30 | } 31 | }, 32 | "node_modules/@isaacs/brace-expansion": { 33 | "version": "5.0.0", 34 | "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", 35 | "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", 36 | "license": "MIT", 37 | "dependencies": { 38 | "@isaacs/balanced-match": "^4.0.1" 39 | }, 40 | "engines": { 41 | "node": "20 || >=22" 42 | } 43 | }, 44 | "node_modules/@isaacs/cliui": { 45 | "version": "8.0.2", 46 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 47 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 48 | "license": "ISC", 49 | "dependencies": { 50 | "string-width": "^5.1.2", 51 | "string-width-cjs": "npm:string-width@^4.2.0", 52 | "strip-ansi": "^7.0.1", 53 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 54 | "wrap-ansi": "^8.1.0", 55 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 56 | }, 57 | "engines": { 58 | "node": ">=12" 59 | } 60 | }, 61 | "node_modules/@types/vscode": { 62 | "version": "1.105.0", 63 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.105.0.tgz", 64 | "integrity": "sha512-Lotk3CTFlGZN8ray4VxJE7axIyLZZETQJVWi/lYoUVQuqfRxlQhVOfoejsD2V3dVXPSbS15ov5ZyowMAzgUqcw==", 65 | "dev": true, 66 | "license": "MIT" 67 | }, 68 | "node_modules/@vscode/test-electron": { 69 | "version": "2.5.2", 70 | "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", 71 | "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", 72 | "dev": true, 73 | "license": "MIT", 74 | "dependencies": { 75 | "http-proxy-agent": "^7.0.2", 76 | "https-proxy-agent": "^7.0.5", 77 | "jszip": "^3.10.1", 78 | "ora": "^8.1.0", 79 | "semver": "^7.6.2" 80 | }, 81 | "engines": { 82 | "node": ">=16" 83 | } 84 | }, 85 | "node_modules/agent-base": { 86 | "version": "7.1.4", 87 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", 88 | "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", 89 | "dev": true, 90 | "license": "MIT", 91 | "engines": { 92 | "node": ">= 14" 93 | } 94 | }, 95 | "node_modules/ansi-regex": { 96 | "version": "6.2.2", 97 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", 98 | "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", 99 | "license": "MIT", 100 | "engines": { 101 | "node": ">=12" 102 | }, 103 | "funding": { 104 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 105 | } 106 | }, 107 | "node_modules/ansi-styles": { 108 | "version": "6.2.3", 109 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", 110 | "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", 111 | "license": "MIT", 112 | "engines": { 113 | "node": ">=12" 114 | }, 115 | "funding": { 116 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 117 | } 118 | }, 119 | "node_modules/balanced-match": { 120 | "version": "1.0.2", 121 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 122 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 123 | "license": "MIT" 124 | }, 125 | "node_modules/brace-expansion": { 126 | "version": "2.0.2", 127 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 128 | "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 129 | "license": "MIT", 130 | "dependencies": { 131 | "balanced-match": "^1.0.0" 132 | } 133 | }, 134 | "node_modules/chalk": { 135 | "version": "5.6.2", 136 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", 137 | "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", 138 | "dev": true, 139 | "license": "MIT", 140 | "engines": { 141 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 142 | }, 143 | "funding": { 144 | "url": "https://github.com/chalk/chalk?sponsor=1" 145 | } 146 | }, 147 | "node_modules/cli-cursor": { 148 | "version": "5.0.0", 149 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", 150 | "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", 151 | "dev": true, 152 | "license": "MIT", 153 | "dependencies": { 154 | "restore-cursor": "^5.0.0" 155 | }, 156 | "engines": { 157 | "node": ">=18" 158 | }, 159 | "funding": { 160 | "url": "https://github.com/sponsors/sindresorhus" 161 | } 162 | }, 163 | "node_modules/cli-spinners": { 164 | "version": "2.9.2", 165 | "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", 166 | "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", 167 | "dev": true, 168 | "license": "MIT", 169 | "engines": { 170 | "node": ">=6" 171 | }, 172 | "funding": { 173 | "url": "https://github.com/sponsors/sindresorhus" 174 | } 175 | }, 176 | "node_modules/color-convert": { 177 | "version": "2.0.1", 178 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 179 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 180 | "license": "MIT", 181 | "dependencies": { 182 | "color-name": "~1.1.4" 183 | }, 184 | "engines": { 185 | "node": ">=7.0.0" 186 | } 187 | }, 188 | "node_modules/color-name": { 189 | "version": "1.1.4", 190 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 191 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 192 | "license": "MIT" 193 | }, 194 | "node_modules/core-util-is": { 195 | "version": "1.0.3", 196 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 197 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 198 | "dev": true, 199 | "license": "MIT" 200 | }, 201 | "node_modules/cross-spawn": { 202 | "version": "7.0.6", 203 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 204 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 205 | "license": "MIT", 206 | "dependencies": { 207 | "path-key": "^3.1.0", 208 | "shebang-command": "^2.0.0", 209 | "which": "^2.0.1" 210 | }, 211 | "engines": { 212 | "node": ">= 8" 213 | } 214 | }, 215 | "node_modules/debug": { 216 | "version": "4.4.3", 217 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 218 | "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 219 | "dev": true, 220 | "license": "MIT", 221 | "dependencies": { 222 | "ms": "^2.1.3" 223 | }, 224 | "engines": { 225 | "node": ">=6.0" 226 | }, 227 | "peerDependenciesMeta": { 228 | "supports-color": { 229 | "optional": true 230 | } 231 | } 232 | }, 233 | "node_modules/eastasianwidth": { 234 | "version": "0.2.0", 235 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 236 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 237 | "license": "MIT" 238 | }, 239 | "node_modules/emoji-regex": { 240 | "version": "9.2.2", 241 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 242 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 243 | "license": "MIT" 244 | }, 245 | "node_modules/foreground-child": { 246 | "version": "3.3.1", 247 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", 248 | "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", 249 | "license": "ISC", 250 | "dependencies": { 251 | "cross-spawn": "^7.0.6", 252 | "signal-exit": "^4.0.1" 253 | }, 254 | "engines": { 255 | "node": ">=14" 256 | }, 257 | "funding": { 258 | "url": "https://github.com/sponsors/isaacs" 259 | } 260 | }, 261 | "node_modules/get-east-asian-width": { 262 | "version": "1.4.0", 263 | "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", 264 | "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", 265 | "dev": true, 266 | "license": "MIT", 267 | "engines": { 268 | "node": ">=18" 269 | }, 270 | "funding": { 271 | "url": "https://github.com/sponsors/sindresorhus" 272 | } 273 | }, 274 | "node_modules/glob": { 275 | "version": "11.0.3", 276 | "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", 277 | "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", 278 | "license": "ISC", 279 | "dependencies": { 280 | "foreground-child": "^3.3.1", 281 | "jackspeak": "^4.1.1", 282 | "minimatch": "^10.0.3", 283 | "minipass": "^7.1.2", 284 | "package-json-from-dist": "^1.0.0", 285 | "path-scurry": "^2.0.0" 286 | }, 287 | "bin": { 288 | "glob": "dist/esm/bin.mjs" 289 | }, 290 | "engines": { 291 | "node": "20 || >=22" 292 | }, 293 | "funding": { 294 | "url": "https://github.com/sponsors/isaacs" 295 | } 296 | }, 297 | "node_modules/http-proxy-agent": { 298 | "version": "7.0.2", 299 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", 300 | "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", 301 | "dev": true, 302 | "license": "MIT", 303 | "dependencies": { 304 | "agent-base": "^7.1.0", 305 | "debug": "^4.3.4" 306 | }, 307 | "engines": { 308 | "node": ">= 14" 309 | } 310 | }, 311 | "node_modules/https-proxy-agent": { 312 | "version": "7.0.6", 313 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", 314 | "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", 315 | "dev": true, 316 | "license": "MIT", 317 | "dependencies": { 318 | "agent-base": "^7.1.2", 319 | "debug": "4" 320 | }, 321 | "engines": { 322 | "node": ">= 14" 323 | } 324 | }, 325 | "node_modules/immediate": { 326 | "version": "3.0.6", 327 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 328 | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", 329 | "dev": true, 330 | "license": "MIT" 331 | }, 332 | "node_modules/inherits": { 333 | "version": "2.0.4", 334 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 335 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 336 | "dev": true, 337 | "license": "ISC" 338 | }, 339 | "node_modules/is-fullwidth-code-point": { 340 | "version": "3.0.0", 341 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 342 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 343 | "license": "MIT", 344 | "engines": { 345 | "node": ">=8" 346 | } 347 | }, 348 | "node_modules/is-interactive": { 349 | "version": "2.0.0", 350 | "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", 351 | "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", 352 | "dev": true, 353 | "license": "MIT", 354 | "engines": { 355 | "node": ">=12" 356 | }, 357 | "funding": { 358 | "url": "https://github.com/sponsors/sindresorhus" 359 | } 360 | }, 361 | "node_modules/is-unicode-supported": { 362 | "version": "2.1.0", 363 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", 364 | "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", 365 | "dev": true, 366 | "license": "MIT", 367 | "engines": { 368 | "node": ">=18" 369 | }, 370 | "funding": { 371 | "url": "https://github.com/sponsors/sindresorhus" 372 | } 373 | }, 374 | "node_modules/isarray": { 375 | "version": "1.0.0", 376 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 377 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 378 | "dev": true, 379 | "license": "MIT" 380 | }, 381 | "node_modules/isexe": { 382 | "version": "2.0.0", 383 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 384 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 385 | "license": "ISC" 386 | }, 387 | "node_modules/jackspeak": { 388 | "version": "4.1.1", 389 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", 390 | "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", 391 | "license": "BlueOak-1.0.0", 392 | "dependencies": { 393 | "@isaacs/cliui": "^8.0.2" 394 | }, 395 | "engines": { 396 | "node": "20 || >=22" 397 | }, 398 | "funding": { 399 | "url": "https://github.com/sponsors/isaacs" 400 | } 401 | }, 402 | "node_modules/jszip": { 403 | "version": "3.10.1", 404 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", 405 | "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", 406 | "dev": true, 407 | "license": "(MIT OR GPL-3.0-or-later)", 408 | "dependencies": { 409 | "lie": "~3.3.0", 410 | "pako": "~1.0.2", 411 | "readable-stream": "~2.3.6", 412 | "setimmediate": "^1.0.5" 413 | } 414 | }, 415 | "node_modules/lie": { 416 | "version": "3.3.0", 417 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", 418 | "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 419 | "dev": true, 420 | "license": "MIT", 421 | "dependencies": { 422 | "immediate": "~3.0.5" 423 | } 424 | }, 425 | "node_modules/log-symbols": { 426 | "version": "6.0.0", 427 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", 428 | "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", 429 | "dev": true, 430 | "license": "MIT", 431 | "dependencies": { 432 | "chalk": "^5.3.0", 433 | "is-unicode-supported": "^1.3.0" 434 | }, 435 | "engines": { 436 | "node": ">=18" 437 | }, 438 | "funding": { 439 | "url": "https://github.com/sponsors/sindresorhus" 440 | } 441 | }, 442 | "node_modules/log-symbols/node_modules/is-unicode-supported": { 443 | "version": "1.3.0", 444 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", 445 | "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", 446 | "dev": true, 447 | "license": "MIT", 448 | "engines": { 449 | "node": ">=12" 450 | }, 451 | "funding": { 452 | "url": "https://github.com/sponsors/sindresorhus" 453 | } 454 | }, 455 | "node_modules/lru-cache": { 456 | "version": "11.2.2", 457 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", 458 | "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", 459 | "license": "ISC", 460 | "engines": { 461 | "node": "20 || >=22" 462 | } 463 | }, 464 | "node_modules/mimic-function": { 465 | "version": "5.0.1", 466 | "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", 467 | "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", 468 | "dev": true, 469 | "license": "MIT", 470 | "engines": { 471 | "node": ">=18" 472 | }, 473 | "funding": { 474 | "url": "https://github.com/sponsors/sindresorhus" 475 | } 476 | }, 477 | "node_modules/minimatch": { 478 | "version": "10.0.3", 479 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", 480 | "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", 481 | "license": "ISC", 482 | "dependencies": { 483 | "@isaacs/brace-expansion": "^5.0.0" 484 | }, 485 | "engines": { 486 | "node": "20 || >=22" 487 | }, 488 | "funding": { 489 | "url": "https://github.com/sponsors/isaacs" 490 | } 491 | }, 492 | "node_modules/minipass": { 493 | "version": "7.1.2", 494 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 495 | "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 496 | "license": "ISC", 497 | "engines": { 498 | "node": ">=16 || 14 >=14.17" 499 | } 500 | }, 501 | "node_modules/ms": { 502 | "version": "2.1.3", 503 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 504 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 505 | "dev": true, 506 | "license": "MIT" 507 | }, 508 | "node_modules/onetime": { 509 | "version": "7.0.0", 510 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", 511 | "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", 512 | "dev": true, 513 | "license": "MIT", 514 | "dependencies": { 515 | "mimic-function": "^5.0.0" 516 | }, 517 | "engines": { 518 | "node": ">=18" 519 | }, 520 | "funding": { 521 | "url": "https://github.com/sponsors/sindresorhus" 522 | } 523 | }, 524 | "node_modules/ora": { 525 | "version": "8.2.0", 526 | "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", 527 | "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", 528 | "dev": true, 529 | "license": "MIT", 530 | "dependencies": { 531 | "chalk": "^5.3.0", 532 | "cli-cursor": "^5.0.0", 533 | "cli-spinners": "^2.9.2", 534 | "is-interactive": "^2.0.0", 535 | "is-unicode-supported": "^2.0.0", 536 | "log-symbols": "^6.0.0", 537 | "stdin-discarder": "^0.2.2", 538 | "string-width": "^7.2.0", 539 | "strip-ansi": "^7.1.0" 540 | }, 541 | "engines": { 542 | "node": ">=18" 543 | }, 544 | "funding": { 545 | "url": "https://github.com/sponsors/sindresorhus" 546 | } 547 | }, 548 | "node_modules/ora/node_modules/emoji-regex": { 549 | "version": "10.6.0", 550 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", 551 | "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", 552 | "dev": true, 553 | "license": "MIT" 554 | }, 555 | "node_modules/ora/node_modules/string-width": { 556 | "version": "7.2.0", 557 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", 558 | "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", 559 | "dev": true, 560 | "license": "MIT", 561 | "dependencies": { 562 | "emoji-regex": "^10.3.0", 563 | "get-east-asian-width": "^1.0.0", 564 | "strip-ansi": "^7.1.0" 565 | }, 566 | "engines": { 567 | "node": ">=18" 568 | }, 569 | "funding": { 570 | "url": "https://github.com/sponsors/sindresorhus" 571 | } 572 | }, 573 | "node_modules/package-json-from-dist": { 574 | "version": "1.0.1", 575 | "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 576 | "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 577 | "license": "BlueOak-1.0.0" 578 | }, 579 | "node_modules/pako": { 580 | "version": "1.0.11", 581 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 582 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", 583 | "dev": true, 584 | "license": "(MIT AND Zlib)" 585 | }, 586 | "node_modules/path-key": { 587 | "version": "3.1.1", 588 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 589 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 590 | "license": "MIT", 591 | "engines": { 592 | "node": ">=8" 593 | } 594 | }, 595 | "node_modules/path-scurry": { 596 | "version": "2.0.0", 597 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", 598 | "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", 599 | "license": "BlueOak-1.0.0", 600 | "dependencies": { 601 | "lru-cache": "^11.0.0", 602 | "minipass": "^7.1.2" 603 | }, 604 | "engines": { 605 | "node": "20 || >=22" 606 | }, 607 | "funding": { 608 | "url": "https://github.com/sponsors/isaacs" 609 | } 610 | }, 611 | "node_modules/process-nextick-args": { 612 | "version": "2.0.1", 613 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 614 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 615 | "dev": true, 616 | "license": "MIT" 617 | }, 618 | "node_modules/readable-stream": { 619 | "version": "2.3.8", 620 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 621 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 622 | "dev": true, 623 | "license": "MIT", 624 | "dependencies": { 625 | "core-util-is": "~1.0.0", 626 | "inherits": "~2.0.3", 627 | "isarray": "~1.0.0", 628 | "process-nextick-args": "~2.0.0", 629 | "safe-buffer": "~5.1.1", 630 | "string_decoder": "~1.1.1", 631 | "util-deprecate": "~1.0.1" 632 | } 633 | }, 634 | "node_modules/restore-cursor": { 635 | "version": "5.1.0", 636 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", 637 | "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", 638 | "dev": true, 639 | "license": "MIT", 640 | "dependencies": { 641 | "onetime": "^7.0.0", 642 | "signal-exit": "^4.1.0" 643 | }, 644 | "engines": { 645 | "node": ">=18" 646 | }, 647 | "funding": { 648 | "url": "https://github.com/sponsors/sindresorhus" 649 | } 650 | }, 651 | "node_modules/safe-buffer": { 652 | "version": "5.1.2", 653 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 654 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 655 | "dev": true, 656 | "license": "MIT" 657 | }, 658 | "node_modules/semver": { 659 | "version": "7.7.3", 660 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 661 | "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 662 | "license": "ISC", 663 | "bin": { 664 | "semver": "bin/semver.js" 665 | }, 666 | "engines": { 667 | "node": ">=10" 668 | } 669 | }, 670 | "node_modules/setimmediate": { 671 | "version": "1.0.5", 672 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 673 | "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", 674 | "dev": true, 675 | "license": "MIT" 676 | }, 677 | "node_modules/shebang-command": { 678 | "version": "2.0.0", 679 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 680 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 681 | "license": "MIT", 682 | "dependencies": { 683 | "shebang-regex": "^3.0.0" 684 | }, 685 | "engines": { 686 | "node": ">=8" 687 | } 688 | }, 689 | "node_modules/shebang-regex": { 690 | "version": "3.0.0", 691 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 692 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 693 | "license": "MIT", 694 | "engines": { 695 | "node": ">=8" 696 | } 697 | }, 698 | "node_modules/signal-exit": { 699 | "version": "4.1.0", 700 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 701 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 702 | "license": "ISC", 703 | "engines": { 704 | "node": ">=14" 705 | }, 706 | "funding": { 707 | "url": "https://github.com/sponsors/isaacs" 708 | } 709 | }, 710 | "node_modules/stdin-discarder": { 711 | "version": "0.2.2", 712 | "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", 713 | "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", 714 | "dev": true, 715 | "license": "MIT", 716 | "engines": { 717 | "node": ">=18" 718 | }, 719 | "funding": { 720 | "url": "https://github.com/sponsors/sindresorhus" 721 | } 722 | }, 723 | "node_modules/string_decoder": { 724 | "version": "1.1.1", 725 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 726 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 727 | "dev": true, 728 | "license": "MIT", 729 | "dependencies": { 730 | "safe-buffer": "~5.1.0" 731 | } 732 | }, 733 | "node_modules/string-width": { 734 | "version": "5.1.2", 735 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 736 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 737 | "license": "MIT", 738 | "dependencies": { 739 | "eastasianwidth": "^0.2.0", 740 | "emoji-regex": "^9.2.2", 741 | "strip-ansi": "^7.0.1" 742 | }, 743 | "engines": { 744 | "node": ">=12" 745 | }, 746 | "funding": { 747 | "url": "https://github.com/sponsors/sindresorhus" 748 | } 749 | }, 750 | "node_modules/string-width-cjs": { 751 | "name": "string-width", 752 | "version": "4.2.3", 753 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 754 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 755 | "license": "MIT", 756 | "dependencies": { 757 | "emoji-regex": "^8.0.0", 758 | "is-fullwidth-code-point": "^3.0.0", 759 | "strip-ansi": "^6.0.1" 760 | }, 761 | "engines": { 762 | "node": ">=8" 763 | } 764 | }, 765 | "node_modules/string-width-cjs/node_modules/ansi-regex": { 766 | "version": "5.0.1", 767 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 768 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 769 | "license": "MIT", 770 | "engines": { 771 | "node": ">=8" 772 | } 773 | }, 774 | "node_modules/string-width-cjs/node_modules/emoji-regex": { 775 | "version": "8.0.0", 776 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 777 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 778 | "license": "MIT" 779 | }, 780 | "node_modules/string-width-cjs/node_modules/strip-ansi": { 781 | "version": "6.0.1", 782 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 783 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 784 | "license": "MIT", 785 | "dependencies": { 786 | "ansi-regex": "^5.0.1" 787 | }, 788 | "engines": { 789 | "node": ">=8" 790 | } 791 | }, 792 | "node_modules/strip-ansi": { 793 | "version": "7.1.2", 794 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", 795 | "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", 796 | "license": "MIT", 797 | "dependencies": { 798 | "ansi-regex": "^6.0.1" 799 | }, 800 | "engines": { 801 | "node": ">=12" 802 | }, 803 | "funding": { 804 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 805 | } 806 | }, 807 | "node_modules/strip-ansi-cjs": { 808 | "name": "strip-ansi", 809 | "version": "6.0.1", 810 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 811 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 812 | "license": "MIT", 813 | "dependencies": { 814 | "ansi-regex": "^5.0.1" 815 | }, 816 | "engines": { 817 | "node": ">=8" 818 | } 819 | }, 820 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 821 | "version": "5.0.1", 822 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 823 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 824 | "license": "MIT", 825 | "engines": { 826 | "node": ">=8" 827 | } 828 | }, 829 | "node_modules/util-deprecate": { 830 | "version": "1.0.2", 831 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 832 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 833 | "dev": true, 834 | "license": "MIT" 835 | }, 836 | "node_modules/vscode-jsonrpc": { 837 | "version": "8.2.0", 838 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", 839 | "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", 840 | "license": "MIT", 841 | "engines": { 842 | "node": ">=14.0.0" 843 | } 844 | }, 845 | "node_modules/vscode-languageclient": { 846 | "version": "9.0.1", 847 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", 848 | "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", 849 | "license": "MIT", 850 | "dependencies": { 851 | "minimatch": "^5.1.0", 852 | "semver": "^7.3.7", 853 | "vscode-languageserver-protocol": "3.17.5" 854 | }, 855 | "engines": { 856 | "vscode": "^1.82.0" 857 | } 858 | }, 859 | "node_modules/vscode-languageclient/node_modules/minimatch": { 860 | "version": "5.1.6", 861 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 862 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 863 | "license": "ISC", 864 | "dependencies": { 865 | "brace-expansion": "^2.0.1" 866 | }, 867 | "engines": { 868 | "node": ">=10" 869 | } 870 | }, 871 | "node_modules/vscode-languageserver-protocol": { 872 | "version": "3.17.5", 873 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", 874 | "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", 875 | "license": "MIT", 876 | "dependencies": { 877 | "vscode-jsonrpc": "8.2.0", 878 | "vscode-languageserver-types": "3.17.5" 879 | } 880 | }, 881 | "node_modules/vscode-languageserver-types": { 882 | "version": "3.17.5", 883 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", 884 | "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", 885 | "license": "MIT" 886 | }, 887 | "node_modules/which": { 888 | "version": "2.0.2", 889 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 890 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 891 | "license": "ISC", 892 | "dependencies": { 893 | "isexe": "^2.0.0" 894 | }, 895 | "bin": { 896 | "node-which": "bin/node-which" 897 | }, 898 | "engines": { 899 | "node": ">= 8" 900 | } 901 | }, 902 | "node_modules/wrap-ansi": { 903 | "version": "8.1.0", 904 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 905 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 906 | "license": "MIT", 907 | "dependencies": { 908 | "ansi-styles": "^6.1.0", 909 | "string-width": "^5.0.1", 910 | "strip-ansi": "^7.0.1" 911 | }, 912 | "engines": { 913 | "node": ">=12" 914 | }, 915 | "funding": { 916 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 917 | } 918 | }, 919 | "node_modules/wrap-ansi-cjs": { 920 | "name": "wrap-ansi", 921 | "version": "7.0.0", 922 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 923 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 924 | "license": "MIT", 925 | "dependencies": { 926 | "ansi-styles": "^4.0.0", 927 | "string-width": "^4.1.0", 928 | "strip-ansi": "^6.0.0" 929 | }, 930 | "engines": { 931 | "node": ">=10" 932 | }, 933 | "funding": { 934 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 935 | } 936 | }, 937 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 938 | "version": "5.0.1", 939 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 940 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 941 | "license": "MIT", 942 | "engines": { 943 | "node": ">=8" 944 | } 945 | }, 946 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 947 | "version": "4.3.0", 948 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 949 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 950 | "license": "MIT", 951 | "dependencies": { 952 | "color-convert": "^2.0.1" 953 | }, 954 | "engines": { 955 | "node": ">=8" 956 | }, 957 | "funding": { 958 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 959 | } 960 | }, 961 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 962 | "version": "8.0.0", 963 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 964 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 965 | "license": "MIT" 966 | }, 967 | "node_modules/wrap-ansi-cjs/node_modules/string-width": { 968 | "version": "4.2.3", 969 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 970 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 971 | "license": "MIT", 972 | "dependencies": { 973 | "emoji-regex": "^8.0.0", 974 | "is-fullwidth-code-point": "^3.0.0", 975 | "strip-ansi": "^6.0.1" 976 | }, 977 | "engines": { 978 | "node": ">=8" 979 | } 980 | }, 981 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 982 | "version": "6.0.1", 983 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 984 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 985 | "license": "MIT", 986 | "dependencies": { 987 | "ansi-regex": "^5.0.1" 988 | }, 989 | "engines": { 990 | "node": ">=8" 991 | } 992 | } 993 | } 994 | } 995 | --------------------------------------------------------------------------------