├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── dependency-review.yml │ ├── publish.yaml │ └── ci.yaml ├── .gitignore ├── logo └── logo.png ├── src ├── array.ts ├── test │ ├── fixtures │ │ ├── mergeTagInput.yaml │ │ ├── mergeTagExpected.json │ │ ├── longLinesExpectedUnlimited.yaml │ │ ├── longLinesInput.json │ │ ├── longLinesExpectedLimited.yaml │ │ ├── input.yaml │ │ ├── expected.yaml │ │ ├── input.json │ │ └── expected.json │ ├── suite │ │ ├── extension.test.ts │ │ ├── index.ts │ │ └── helpers.test.ts │ ├── testHelpers.ts │ ├── testUtil.ts │ └── runTest.ts ├── onRightClickAndConvertFile.ts ├── onRightClickAndConvertSelectedFiles.ts ├── onRightClickAndConvertDirectoryFiles.ts ├── onPreviewSelection.ts ├── files.ts ├── onConvertSelection.ts ├── extension.ts ├── config.ts ├── helpers.ts ├── onRename.ts └── converter.ts ├── .prettierignore ├── .prettierrc.yaml ├── .husky └── pre-commit ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── .vscodeignore ├── .editorconfig ├── eslint.config.js ├── tsconfig.json ├── webpack.config.js ├── LICENSE ├── CHANGELOG.md ├── README.md └── package.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @hilleer 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | out/ 3 | dist/ 4 | .vscode-test 5 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hilleer/vscode-yaml-plus-json/HEAD/logo/logo.png -------------------------------------------------------------------------------- /src/array.ts: -------------------------------------------------------------------------------- 1 | export const isEmptyArray = (values: T[]) => values.length === 0; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vscode-test/ 2 | dist/ 3 | out/ 4 | src/test/fixtures/ 5 | node_modules/ 6 | package-lock.json 7 | package.json 8 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | trailingComma: all 3 | printWidth: 120 4 | tabWidth: 2 5 | useTabs: false 6 | endOfLine: auto 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | prettier $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --write --ignore-unknown 2 | git update-index --again 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [] 5 | } 6 | -------------------------------------------------------------------------------- /src/test/fixtures/mergeTagInput.yaml: -------------------------------------------------------------------------------- 1 | anchor: &default 2 | first: 1 3 | second: 2 4 | other : the default 5 | alias: *default 6 | merge: 7 | <<: *default 8 | other: not the default 9 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .vscode-test/ 3 | out/ 4 | src/ 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | .wakatime-project 12 | webpack.config.js 13 | node_modules/ -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.md] 11 | max_line_length = off 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /src/test/fixtures/mergeTagExpected.json: -------------------------------------------------------------------------------- 1 | { 2 | "anchor": { 3 | "first": 1, 4 | "second": 2, 5 | "other": "the default" 6 | }, 7 | "alias": { 8 | "first": 1, 9 | "second": 2, 10 | "other": "the default" 11 | }, 12 | "merge": { 13 | "first": 1, 14 | "second": 2, 15 | "other": "not the default" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/test/fixtures/longLinesExpectedUnlimited.yaml: -------------------------------------------------------------------------------- 1 | one: Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book 2 | two: Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book 3 | -------------------------------------------------------------------------------- /src/test/fixtures/longLinesInput.json: -------------------------------------------------------------------------------- 1 | { 2 | "one": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book", 3 | "two": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book" 4 | } 5 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | // import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | // import * as vscode from 'vscode'; 6 | // import * as myExtension from '../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | // vscode.window.showInformationMessage('Start all tests.'); 10 | // test('Sample test', () => { 11 | // assert.equal(-1, [1, 2, 3].indexOf(5)); 12 | // assert.equal(-1, [1, 2, 3].indexOf(0)); 13 | // }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/test/fixtures/longLinesExpectedLimited.yaml: -------------------------------------------------------------------------------- 1 | one: Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been 2 | the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of 3 | type and scrambled it to make a type specimen book 4 | two: Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been 5 | the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of 6 | type and scrambled it to make a type specimen book 7 | -------------------------------------------------------------------------------- /src/test/testHelpers.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | import * as path from 'path'; 3 | 4 | // __dirname points to compiled folder in "out/" folder 5 | const fixturesRootDir = path.join(__dirname, '..', '..', 'src', 'test', 'fixtures'); 6 | export const loadFixture = (fixtureFileName: string) => 7 | fs.readFile(path.join(fixturesRootDir, fixtureFileName), 'utf-8'); 8 | 9 | export const loadFixtures = async (...fixtures: string[]) => 10 | Promise.all(fixtures.map((fixture) => loadFixture(fixture))); 11 | 12 | export const stripNewLines = (value: string) => value.replace(/\r?\n|\r/g, ''); 13 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const globals = require('globals'); 2 | const pluginJs = require('@eslint/js'); 3 | const tseslint = require('typescript-eslint'); 4 | const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended'); 5 | 6 | module.exports = [ 7 | { 8 | files: ['**/*.{js,ts}'], 9 | languageOptions: { 10 | globals: globals.node, 11 | ecmaVersion: 6, 12 | sourceType: 'module', 13 | }, 14 | rules: { 15 | semi: 0, 16 | }, 17 | }, 18 | pluginJs.configs.recommended, 19 | ...tseslint.configs.recommended, 20 | eslintPluginPrettierRecommended, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/test/testUtil.ts: -------------------------------------------------------------------------------- 1 | import * as Sinon from 'sinon'; 2 | import * as vscode from 'vscode'; 3 | import { ConfigId, Configs } from '../config'; 4 | 5 | type ConfigInput = Partial; 6 | 7 | export class WorkspaceConfigurationMock { 8 | private stub: Sinon.SinonStub; 9 | 10 | constructor(configMock: ConfigInput = {}) { 11 | this.stub = Sinon.stub(vscode.workspace, 'getConfiguration'); 12 | 13 | this.stub.returns({ 14 | get: (configKey: ConfigId) => configMock[configKey], 15 | } as vscode.WorkspaceConfiguration); 16 | } 17 | 18 | restore() { 19 | this.stub.restore(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6"], 7 | "noUnusedLocals": true, 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true /* enable all strict type-checking options */, 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | "skipLibCheck": true 16 | }, 17 | "exclude": ["node_modules", ".vscode-test"] 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement, feature 6 | assignees: hilleer 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: hilleer 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Create a file '...' with content '....' 16 | 2. Use command '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **(please complete the following information):** 26 | 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | - Vscode version [e.g. 1.54.3] 30 | - Extension version [e.g. 1.7.0] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 'use strict'; 3 | 4 | const path = require('path'); 5 | 6 | /**@type {import('webpack').Configuration}*/ 7 | const config = { 8 | target: 'node', 9 | entry: './src/extension.ts', 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: 'extension.js', 13 | libraryTarget: 'commonjs2', 14 | devtoolModuleFilenameTemplate: '../[resource-path]', 15 | }, 16 | devtool: 'source-map', 17 | externals: { 18 | vscode: 'commonjs vscode', 19 | }, 20 | resolve: { 21 | extensions: ['.ts', '.js'], 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.ts$/, 27 | exclude: /node_modules/, 28 | use: [ 29 | { 30 | loader: 'ts-loader', 31 | }, 32 | ], 33 | }, 34 | ], 35 | }, 36 | }; 37 | 38 | module.exports = config; 39 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | process.on('uncaughtException', () => {}); // ignore those, expected, should still be able to rely on tests 6 | 7 | async function main() { 8 | try { 9 | // The folder containing the Extension Manifest package.json 10 | // Passed to `--extensionDevelopmentPath` 11 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 12 | 13 | // The path to test runner 14 | // Passed to --extensionTestsPath 15 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 16 | 17 | // Download VS Code, unzip it and run the integration test 18 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 19 | } catch (err) { 20 | console.error(err); 21 | console.error('Failed to run tests'); 22 | process.exit(1); 23 | } 24 | } 25 | 26 | main(); 27 | -------------------------------------------------------------------------------- /src/onRightClickAndConvertFile.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { FileConverter, ConvertFromType } from './converter'; 4 | 5 | const jsonFileConverter = new FileConverter(ConvertFromType.Json); 6 | const yamlFileConverter = new FileConverter(ConvertFromType.Yaml); 7 | 8 | export async function onRightClickAndConvertJsonFile(oldUri: vscode.Uri) { 9 | if (!oldUri) { 10 | oldUri = getActiveTextEditorUri(); 11 | } 12 | 13 | await jsonFileConverter.convertFiles([oldUri]); 14 | } 15 | 16 | export async function onRightClickAndConvertYamlFile(oldUri: vscode.Uri) { 17 | if (!oldUri) { 18 | oldUri = getActiveTextEditorUri(); 19 | } 20 | 21 | await yamlFileConverter.convertFiles([oldUri]); 22 | } 23 | 24 | function getActiveTextEditorUri() { 25 | const editor = vscode.window.activeTextEditor; 26 | if (!editor) { 27 | throw new Error('Failed to get active text editor'); 28 | } 29 | return editor.document.uri; 30 | } 31 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import { glob } from 'glob'; 4 | 5 | // Create the mocha test 6 | const mocha = new Mocha({ 7 | ui: 'tdd', 8 | color: true, 9 | }); 10 | 11 | const testsRoot = path.resolve(__dirname, '..'); 12 | 13 | export function run(): Promise { 14 | // eslint-disable-next-line no-async-promise-executor 15 | return new Promise(async (c, e) => { 16 | const files = await glob('**/**.test.js', { cwd: testsRoot }); 17 | // Add files to the test suite 18 | files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f))); 19 | 20 | try { 21 | // Run the mocha test 22 | mocha.run((failures) => { 23 | if (failures > 0) { 24 | return e(new Error(`${failures} tests failed.`)); 25 | } 26 | 27 | c(); 28 | }); 29 | } catch (err) { 30 | console.error(err); 31 | e(err); 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/onRightClickAndConvertSelectedFiles.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | 4 | import { ConvertFromType, FileConverter } from './converter'; 5 | 6 | export async function onConvertSelectedYamlFilesToJson(clickedFile: vscode.Uri, selections: vscode.Uri[]) { 7 | const files = selections.filter(createExtensionNameFilter(['.yaml', '.yml'])); 8 | 9 | const yamlFileConverter = new FileConverter(ConvertFromType.Yaml); 10 | await yamlFileConverter.convertFiles(files); 11 | } 12 | 13 | export async function onConvertSelectedJsonFilesToYaml(clickedFile: vscode.Uri, selections: vscode.Uri[]) { 14 | const files = selections.filter(createExtensionNameFilter(['.json'])); 15 | 16 | const jsonFileConverter = new FileConverter(ConvertFromType.Json); 17 | await jsonFileConverter.convertFiles(files); 18 | } 19 | 20 | function createExtensionNameFilter(extensions: string[]) { 21 | return (uri: vscode.Uri) => extensions.includes(path.extname(uri.fsPath)); 22 | } 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm # See documentation for possible values 4 | directory: '/' # Location of package manifests 5 | schedule: 6 | interval: 'weekly' 7 | labels: 8 | - 'dependencies' 9 | - 'npm' 10 | groups: 11 | prod-updates: 12 | dependency-type: production 13 | update-types: 14 | - 'minor' 15 | - 'patch' 16 | dev-dependencies: 17 | dependency-type: development 18 | update-types: 19 | - 'major' 20 | - 'minor' 21 | - 'patch' 22 | - package-ecosystem: 'github-actions' 23 | directory: '.github/workflows' # Location of workflow files 24 | labels: 25 | - 'dependencies' 26 | - 'github-actions' 27 | schedule: 28 | interval: 'weekly' 29 | commit-message: 30 | prefix: 'github-actions (deps)' 31 | groups: 32 | patch-minor-updates: 33 | update-types: 34 | - 'patch' 35 | - 'minor' 36 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1 21 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | # based on https://github.com/marketplace/actions/publish-vs-code-extension 2 | 3 | name: publish extension 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | ci: 10 | uses: ./.github/workflows/ci.yaml 11 | secrets: inherit 12 | 13 | publish-open-vsx: 14 | needs: ci 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 3 17 | steps: 18 | - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 19 | - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 20 | with: 21 | node-version: 16 22 | - run: npm ci 23 | - name: Publish to Open VSX Registry 24 | uses: HaaLeo/publish-vscode-extension@ca5561daa085dee804bf9f37fe0165785a9b14db # 2.0.0 25 | with: 26 | pat: ${{ secrets.OPEN_VSX_TOKEN }} 27 | 28 | - name: Publish to Visual Studio Marketplace 29 | uses: HaaLeo/publish-vscode-extension@ca5561daa085dee804bf9f37fe0165785a9b14db # 2.0.0 30 | with: 31 | pat: ${{ secrets.VS_MARKETPLACE_TOKEN }} 32 | registryUrl: https://marketplace.visualstudio.com 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniel Hillmann 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 15 | "preLaunchTask": "${defaultBuildTask}" 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 25 | ], 26 | "outFiles": ["${workspaceFolder}/out/test/**/*.js"], 27 | "preLaunchTask": "${defaultBuildTask}" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/onRightClickAndConvertDirectoryFiles.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { isEmptyArray } from './array'; 4 | import { ConvertFromType, FileConverter } from './converter'; 5 | import { getFilesInDirectory } from './files'; 6 | 7 | export async function onRightClickAndConvertJsonFilesToYaml(uri: vscode.Uri): Promise { 8 | const files = await getFilesInDirectory(uri, 'json'); 9 | 10 | if (!files || isEmptyArray(files)) { 11 | vscode.window.showInformationMessage('Did not find any json files in the selected directory'); 12 | return; 13 | } 14 | 15 | const jsonFileConverter = new FileConverter(ConvertFromType.Json); 16 | await jsonFileConverter.convertFiles(files); 17 | } 18 | 19 | export async function onRightClickConvertYamlFilesToJson(uri: vscode.Uri): Promise { 20 | const files = await getFilesInDirectory(uri, ['yaml', 'yml']); 21 | 22 | if (!files || isEmptyArray(files)) { 23 | vscode.window.showInformationMessage('Did not find any yaml files in the selected directory'); 24 | return; 25 | } 26 | 27 | const yamlFileConverter = new FileConverter(ConvertFromType.Yaml); 28 | await yamlFileConverter.convertFiles(files); 29 | } 30 | -------------------------------------------------------------------------------- /src/test/fixtures/input.yaml: -------------------------------------------------------------------------------- 1 | name: Titanium.UI.iOS.BlurView 2 | summary: | 3 | A object gives you an easy way implement some complex visual effects. 4 | The blur effect is applied to every view the blur view is added to by default. You can also place the 5 | blur view above other views and all visible views layered under the blur view are blurred as well. 6 | For more information on BlurView, please refer to the official [Apple documentation](https://developer.apple.com/documentation/uikit/uivisualeffectview). 7 | Note: Apple introduced two new constants and in 8 | iOS 10. These are internally mapped to and . 9 | extends: Titanium.UI.View 10 | platforms: [iphone, ipad, macos] 11 | since: {iphone: "5.4.0", ipad: "5.4.0", macos: "9.2.0"} 12 | osver: {"ios":{"min": "8.0"}} 13 | properties: 14 | - name: effect 15 | summary: The effect you provide for the view. 16 | type: Number 17 | constants: Titanium.UI.iOS.BLUR_EFFECT_STYLE_* 18 | default: undefined (no effect is applied) -------------------------------------------------------------------------------- /src/test/fixtures/expected.yaml: -------------------------------------------------------------------------------- 1 | name: Titanium.UI.iOS.BlurView 2 | summary: "A object gives you an easy way implement 3 | some complex visual effects. The blur effect is applied to every view the blur 4 | view is added to by default. You can also place the blur view above other 5 | views and all visible views layered under the blur view are blurred as well. 6 | For more information on BlurView, please refer to the official [Apple 7 | documentation](https://developer.apple.com/documentation/uikit/uivisualeffect\ 8 | view). Note: Apple introduced two new constants 9 | and 10 | in iOS 10. These are internally 11 | mapped to and 12 | ." 13 | extends: Titanium.UI.View 14 | platforms: 15 | - iphone 16 | - ipad 17 | - macos 18 | since: 19 | iphone: 5.4.0 20 | ipad: 5.4.0 21 | macos: 9.2.0 22 | osver: 23 | ios: 24 | min: "8.0" 25 | properties: 26 | - name: effect 27 | summary: The effect you provide for the view. 28 | type: Number 29 | constants: Titanium.UI.iOS.BLUR_EFFECT_STYLE_* 30 | default: undefined (no effect is applied) 31 | -------------------------------------------------------------------------------- /src/onPreviewSelection.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { ConvertFromType } from './converter'; 4 | import { getSelectionConverter } from './onConvertSelection'; 5 | 6 | export function onPreviewSelection(fromType: ConvertFromType) { 7 | const converter = getSelectionConverter(fromType); 8 | 9 | return async () => { 10 | try { 11 | const editor = vscode.window.activeTextEditor; 12 | if (!editor) { 13 | throw new Error('editor not found'); 14 | } 15 | 16 | const { selection, document } = editor; 17 | const text = document.getText(selection); 18 | 19 | const previewText = converter(text); 20 | const previewDocument = await vscode.workspace.openTextDocument({ 21 | content: previewText, 22 | language: getTextDocumentLanguage(fromType), 23 | }); 24 | 25 | await vscode.window.showTextDocument(previewDocument); 26 | } catch (error) { 27 | console.error(error); 28 | vscode.window.showErrorMessage(`an error occurred converting content from ${fromType.toLowerCase()}`); 29 | } 30 | }; 31 | } 32 | 33 | function getTextDocumentLanguage(fromType: ConvertFromType) { 34 | return { 35 | [ConvertFromType.Json]: 'yaml', 36 | [ConvertFromType.Yaml]: 'json', 37 | }[fromType]; 38 | } 39 | -------------------------------------------------------------------------------- /src/test/fixtures/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Titanium.UI.iOS.BlurView", 3 | "summary": "A object gives you an easy way implement some complex visual effects. The blur effect is applied to every view the blur view is added to by default. You can also place the blur view above other views and all visible views layered under the blur view are blurred as well. For more information on BlurView, please refer to the official [Apple documentation](https://developer.apple.com/documentation/uikit/uivisualeffectview). Note: Apple introduced two new constants and in iOS 10. These are internally mapped to and .", 4 | "extends": "Titanium.UI.View", 5 | "platforms": [ 6 | "iphone", 7 | "ipad", 8 | "macos" 9 | ], 10 | "since": { 11 | "iphone": "5.4.0", 12 | "ipad": "5.4.0", 13 | "macos": "9.2.0" 14 | }, 15 | "osver": { 16 | "ios": { 17 | "min": "8.0" 18 | } 19 | }, 20 | "properties": [ 21 | { 22 | "name": "effect", 23 | "summary": "The effect you provide for the view.", 24 | "type": "Number", 25 | "constants": "Titanium.UI.iOS.BLUR_EFFECT_STYLE_*", 26 | "default": "undefined (no effect is applied)" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs 3 | 4 | name: extension CI 5 | 6 | on: 7 | push: 8 | branches: ['main'] 9 | pull_request: 10 | branches: ['main'] 11 | workflow_call: # allow to be called from other workflows 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | node-version: [18, 20, 22, latest] 18 | os: [macos-latest, ubuntu-latest, windows-latest] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | runs-on: ${{matrix.os}} 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm run lint 32 | - run: xvfb-run -a npm test 33 | if: runner.os == 'Linux' 34 | - run: npm test 35 | if: runner.os != 'Linux' 36 | -------------------------------------------------------------------------------- /src/test/fixtures/expected.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Titanium.UI.iOS.BlurView", 3 | "summary": "A object gives you an easy way implement some complex visual effects.\nThe blur effect is applied to every view the blur view is added to by default. You can also place the\nblur view above other views and all visible views layered under the blur view are blurred as well.\nFor more information on BlurView, please refer to the official [Apple documentation](https://developer.apple.com/documentation/uikit/uivisualeffectview).\nNote: Apple introduced two new constants and in\niOS 10. These are internally mapped to and .\n", 4 | "extends": "Titanium.UI.View", 5 | "platforms": [ 6 | "iphone", 7 | "ipad", 8 | "macos" 9 | ], 10 | "since": { 11 | "iphone": "5.4.0", 12 | "ipad": "5.4.0", 13 | "macos": "9.2.0" 14 | }, 15 | "osver": { 16 | "ios": { 17 | "min": "8.0" 18 | } 19 | }, 20 | "properties": [ 21 | { 22 | "name": "effect", 23 | "summary": "The effect you provide for the view.", 24 | "type": "Number", 25 | "constants": "Titanium.UI.iOS.BLUR_EFFECT_STYLE_*", 26 | "default": "undefined (no effect is applied)" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /src/files.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | 4 | type FileExtension = 'json' | 'yaml' | 'yml'; 5 | 6 | export async function getFilesInDirectory(uri: vscode.Uri, fileExtensions: FileExtension | FileExtension[]) { 7 | const { fsPath, scheme } = uri; 8 | 9 | if (scheme !== 'file') { 10 | vscode.window.showErrorMessage('Unexpected file scheme'); 11 | return; 12 | } 13 | 14 | const stat = await vscode.workspace.fs.stat(uri); 15 | const isDirectory = stat.type === vscode.FileType.Directory; 16 | 17 | if (!isDirectory) { 18 | vscode.window.showInformationMessage('The selection was not recognised as a directory'); 19 | return; 20 | } 21 | 22 | const getFileUri = ([filePath]: [string, vscode.FileType]) => vscode.Uri.file(path.join(fsPath, filePath)); 23 | 24 | const directoryFiles = await vscode.workspace.fs.readDirectory(uri); 25 | 26 | if (!Array.isArray(fileExtensions)) { 27 | fileExtensions = [fileExtensions]; 28 | } 29 | 30 | return directoryFiles.filter(filterMatchingFilesInDirectory(fileExtensions)).map(getFileUri); 31 | } 32 | 33 | function filterMatchingFilesInDirectory(fileExtensions: FileExtension[]) { 34 | return ([filePath, fileType]: [string, vscode.FileType]) => 35 | fileType === vscode.FileType.File && 36 | fileExtensions.some((extension) => isMatchingFileExtension(filePath, extension)); 37 | } 38 | 39 | function isMatchingFileExtension(filePath: string, extension: string) { 40 | const fileExtension = path.extname(filePath); 41 | 42 | return fileExtension.includes(extension); 43 | } 44 | -------------------------------------------------------------------------------- /src/onConvertSelection.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ConvertFromType } from './converter'; 3 | 4 | import { getJsonFromYaml, getYamlFromJson, showError } from './helpers'; 5 | 6 | export function onConvertSelection(fromType: ConvertFromType) { 7 | const converter = getSelectionConverter(fromType); 8 | 9 | return async () => { 10 | try { 11 | const editor = vscode.window.activeTextEditor; 12 | 13 | if (!editor) { 14 | return; 15 | } 16 | 17 | const { selection, document } = editor; 18 | const text = document.getText(selection); 19 | const newText = converter(text); 20 | 21 | const range = getSelectionRange(selection); 22 | 23 | await replaceSelection(document, range, newText); 24 | const { end } = selection; 25 | editor.selection = new vscode.Selection(end, end); 26 | } catch (error) { 27 | showError(error); 28 | } 29 | }; 30 | } 31 | 32 | export function getSelectionConverter(fromType: ConvertFromType) { 33 | return { 34 | [ConvertFromType.Json]: getYamlFromJson, 35 | [ConvertFromType.Yaml]: getJsonFromYaml, 36 | }[fromType]; 37 | } 38 | 39 | function getSelectionRange(selection: vscode.Selection) { 40 | const { start, end } = selection; 41 | const range = new vscode.Range(start, end); 42 | 43 | return range; 44 | } 45 | 46 | async function replaceSelection(document: vscode.TextDocument, range: vscode.Range, replacement: string) { 47 | const { uri } = document; 48 | 49 | const edit = new vscode.WorkspaceEdit(); 50 | 51 | try { 52 | edit.replace(uri, range, replacement); 53 | await vscode.workspace.applyEdit(edit); 54 | } catch (error) { 55 | showError(error); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { onRename } from './onRename'; 4 | import { onRightClickAndConvertJsonFile, onRightClickAndConvertYamlFile } from './onRightClickAndConvertFile'; 5 | import { 6 | onRightClickAndConvertJsonFilesToYaml, 7 | onRightClickConvertYamlFilesToJson, 8 | } from './onRightClickAndConvertDirectoryFiles'; 9 | import { 10 | onConvertSelectedJsonFilesToYaml, 11 | onConvertSelectedYamlFilesToJson, 12 | } from './onRightClickAndConvertSelectedFiles'; 13 | import { onConvertSelection } from './onConvertSelection'; 14 | import { ConvertFromType } from './converter'; 15 | import { onPreviewSelection } from './onPreviewSelection'; 16 | 17 | const { registerCommand } = vscode.commands; 18 | 19 | export function activate(context: vscode.ExtensionContext) { 20 | context.subscriptions.push( 21 | registerCommand('extension.rightClickJson', onRightClickAndConvertJsonFile), 22 | registerCommand('extension.rightClickYaml', onRightClickAndConvertYamlFile), 23 | registerCommand('extension.yamlSelectionToJson', onConvertSelection(ConvertFromType.Yaml)), 24 | registerCommand('extension.jsonSelectionToYaml', onConvertSelection(ConvertFromType.Json)), 25 | registerCommand('extension.convertYamlFilesToJson', onRightClickConvertYamlFilesToJson), 26 | registerCommand('extension.convertJsonFilesToYaml', onRightClickAndConvertJsonFilesToYaml), 27 | registerCommand('extension.convertJsonSelectionsToYaml', onConvertSelectedJsonFilesToYaml), 28 | registerCommand('extension.convertYamlSelectionsToJson', onConvertSelectedYamlFilesToJson), 29 | registerCommand('extension.previewAsYaml', onPreviewSelection(ConvertFromType.Json)), 30 | registerCommand('extension.previewAsJson', onPreviewSelection(ConvertFromType.Yaml)), 31 | ); 32 | 33 | vscode.workspace.onDidRenameFiles(onRename); 34 | } 35 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export enum ConfigId { 4 | ConvertOnRename = 'convertOnRename', 5 | FileExtensionsJson = 'fileExtensions.json', 6 | FileExtensionsYaml = 'fileExtensions.yaml', 7 | KeepOriginalFiles = 'keepOriginalFiles', 8 | OverwriteExistentFiles = 'overwriteExistentFiles', 9 | YamlIndent = 'yamlIndent', 10 | YamlSchema = 'yamlSchema', 11 | YamlLineWidth = 'yamlLineWidth', 12 | YamlMerge = 'yamlMerge', 13 | YamlOptions = 'yamlOptions', 14 | JsonOptions = 'jsonOptions', 15 | } 16 | 17 | export type Configs = { 18 | [ConfigId.ConvertOnRename]?: boolean; 19 | [ConfigId.FileExtensionsJson]?: string; 20 | [ConfigId.FileExtensionsYaml]?: string; 21 | [ConfigId.KeepOriginalFiles]?: 'ask' | 'always'; 22 | [ConfigId.OverwriteExistentFiles]?: 'ask' | 'always'; 23 | [ConfigId.YamlIndent]?: number; 24 | [ConfigId.YamlLineWidth]?: number; 25 | [ConfigId.YamlMerge]?: boolean; 26 | [ConfigId.YamlOptions]?: object; 27 | [ConfigId.YamlSchema]?: 'core' | 'failsafe' | 'json' | 'yaml-1.1'; 28 | [ConfigId.JsonOptions]?: object; 29 | }; 30 | 31 | enum ConfigIdLegacy { 32 | // Same key as new - only here for convenience/transparency 33 | ConvertOnRename = 'convertOnRename', 34 | Indent = 'yaml-indent', 35 | } 36 | 37 | const EXTENSION_CONFIG_ID = 'yaml-plus-json'; 38 | 39 | // TODO set extended type of generic 40 | export function getConfig(configId: ConfigId | `${ConfigId}`): T | undefined { 41 | const config = vscode.workspace.getConfiguration(EXTENSION_CONFIG_ID); 42 | 43 | const legacyConfigKey = getLegacyConfigKey(configId as ConfigId); 44 | 45 | return config.get(legacyConfigKey) || config.get(configId); 46 | } 47 | 48 | /** 49 | * @deprecated do not add new configs here 50 | */ 51 | const LEGACY_CONFIGS = Object.freeze({ 52 | [ConfigId.ConvertOnRename]: ConfigIdLegacy.ConvertOnRename, 53 | [ConfigId.YamlIndent]: ConfigIdLegacy.Indent, 54 | }); 55 | 56 | function getLegacyConfigKey(configId: ConfigId) { 57 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 58 | // @ts-expect-error 59 | return LEGACY_CONFIGS[configId]; 60 | } 61 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as YAML from 'yaml'; 3 | 4 | import { ConfigId, Configs, getConfig } from './config'; 5 | 6 | const DEFAULT_ERROR_MESSAGE = 7 | 'Something went wrong, please validate your file and try again or create an issue if the problem persist'; 8 | 9 | /** 10 | * prints errors to console and shows its error message to the user. 11 | */ 12 | export function showError(error: unknown) { 13 | console.error(error); 14 | 15 | const message = error instanceof Error ? error.message : DEFAULT_ERROR_MESSAGE; 16 | 17 | vscode.window.showErrorMessage(message); 18 | } 19 | 20 | export function getYamlFromJson(json: string): string { 21 | const indent = getConfig(ConfigId.YamlIndent); 22 | const schema = getConfig(ConfigId.YamlSchema); 23 | const lineWidth = getConfig(ConfigId.YamlLineWidth); 24 | const options = getConfig(ConfigId.YamlOptions) || {}; 25 | const merge = getConfig(ConfigId.YamlMerge) ?? true; 26 | 27 | try { 28 | const jsonObject = JSON.parse(json); 29 | 30 | return YAML.stringify(jsonObject, { 31 | ...options, // do first so specific options take precedence 32 | ...(indent !== undefined && { indent }), 33 | ...(schema !== undefined && { schema }), 34 | ...(lineWidth !== undefined && { lineWidth }), 35 | merge, 36 | }); 37 | } catch (error) { 38 | console.error(error); 39 | throw new Error('Failed to parse YAML. Please make sure it has a valid format and try again.'); 40 | } 41 | } 42 | 43 | export function getJsonFromYaml(yaml: string): string { 44 | const schema = getConfig(ConfigId.YamlSchema); 45 | const options = getConfig(ConfigId.JsonOptions) || {}; 46 | 47 | try { 48 | const json = YAML.parse(yaml, { 49 | ...options, // do first so specific options take precedence 50 | merge: true, 51 | ...(schema && { schema }), 52 | }); 53 | 54 | return JSON.stringify(json, undefined, 2); 55 | } catch (error) { 56 | console.error(error); 57 | throw new Error('Failed to parse JSON. Please make sure it has a valid format and try again.'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/onRename.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ConfigId, getConfig } from './config'; 3 | 4 | import { showError, getJsonFromYaml, getYamlFromJson } from './helpers'; 5 | 6 | export function onRename(e: vscode.FileRenameEvent) { 7 | const shouldConvertOnRename = getConfig(ConfigId.ConvertOnRename); 8 | 9 | if (!shouldConvertOnRename) { 10 | return; 11 | } 12 | 13 | e.files.forEach(async (change) => { 14 | const { oldUri, newUri } = change; 15 | 16 | const oldPath = oldUri.path; 17 | const newPath = newUri.path; 18 | 19 | const shouldConvertJson = oldPath.endsWith('.json') && (newPath.endsWith('.yaml') || newPath.endsWith('.yml')); 20 | const shouldConvertYaml = (oldPath.endsWith('.yaml') || oldPath.endsWith('.yml')) && newPath.endsWith('.json'); 21 | 22 | if (!shouldConvertJson && !shouldConvertYaml) { 23 | return; 24 | } 25 | 26 | const document = await vscode.workspace.openTextDocument(newUri); 27 | 28 | // language id of NEW file 29 | switch (document.languageId) { 30 | case 'json': 31 | convertYamlToJson(document); 32 | break; 33 | case 'yaml': 34 | convertJsonToYaml(document); 35 | break; 36 | } 37 | }); 38 | } 39 | 40 | async function convertJsonToYaml(document: vscode.TextDocument) { 41 | try { 42 | const json = document.getText(); 43 | const yaml = getYamlFromJson(json); 44 | 45 | await replaceFileContent(document, yaml); 46 | } catch (error: unknown) { 47 | showError(error); 48 | } 49 | } 50 | 51 | async function convertYamlToJson(document: vscode.TextDocument) { 52 | try { 53 | const yaml = document.getText(); 54 | const json = getJsonFromYaml(yaml); 55 | 56 | await replaceFileContent(document, json); 57 | } catch (error: unknown) { 58 | showError(error); 59 | } 60 | } 61 | 62 | async function replaceFileContent(document: vscode.TextDocument, newText: string) { 63 | const { lineCount, isDirty, uri } = document; 64 | 65 | const edit = new vscode.WorkspaceEdit(); 66 | const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(lineCount, Number.MAX_VALUE)); 67 | 68 | try { 69 | if (isDirty) { 70 | await document.save(); 71 | } 72 | 73 | edit.replace(uri, range, newText); 74 | await vscode.workspace.applyEdit(edit); 75 | await document.save(); 76 | } catch (error: unknown) { 77 | showError(error); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/suite/helpers.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import { getJsonFromYaml, getYamlFromJson } from '../../helpers'; 4 | import { loadFixtures, stripNewLines } from '../testHelpers'; 5 | import { ConfigId, Configs } from '../../config'; 6 | import { WorkspaceConfigurationMock } from '../testUtil'; 7 | 8 | type Test = { 9 | inputFilePath: string; 10 | expectedFilePath: string; 11 | description: string; 12 | /** 13 | * enable mocking of vscode.workspace.getConfiguration 14 | */ 15 | configMock?: Partial; 16 | }; 17 | 18 | suite('helpers', () => { 19 | suite('getYamlFromJson()', () => { 20 | const tests: Test[] = [ 21 | { 22 | description: 'should convert json to yaml', 23 | inputFilePath: 'input.json', 24 | expectedFilePath: 'expected.yaml', 25 | }, 26 | { 27 | description: 'when json lines are long and line width is not limited', 28 | inputFilePath: 'longLinesInput.json', 29 | expectedFilePath: 'longLinesExpectedUnlimited.yaml', 30 | configMock: { 31 | [ConfigId.YamlLineWidth]: 0, // 0 means unlimited 32 | }, 33 | }, 34 | { 35 | description: 'when json lines are long and line width is limited', 36 | inputFilePath: 'longLinesInput.json', 37 | expectedFilePath: 'longLinesExpectedLimited.yaml', 38 | configMock: { 39 | [ConfigId.YamlLineWidth]: 100, 40 | }, 41 | }, 42 | ]; 43 | 44 | for (const t of tests) { 45 | suite(t.description, () => { 46 | let workspaceConfigMock: WorkspaceConfigurationMock; 47 | suiteSetup(() => (workspaceConfigMock = new WorkspaceConfigurationMock(t.configMock))); 48 | 49 | suiteTeardown(() => workspaceConfigMock.restore()); 50 | 51 | test('should return expected yaml', async () => assertTest(t, getYamlFromJson)); 52 | }); 53 | } 54 | }); 55 | 56 | suite('getJsonFromYaml', async () => { 57 | const TESTS: Test[] = [ 58 | { 59 | inputFilePath: 'input.yaml', 60 | expectedFilePath: 'expected.json', 61 | description: 'should convert basic yaml to json', 62 | }, 63 | { 64 | inputFilePath: 'mergeTagInput.yaml', 65 | expectedFilePath: 'mergeTagExpected.json', 66 | description: 'should convert yaml with merge tags to json', 67 | }, 68 | ]; 69 | 70 | TESTS.forEach((t) => { 71 | suite(t.description, () => { 72 | test('should return expected json', () => assertTest(t, getJsonFromYaml)); 73 | }); 74 | }); 75 | }); 76 | }); 77 | 78 | async function assertTest(t: Test, converter: (input: string) => string) { 79 | const [yaml, expected] = await loadFixtures(t.inputFilePath, t.expectedFilePath); 80 | 81 | const actual = converter(yaml); 82 | 83 | assert.deepStrictEqual(stripNewLines(actual), stripNewLines(expected)); 84 | } 85 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.13.0] - 2025-04-14 4 | 5 | ### Added 6 | 7 | - Config: `yamlLineWidth` to control YAML line width (and option to disable line wrapping) 8 | - Config: Expose `yamlMerge` config currently defaulted to true always. 9 | - Config: Enable (more advanced) users to configure all available YAML module options that is available using `yamlOptions` object 10 | 11 | ### Changed 12 | 13 | - Bump version of [YAML](https://www.npmjs.com/package/yaml) parser module to `2.7.1` 14 | 15 | ## [1.12.1] - 2023-04-13 16 | 17 | #### Fixed 18 | 19 | - CodeQL README badge 20 | 21 | ## [1.12.0] - 2023-04-07 22 | 23 | ### Added 24 | 25 | - Config: Allow overwriting existent files 26 | 27 | ### Changed 28 | 29 | - Update YAML stringify/parse library 30 | - set `merge: true` in options when using YAML library 31 | 32 | ## [1.11.0] - 2022-08-28 33 | 34 | ### Changed 35 | 36 | - Remove information message after successfully doing revert of converted files 37 | 38 | ### Added 39 | 40 | - Additional extension activation events 41 | 42 | ## [1.10.0] - 2022-04-08 43 | 44 | ### Added 45 | 46 | - Feature: Preview YAML selection as JSON by using command `YAML+JSON: Preview as JSON (from YAML. Opens in new file)` 47 | - Feature: Preview JSON selection as YAML `YAML+JSON: Preview as YAML (from JSON. Opens in new file)` 48 | 49 | ## [1.9.2] - 2021-11-21 50 | 51 | ### Changes 52 | 53 | - remove `null` default from `keepOriginalFiles` config 54 | 55 | ## [1.9.1] - 2021-11-21 56 | 57 | ### Fixed 58 | 59 | - npm audit to fix security concerns with dependencies 60 | 61 | ## [1.9.0] - 2021-11-21 62 | 63 | ### Added 64 | 65 | - feature: keep original files on conversion, based on configuration; `ask` to be asked to keep original files and `always` to always keep original files. 66 | 67 | ## [1.8.0] - 2021-05-27 68 | 69 | ### Fixed 70 | 71 | - extension not being activated when workspace don't contain any `.json` or `.y(a)ml` file(s) 72 | 73 | ## [1.7.0] - 2021-03-20 74 | 75 | ### Added 76 | 77 | - Configurable filename extensions: 78 | - yaml can be `.yaml` or `.yml` 79 | - json can be `json` 80 | 81 | `jsonc` might be supported at a later point if requested as the extension should also support converting from `jsonc` then. 82 | 83 | ### Changed 84 | 85 | - Default configurations correction for `yamlIndent` (no affect on usage) 86 | 87 | ## [1.6.0] - 2020-12-10 88 | 89 | ### Changed 90 | 91 | - Old configurations keys has been marked as deprecated 92 | - New configurations keys are grouped in an object `yaml-plus-json`. 93 | 94 | ### Added 95 | 96 | - `yamlSchema` configuration. See [yaml module documentation](https://github.com/eemeli/yaml/blob/master/docs/03_options.md#data-schemas) for details. 97 | 98 | ## [1.5.0] - 2020-12-08 99 | 100 | ### Added 101 | 102 | - Revert files converted. After converting one or multiple files (using features that includes right click on file(s)), a prompt will be shown allowing to revert. Reverting will include YAML comments. 103 | 104 | ## [1.4.0] - 2020-09-07 105 | 106 | ### Added 107 | 108 | - Configure the amount of spaces used for indentation when converting to YAML. 109 | 110 | ## [1.3.0] - 2020-06-30 111 | 112 | ### Fixed 113 | 114 | - Converting multiple files on windows. 115 | 116 | ## [1.2.1] - 2020-04-13 117 | 118 | ### Changed 119 | 120 | - Now hidding commands from command palette related to multi file conversion: 121 | - `Convert YAML files to JSON` 122 | - `Convert JSON files to YAML` 123 | - `Convert selected files to YAML` 124 | - `Convert selected files to JSON` 125 | 126 | ## [1.2.0] - 2020-04-09 127 | 128 | ### Added 129 | 130 | - Convert a range of selected items from JSON to YAML and vice versa. 131 | 132 | ## [1.1.1] - 2020-04-09 133 | 134 | ### Changed 135 | 136 | - Folder conversion context menus only shown if selected resources is a folder. 137 | 138 | ## [1.1.0] - 2020-04-07 139 | 140 | ### Added 141 | 142 | - Convert YAML files in directory to JSON and vice versa. 143 | 144 | ## [1.0.0] - 2020-03-21 145 | 146 | ### Added 147 | 148 | - Yaml Plus Json configurations 149 | - `yaml-plus-json.convertOnRename` - disable/enable automatic conversion on file rename. Defaults to true. 150 | 151 | ## [0.4.0-0.7.0] - 2020-03-21 152 | 153 | ### changed 154 | 155 | - Bundle extension. 156 | - Reduce publish size. 157 | 158 | ## [0.3.1] - 2020-03-15 159 | 160 | - Security patch. 161 | 162 | ## [0.3.0] - 2020-03-8 163 | 164 | - Feature: Convert selection of JSON / YAML. 165 | 166 | ## [0.2.1] - 2020-02-23 167 | 168 | - Updated readme. 169 | 170 | ## [0.2.0] - 2020-02-23 171 | 172 | - Remove convert commands from command palette, when they are not applicable. 173 | - Make those commands actually work using the command palette and not only using the right click context menu. 174 | 175 | ## [0.1.2] - 2020-02-10 176 | 177 | - Added extension logo. Thanks to [Jonathan](https://github.com/JonathanMH) for help creating it. 178 | 179 | ## [0.1.1] - 2020-02-8 180 | 181 | ### Changed 182 | 183 | - Updated readme. 184 | 185 | ## [0.1.0] - 2020-02-8 186 | 187 | ### Added 188 | 189 | - Right click `.json` files to convert to `.yml`. 190 | - Right click `.yml` or `.yaml` files to convert to json. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YAML :heart: JSON 2 | 3 | [![CodeQL](https://github.com/hilleer/vscode-yaml-plus-json/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/hilleer/vscode-yaml-plus-json/actions/workflows/github-code-scanning/codeql) 4 | [![extension CI](https://github.com/hilleer/vscode-yaml-plus-json/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/hilleer/vscode-yaml-plus-json/actions/workflows/ci.yaml) 5 | 6 | Easily convert YAML to JSON or vice versa. Conversion can be done by each individual file or by all files in a folder. 7 | 8 | Any good ideas or feature requests? Please, do not hesitate to open [a new issue](https://github.com/hilleer/vscode-yaml-plus-json/issues/new)! 9 | 10 | ## Features and usage 11 | 12 | - **Convert a single file:** 13 | - Convert a YAML file to JSON by right clicking it and selecting `Convert to JSON`. 14 | - Convert a YAML file to JSON by changing file extension to `.json`. 15 | - Convert a JSON file to YAML by right clicking it and selecting `Convert to YAML`. 16 | - Convert a JSON file to YAML by changing file extension to `.yaml` or `.yml`. 17 | - **Convert selection as preview:** 18 | - Convert YAML selection as preview by using command `YAML+JSON: Preview as JSON (from YAML. Opens in new file)` 19 | - Convert JSON selection as preview by using command `YAML+JSON: Preview as YAML (from JSON. Opens in new file)` 20 | - **Convert text selection:** 21 | - Convert YAML selection by using command `Convert selection to JSON` - _does not_ change file extension. 22 | - Convert JSON selection by using command `Convert selection to YAML` - _does not_ change file extension. 23 | - **Converting multiple files:** 24 | 25 | - Convert a selection of JSON files to YAML by right clicking one of the selected files and selecting `Convert selected files to YAML`. 26 | - Convert a selection of YAML files to JSON by right clicking one of the selected files and selecting `Convert selected files to JSON`. 27 | - Convert YAML files in a directory to JSON by right clicking the directory and selecting `Convert YAML files to JSON`. 28 | - Convert JSON files in a directory to YAML by right clicking the directory and selecting `Convert JSON files to YAML`. 29 | 30 | - **Reverting converted files:** When a file has been reverted, a _"revert"_ prompt will be shown to revert it. Using this will return the entirety of the original file, including YAML comments. 31 | - **Overwriting existent files:** When trying to convert a file into a destination that already exist, you can use the `overwriteExistentFiles` configuration to overwrite such. **Notice** if you use the revert feature after overwriting a file, the extension cannot (currently) revert the overwritten file. Also, due to limitation in vscode of active user prompts, if you set it to `"ask"` you will only be prompted to overwrite N number of files, while others will be skipped. 32 | 33 | ## Config 34 | 35 | All configurations should be defined in `yaml-plus-json` in vscode settings (e.g. workspace file `.vscode/settings.json`). Example: 36 | 37 | ```jsonc 38 | { 39 | "yaml-plus-json": { 40 | "convertOnRename": true, 41 | "yamlIndent": 2, 42 | "yamlLineWidth": 0, 43 | "yamlMerge": false, 44 | "fileExtensions": { 45 | "yaml": ".yaml", 46 | "json": ".json", 47 | }, 48 | // for more advanced parser configurations 49 | // see the YAML module documentation for details: 50 | // https://github.com/eemeli/yaml/blob/main/docs/03_options.md#options 51 | // note specific extension configs set takes precedence ("yamlIndent" for example) 52 | "yamlOptions": { 53 | "indent": 2, 54 | "lineWidth": 0, 55 | }, 56 | }, 57 | } 58 | ``` 59 | 60 | 61 | 62 | | | description | type | default | example | 63 | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | --------- | ---------- | 64 | | `convertOnRename` | Convert YAML/JSON files on rename | boolean | `true` | `false` | 65 | | `yamlIndent` | The number of spaces to use when indenting code (yaml) | number | `2` | `4` | 66 | | `yamlSchema` | See [yaml module documentation](https://github.com/eemeli/yaml/blob/master/docs/03_options.md#schema-options) for details | string | `"core"` | `"json"` | 67 | | `yamlMerge` | Enable support for << merge keys. Default value depends on YAML version. | boolean | `true` | `false` | 68 | | `yamlLineWidth` | Set to 0 to disable line wrapping. See [line width options](https://github.com/eemeli/yaml/blob/main/docs/03_options.md#tostring-options) for details | number | | `100` | 69 | | `fileExtensions` | define what filename extension(s) to use when converting file(s) | object | | | 70 | | `fileExtensions.yaml` | yaml filename extension | string | `".yaml"` | `".yml"` | 71 | | `fileExtensions.json` | json filename extension | string | `".json"` | `".json"` | 72 | | `keepOriginalFiles` | Keep original files when converting. Use `"ask"` to be asked every time or `"always"` to always keep original files | string | | `"always"` | 73 | | `overwriteExistentFiles` | Overwrite existent files when converting. Use `"ask"` to be asked every time or `"always"` to always overwrite | string | | `"always"` | 74 | | `yamlOptions` | For more advanced configs using the YAML parser. See the module [docs](https://github.com/eemeli/yaml/blob/main/docs/03_options.md) for details. Note that specific extension configs set takes precedence. | object | | | 75 | 76 | --- 77 | 78 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/hilleer) 79 | -------------------------------------------------------------------------------- /src/converter.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | 4 | import { getJsonFromYaml, getYamlFromJson, showError } from './helpers'; 5 | import { ConfigId, Configs, getConfig } from './config'; 6 | 7 | type ConvertedFile = { 8 | oldFileUri: vscode.Uri; 9 | oldFileContent: Uint8Array; 10 | newFileUri: vscode.Uri; 11 | }; 12 | 13 | export enum ConvertFromType { 14 | Yaml = 'YAML', 15 | Json = 'JSON', 16 | } 17 | 18 | enum UserInputPrompt { 19 | Yes = 'Yes', 20 | No = 'No', 21 | } 22 | 23 | type ConvertFileContext = { 24 | shouldKeepOriginalFile: boolean; 25 | oldFileUri: vscode.Uri; 26 | newFileUri: vscode.Uri; 27 | fileContent: string; 28 | }; 29 | 30 | export class FileConverter { 31 | private convertFromType: ConvertFromType; 32 | constructor(convertFromType: ConvertFromType) { 33 | this.convertFromType = convertFromType; 34 | } 35 | 36 | public async convertFiles(files: vscode.Uri[]): Promise { 37 | const shouldKeepOriginalFiles = await this.shouldKeepOriginalFiles(files.length); 38 | const convertFilePromises = files.map((file) => this.transformAndConvertFile(shouldKeepOriginalFiles, file)); 39 | const convertedFiles = await Promise.all(convertFilePromises); 40 | const filtered = convertedFiles.filter(Boolean) as ConvertedFile[]; 41 | 42 | // no need to show revert tooltip if we already keeping original files 43 | // might consider to redo this behavior so instead the reverting the user would have the possibility of delete created files 44 | if (!shouldKeepOriginalFiles && filtered.length > 0) { 45 | await this.showReverterTooltip(filtered); 46 | } 47 | } 48 | 49 | /** 50 | * @returns null if file was not converted 51 | */ 52 | private transformAndConvertFile = async ( 53 | shouldKeepOriginalFile: boolean, 54 | oldFileUri: vscode.Uri, 55 | ): Promise => { 56 | const oldFileContent = await vscode.workspace.fs.readFile(oldFileUri); 57 | const oldFileExtension = path.extname(oldFileUri.fsPath); 58 | 59 | const newFileExtension = FileConverter.getNewFileExtension(this.convertFromType); 60 | const newFilePath = oldFileUri.fsPath.replace(oldFileExtension, newFileExtension); 61 | const newFileUri = vscode.Uri.file(newFilePath); 62 | 63 | const fileExists = await this.doFileExist(newFileUri); 64 | if (fileExists) { 65 | const shouldOverwriteFile = await this.isAllowOverwriteExistentFile(newFileUri); 66 | if (!shouldOverwriteFile) { 67 | vscode.window.showInformationMessage(`File already exist.\n${newFileUri}`); 68 | return null; 69 | } 70 | } 71 | 72 | const fileContent = FileConverter.getNewFileContent(this.convertFromType, oldFileContent.toString()); 73 | 74 | await this.convertFile({ newFileUri, oldFileUri, shouldKeepOriginalFile, fileContent }); 75 | 76 | return { oldFileUri, oldFileContent, newFileUri }; 77 | }; 78 | 79 | private async showReverterTooltip(convertedFiles: ConvertedFile[]) { 80 | const filesLength = convertedFiles.length; 81 | const didConvertSingleFile = filesLength === 1; 82 | 83 | const message = didConvertSingleFile ? `Revert converted file?` : `Revert ${filesLength} converted files files?`; 84 | 85 | const revertSelection = await vscode.window.showInformationMessage(message, 'Revert'); 86 | 87 | if (revertSelection !== 'Revert') { 88 | return; 89 | } 90 | 91 | const shouldKeepOriginalFiles = false; // never keep "original" files when reverting 92 | const promises = convertedFiles.map(async (convertedFile) => 93 | this.revertTransformedAndConvertedFile(shouldKeepOriginalFiles, convertedFile), 94 | ); 95 | await Promise.all(promises); 96 | } 97 | 98 | private revertTransformedAndConvertedFile = async (shouldKeepOriginalFile: boolean, convertedFile: ConvertedFile) => { 99 | const { oldFileUri: newFileUri, oldFileContent: newFileContent, newFileUri: oldFileUri } = convertedFile; 100 | 101 | const fileContent = newFileContent.toString(); 102 | await this.convertFile({ shouldKeepOriginalFile, oldFileUri, newFileUri, fileContent }); 103 | }; 104 | 105 | /** 106 | * @returns a boolean signaling if file was converted or not. 107 | */ 108 | private convertFile = async (context: ConvertFileContext): Promise => { 109 | const { fileContent, newFileUri, oldFileUri, shouldKeepOriginalFile } = context; 110 | const newFile = Buffer.from(fileContent); 111 | 112 | try { 113 | if (!shouldKeepOriginalFile) { 114 | await vscode.workspace.fs.delete(oldFileUri); 115 | } 116 | 117 | await vscode.workspace.fs.writeFile(newFileUri, newFile); 118 | } catch (error) { 119 | showError(error); 120 | } 121 | 122 | if (shouldKeepOriginalFile) { 123 | try { 124 | // TODO check if can be removed 125 | // introduced here: https://github.com/hilleer/vscode-yaml-plus-json/pull/68/files#diff-2c718087c1fd72979602bd9da34119fb956d070d90109b6bc01ea23b2ad7eae1L91 126 | } catch (error: unknown) { 127 | showError(error); 128 | } 129 | } 130 | 131 | try { 132 | // TODO check if can be removed 133 | // introduced here: https://github.com/hilleer/vscode-yaml-plus-json/pull/68/files#diff-2c718087c1fd72979602bd9da34119fb956d070d90109b6bc01ea23b2ad7eae1L99 134 | } catch (error: unknown) { 135 | showError(error); 136 | } 137 | }; 138 | 139 | private async doFileExist(fileUri: vscode.Uri): Promise { 140 | try { 141 | await vscode.workspace.fs.readFile(fileUri); 142 | return true; 143 | } catch (error) { 144 | // vscode throws this error when file is not found 145 | if (error instanceof vscode.FileSystemError) { 146 | return false; 147 | } 148 | 149 | throw error; 150 | } 151 | } 152 | 153 | private async shouldKeepOriginalFiles(length: number): Promise { 154 | const keepOriginalFiles = getConfig(ConfigId.KeepOriginalFiles); 155 | 156 | if (keepOriginalFiles === 'always') { 157 | return true; 158 | } 159 | 160 | if (keepOriginalFiles === 'ask') { 161 | const isSingular = length === 1; 162 | const message = `Do you want to keep the original file${isSingular ? '' : 's'}?`; 163 | const selection = await vscode.window.showInformationMessage(message, 'Keep', 'Do not keep'); 164 | 165 | return selection === 'Keep'; 166 | } 167 | 168 | return false; 169 | } 170 | 171 | private static getNewFileContent(convertFromType: ConvertFromType, oldContent: string) { 172 | const converter = { 173 | [ConvertFromType.Json]: getYamlFromJson, 174 | [ConvertFromType.Yaml]: getJsonFromYaml, 175 | }[convertFromType]; 176 | 177 | return converter(oldContent); 178 | } 179 | 180 | private static getNewFileExtension(convertFromType: ConvertFromType) { 181 | const toJsonFileExtension = getConfig<'.json'>(ConfigId.FileExtensionsJson); 182 | const toYamlFileExtension = getConfig<'.yaml' | '.yml'>(ConfigId.FileExtensionsYaml); 183 | 184 | const fileExtension = { 185 | [ConvertFromType.Json]: toYamlFileExtension, 186 | [ConvertFromType.Yaml]: toJsonFileExtension, 187 | }[convertFromType]; 188 | 189 | // should not happen 190 | if (!fileExtension) { 191 | throw new Error(`new file extension from type not found: ${convertFromType}`); 192 | } 193 | 194 | return fileExtension; 195 | } 196 | 197 | private async isAllowOverwriteExistentFile(fileUri: vscode.Uri): Promise { 198 | const config = getConfig(ConfigId.OverwriteExistentFiles); 199 | 200 | if (!config) { 201 | return false; 202 | } 203 | 204 | if (config === 'always') { 205 | return true; 206 | } 207 | 208 | if (config === 'ask') { 209 | const question = `file already exist${fileUri}\nDo you want to overwrite it?`; 210 | const answerOptions = Object.values(UserInputPrompt); 211 | const userResponse = await vscode.window.showInformationMessage(question, ...answerOptions); 212 | 213 | return userResponse === UserInputPrompt.Yes; 214 | } 215 | 216 | throw new Error('config of overwriteExistentFiles has an unexpected or unsupported value'); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yaml-plus-json", 3 | "displayName": "YAML ❤️ JSON", 4 | "description": "Easily convert yaml to json and json to yaml", 5 | "version": "1.13.0", 6 | "publisher": "hilleer", 7 | "engines": { 8 | "vscode": "^1.78.1", 9 | "node": ">=18" 10 | }, 11 | "icon": "logo/logo.png", 12 | "contributors": [ 13 | { 14 | "name": "Daniel Hillmann", 15 | "url": "https://github.com/hilleer", 16 | "email": "hiller@live.dk" 17 | } 18 | ], 19 | "categories": [ 20 | "Other" 21 | ], 22 | "keywords": [ 23 | "converter", 24 | "yaml converter", 25 | "json converter", 26 | "yaml to json", 27 | "json to yaml" 28 | ], 29 | "activationEvents": [ 30 | "workspaceContains:**/**.json", 31 | "workspaceContains:**/**.yml", 32 | "workspaceContains:**/**.yaml", 33 | "onLanguage:json", 34 | "onLanguage:yaml" 35 | ], 36 | "main": "./dist/extension.js", 37 | "contributes": { 38 | "commands": [ 39 | { 40 | "command": "extension.rightClickJson", 41 | "title": "Convert to YAML", 42 | "enablement": "resourceExtname == .json" 43 | }, 44 | { 45 | "command": "extension.rightClickYaml", 46 | "title": "Convert to JSON", 47 | "enablement": "resourceExtname == .yml || resourceExtname == .yaml" 48 | }, 49 | { 50 | "command": "extension.yamlSelectionToJson", 51 | "title": "Convert selection to JSON", 52 | "enablement": "editorHasSelection" 53 | }, 54 | { 55 | "command": "extension.jsonSelectionToYaml", 56 | "title": "Convert selection to YAML", 57 | "enablement": "editorHasSelection" 58 | }, 59 | { 60 | "command": "extension.convertYamlFilesToJson", 61 | "title": "Convert YAML files to JSON", 62 | "enablement": "explorerResourceIsFolder" 63 | }, 64 | { 65 | "command": "extension.convertJsonFilesToYaml", 66 | "title": "Convert JSON files to YAML", 67 | "enablement": "explorerResourceIsFolder" 68 | }, 69 | { 70 | "command": "extension.convertJsonSelectionsToYaml", 71 | "title": "Convert selected files to YAML", 72 | "enablement": "listMultiSelection" 73 | }, 74 | { 75 | "command": "extension.convertYamlSelectionsToJson", 76 | "title": "Convert selected files to JSON", 77 | "enablement": "listMultiSelection" 78 | }, 79 | { 80 | "command": "extension.previewAsYaml", 81 | "title": "YAML+JSON: Preview as YAML (from JSON. Opens in new file)" 82 | }, 83 | { 84 | "command": "extension.previewAsJson", 85 | "title": "YAML+JSON: Preview as JSON (from YAML. Opens in new file)" 86 | } 87 | ], 88 | "menus": { 89 | "commandPalette": [ 90 | { 91 | "command": "extension.rightClickJson", 92 | "when": "resourceExtname == .json" 93 | }, 94 | { 95 | "command": "extension.rightClickYaml", 96 | "when": "resourceExtname == .yml || resourceExtname == .yaml" 97 | }, 98 | { 99 | "command": "extension.yamlSelectionToJson", 100 | "title": "Convert selection to JSON", 101 | "when": "editorHasSelection" 102 | }, 103 | { 104 | "command": "extension.jsonSelectionToYaml", 105 | "title": "Convert selection to YAML", 106 | "when": "editorHasSelection" 107 | }, 108 | { 109 | "command": "extension.convertYamlFilesToJson", 110 | "when": "false" 111 | }, 112 | { 113 | "command": "extension.convertJsonFilesToYaml", 114 | "when": "false" 115 | }, 116 | { 117 | "command": "extension.convertJsonSelectionsToYaml", 118 | "when": "false" 119 | }, 120 | { 121 | "command": "extension.convertYamlSelectionsToJson", 122 | "when": "false" 123 | } 124 | ], 125 | "explorer/context": [ 126 | { 127 | "group": "1_modification", 128 | "command": "extension.convertYamlFilesToJson", 129 | "when": "explorerResourceIsFolder" 130 | }, 131 | { 132 | "group": "1_modification", 133 | "command": "extension.convertJsonFilesToYaml", 134 | "when": "explorerResourceIsFolder" 135 | }, 136 | { 137 | "group": "1_modification", 138 | "command": "extension.rightClickJson", 139 | "when": "resourceExtname == .json" 140 | }, 141 | { 142 | "group": "1_modification", 143 | "command": "extension.rightClickYaml", 144 | "when": "resourceExtname == .yml || resourceExtname == .yaml" 145 | }, 146 | { 147 | "group": "1_modification", 148 | "command": "extension.convertJsonSelectionsToYaml", 149 | "when": "listMultiSelection && resourceExtname == .json" 150 | }, 151 | { 152 | "group": "1_modification", 153 | "command": "extension.convertYamlSelectionsToJson", 154 | "when": "listMultiSelection && resourceExtname == .yml" 155 | }, 156 | { 157 | "group": "1_modification", 158 | "command": "extension.convertYamlSelectionsToJson", 159 | "when": "listMultiSelection && resourceExtname == .yaml" 160 | } 161 | ] 162 | }, 163 | "configuration": [ 164 | { 165 | "title": "Yaml Plus Json", 166 | "properties": { 167 | "yaml-plus-json.convertOnRename": { 168 | "type": "boolean", 169 | "description": "Convert YAML/JSON files on rename", 170 | "deprecationMessage": "please refer to use yaml-plus-json configuration object" 171 | }, 172 | "yaml-plus-json.yaml-indent": { 173 | "type": "number", 174 | "description": "The number of spaces to use when indenting code (yaml)", 175 | "deprecationMessage": "please refer to use yaml-plus-json configuration object" 176 | }, 177 | "yaml-plus-json": { 178 | "type": "object", 179 | "default": { 180 | "convertOnRename": true, 181 | "yamlIndent": 2, 182 | "fileExtensions": { 183 | "yaml": ".yaml", 184 | "json": ".json" 185 | } 186 | }, 187 | "properties": { 188 | "convertOnRename": { 189 | "type": "boolean", 190 | "description": "Convert YAML/JSON files on rename", 191 | "default": true 192 | }, 193 | "yamlSchema": { 194 | "type": "string", 195 | "description": "See yaml module documentation for details https://github.com/eemeli/yaml/blob/master/docs/03_options.md#data-schemas", 196 | "enum": [ 197 | "core", 198 | "failsafe", 199 | "json", 200 | "yaml-1.1" 201 | ], 202 | "default": "core" 203 | }, 204 | "yamlIndent": { 205 | "type": "number", 206 | "description": "The number of spaces to use when indenting code (yaml)" 207 | }, 208 | "yamlLineWidth": { 209 | "type": "number", 210 | "description": "The line width to use when converting to yaml. Set to 0 to disable line wrapping", 211 | "examples": [ 212 | 0, 213 | 100 214 | ] 215 | }, 216 | "yamlMerge": { 217 | "$comment": "Can be configured in \"yamlOptions\" too. This config takes precedence.", 218 | "type": "boolean", 219 | "default": true 220 | }, 221 | "fileExtensions": { 222 | "type": "object", 223 | "description": "define what filename extension(s) to use when converting file(s)", 224 | "properties": { 225 | "yaml": { 226 | "type": "string", 227 | "description": "yaml filename extension", 228 | "enum": [ 229 | ".yaml", 230 | ".yml" 231 | ] 232 | }, 233 | "json": { 234 | "type": "string", 235 | "description": "json filename extension", 236 | "enum": [ 237 | ".json" 238 | ] 239 | } 240 | } 241 | }, 242 | "keepOriginalFiles": { 243 | "type": [ 244 | "string" 245 | ], 246 | "description": "Keep original files when converting. Use 'ask' to be asked every time or use 'always' to always keep files on conversion", 247 | "enum": [ 248 | "ask", 249 | "always" 250 | ] 251 | }, 252 | "overwriteExistentFiles": { 253 | "type": [ 254 | "string" 255 | ], 256 | "description": "Overwrite existent files when converting. Use 'ask' to be asked every time or 'always' to always overwrite existent files", 257 | "enum": [ 258 | "ask", 259 | "always" 260 | ] 261 | }, 262 | "yamlOptions": { 263 | "description": "Options used to convert from JSON to YAML. Note that specific extension configs set takes precedence over this config.", 264 | "type": "object", 265 | "examples": [ 266 | { 267 | "schema": "core", 268 | "indent": 2, 269 | "lineWidth": 0, 270 | "merge": true 271 | } 272 | ] 273 | } 274 | } 275 | } 276 | } 277 | } 278 | ] 279 | }, 280 | "scripts": { 281 | "compile": "tsc -p ./", 282 | "format": "prettier --check .", 283 | "format:fix": "prettier --write .", 284 | "lint": "eslint src/", 285 | "postversion": "git push && git push --tags", 286 | "pretest": "npm run compile && npm run webpack && npm run lint", 287 | "preversion": "npm run pretest", 288 | "test": "node ./out/test/runTest.js", 289 | "vscode:prepublish": "npm run webpack", 290 | "watch": "tsc -watch -p ./", 291 | "webpack-dev": "webpack --mode development --watch", 292 | "webpack": "webpack --mode production" 293 | }, 294 | "devDependencies": { 295 | "@eslint/js": "^9.39.2", 296 | "@types/glob": "^9.0.0", 297 | "@types/mocha": "^10.0.10", 298 | "@types/node": "^25.0.2", 299 | "@types/sinon": "21.0.0", 300 | "@types/vscode": "^1.107.0", 301 | "@types/yaml": "1.9.7", 302 | "@typescript-eslint/eslint-plugin": "^8.49.0", 303 | "@typescript-eslint/parser": "^8.49.0", 304 | "@vscode/test-electron": "2.5.2", 305 | "eslint": "^9.39.2", 306 | "eslint-config-prettier": "10.1.8", 307 | "eslint-plugin-prettier": "5.5.4", 308 | "glob": "^13.0.0", 309 | "globals": "^16.5.0", 310 | "mocha": "^11.7.5", 311 | "sinon": "21.0.0", 312 | "ts-loader": "^9.5.4", 313 | "typescript": "^5.9.3", 314 | "typescript-eslint": "^8.49.0", 315 | "webpack": "5.103.0", 316 | "webpack-cli": "^6.0.1" 317 | }, 318 | "dependencies": { 319 | "yaml": "2.8.1" 320 | }, 321 | "bugs": { 322 | "url": "https://github.com/hilleer/vscode-yaml-plus-json/issues/new" 323 | }, 324 | "repository": { 325 | "url": "https://github.com/hilleer/vscode-yaml-plus-json", 326 | "type": "git" 327 | } 328 | } 329 | --------------------------------------------------------------------------------