├── .gitignore ├── .pipelines ├── build-package.yml ├── github-release │ ├── github-release.js │ ├── package-lock.json │ └── package.json ├── pipeline.yml ├── release-server.yml ├── release-service.yml ├── version-ci.yml └── version-release.yml ├── .vscode ├── launch.json └── tasks.json ├── CONTRIBUTING.md ├── README.md ├── RELEASE.md ├── SECURITY.md ├── language-server ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin │ └── azure-pipelines-language-server ├── package-lock.json ├── package.json ├── src │ └── server.ts ├── test │ ├── autoCompletion.test.ts │ ├── autoCompletion2.test.ts │ ├── autoCompletion3.schema.json │ ├── autoCompletion3.test.ts │ ├── documentSymbols.test.ts │ ├── formatter.test.ts │ ├── hover.test.ts │ ├── hover2.test.ts │ ├── integration.test.ts │ ├── schemaNotFound.test.ts │ ├── schemaValidation.test.ts │ ├── schemaValidation2.schema.json │ ├── schemaValidation2.test.ts │ └── testHelper.ts ├── thirdpartynotice.txt └── tsconfig.json └── language-service ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.ts ├── jsonContributions.ts ├── jsonSchema.ts ├── parser │ ├── jsonParser.ts │ └── yamlParser.ts ├── services │ ├── documentSymbols.ts │ ├── jsonSchemaService.ts │ ├── yamlCompletion.ts │ ├── yamlDefinition.ts │ ├── yamlFormatter.ts │ ├── yamlHover.ts │ ├── yamlTraversal.ts │ └── yamlValidation.ts ├── utils │ ├── arrUtils.ts │ ├── documentPositionCalculator.ts │ ├── errorHandler.ts │ ├── objects.ts │ ├── strings.ts │ ├── uri.ts │ └── yamlServiceUtils.ts └── yamlLanguageService.ts ├── test ├── arrUtils.test.ts ├── documentPositionCalculator.test.ts ├── fixtures │ ├── Microsoft.Authorization.json │ ├── Microsoft.Compute.json │ ├── Microsoft.Resources.json │ ├── Microsoft.Sql.json │ ├── Microsoft.Web.json │ ├── SuccessBricks.ClearDB.json │ ├── deploymentParameters.json │ └── deploymentTemplate.json ├── objects.test.ts ├── pipelinesTests │ ├── schema.json │ ├── yamlTraversal.test.ts │ ├── yamlcompletion.test.ts │ └── yamlvalidation.test.ts ├── schema.test.ts ├── strings.test.ts └── uri.test.ts ├── thirdpartynotice.txt ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | _bundles/ 2 | .nyc_output 3 | *.tgz 4 | lib/ 5 | node_modules/ 6 | npm-debug.log 7 | out/ 8 | -------------------------------------------------------------------------------- /.pipelines/build-package.yml: -------------------------------------------------------------------------------- 1 | # This Yaml Document has been converted by ESAI Yaml Pipeline Conversion Tool. 2 | # Please make sure to check all the converted content, it is your team's responsibility to make sure that the pipeline is still valid and functions as expected. 3 | # The Task 'PublishBuildArtifacts@1' has been converted to an output named 'Save the npm module' in the templateContext section. 4 | parameters: 5 | name: '' 6 | root: '' 7 | packagename: '' 8 | tarballPath: '' 9 | configuration: '' 10 | jobs: 11 | - job: ${{ parameters.name }} 12 | templateContext: 13 | outputs: 14 | - output: pipelineArtifact 15 | displayName: 'Save the npm module' 16 | targetPath: ${{ format('$(Build.ArtifactStagingDirectory)/{0}', parameters.packageName) }} 17 | artifactName: ${{ parameters.packagename }} 18 | steps: 19 | - bash: | 20 | PACKAGE_VERSION=$(node -p "require('./package.json').version") 21 | echo "$PACKAGE_VERSION" > version.txt 22 | workingDirectory: ${{ parameters.root }} 23 | displayName: Extract the version number from the package 24 | - template: /.pipelines/${{ format('version-{0}.yml', parameters.configuration) }}@self 25 | parameters: 26 | root: ${{ parameters.root }} 27 | - task: NodeTool@0 28 | displayName: Install Node 16 LTS or greater 29 | inputs: 30 | versionSpec: ">=16.13.0" 31 | - script: npm ci 32 | displayName: npm ci 33 | workingDirectory: ${{ parameters.root }} 34 | - script: npm run build 35 | displayName: Build and generate a package 36 | workingDirectory: ${{ parameters.root }} 37 | - script: npx mocha --require ts-node/register --ui bdd ./test/**/*.test.ts --reporter mocha-junit-reporter 38 | displayName: Run tests 39 | workingDirectory: ${{ parameters.root }} 40 | - task: PublishTestResults@2 41 | displayName: Publish test results 42 | condition: always() 43 | inputs: 44 | testRunner: JUnit 45 | testResultsFiles: ${{ format('./{0}/test-results.xml', parameters.root) }} 46 | - task: CopyFiles@2 47 | displayName: Stage the npm module 48 | inputs: 49 | sourceFolder: ${{ parameters.tarballPath }} 50 | contents: '*.tgz' 51 | targetFolder: ${{ format('$(Build.ArtifactStagingDirectory)/{0}', parameters.packageName) }} 52 | - ${{ if eq( parameters.configuration, 'release') }}: 53 | - bash: | 54 | echo $(Build.SourceBranch) | sed "s|refs/[^/]*/||" > branch.txt 55 | PACKAGE_VERSION=$(cat version.txt) 56 | VERSION_REGEX="#### $(echo $PACKAGE_VERSION | sed 's/\./\\./g')" 57 | sed -n "/$VERSION_REGEX/,/#### [0-9]\+\..*/p" CHANGELOG.md | head -n -2 > minichangelog.txt 58 | LINE_COUNT=$(cat minichangelog.txt | wc -l) 59 | if [ "$LINE_COUNT" -lt 2 ]; then 60 | echo Mini changelog is too short. Did you use the wrong version number in CHANGELOG.txt? 61 | exit 1 62 | fi 63 | workingDirectory: ${{ parameters.root }} 64 | displayName: Get branch and mini-changelog 65 | - ${{ if eq( parameters.configuration, 'release') }}: 66 | - task: CopyFiles@2 67 | displayName: Stage release meta-data files 68 | inputs: 69 | SourceFolder: ${{ parameters.root }} 70 | contents: |- 71 | version.txt 72 | branch.txt 73 | minichangelog.txt 74 | targetFolder: ${{ format('$(Build.ArtifactStagingDirectory)/{0}', parameters.packageName) }} 75 | - ${{ if eq( parameters.configuration, 'release') }}: 76 | - bash: npm install 77 | displayName: Prepare to create GitHub Release 78 | workingDirectory: '$(Build.SourcesDirectory)/.pipelines/github-release' 79 | - ${{ if eq( parameters.configuration, 'release') }}: 80 | - bash: | 81 | SCRIPT=.pipelines/github-release/github-release.js 82 | PACKAGE_VERSION=$(cat $STAGE_DIR/version.txt) 83 | CONTENT=$STAGE_DIR/$PACKAGE_NAME-$PACKAGE_VERSION.tgz 84 | CHANGELOG=$STAGE_DIR/minichangelog.txt 85 | VERSION_TAG=$PACKAGE_NAME/v$PACKAGE_VERSION 86 | node $SCRIPT $CONTENT $CHANGELOG $VERSION_TAG $GITHUB_TOKEN 87 | displayName: Create GitHub Release 88 | env: 89 | GITHUB_TOKEN: $(GitHubSecret) 90 | STAGE_DIR: ${{ format('$(Build.ArtifactStagingDirectory)/{0}', parameters.packageName) }} 91 | PACKAGE_NAME: ${{ parameters.packageName }} -------------------------------------------------------------------------------- /.pipelines/github-release/github-release.js: -------------------------------------------------------------------------------- 1 | const octokit = require('@octokit/rest')({ 2 | headers: { 3 | 'user-agent': 'azure-pipelines/vscode-release-pipeline v1.0' 4 | } 5 | }); 6 | const util = require('util'); 7 | const exec = util.promisify(require('child_process').exec); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | const DEBUG_LOGGING = process.env.SYSTEM_DEBUG && process.env.SYSTEM_DEBUG == 'true'; 12 | let contentName = process.argv[2] || null; 13 | let changelogName = process.argv[3] || null; 14 | let versionTag = process.argv[4] || null; 15 | let token = process.argv[5] || null 16 | if (token === null) { 17 | console.log(`Usage: 18 | 19 | github-release.js 20 | 21 | This will create a new release and tag on GitHub at the current HEAD commit. 22 | 23 | USE AT YOUR OWN RISK. 24 | This is intended to be run by the release pipeline only.`); 25 | process.exit(1); 26 | } 27 | 28 | async function createRelease() { 29 | let target_commitish; 30 | if (process.env.BUILD_SOURCEBRANCH) { 31 | target_commitish = process.env.BUILD_SOURCEBRANCH; 32 | } else { 33 | const { stdout: head_commit } = await exec('git rev-parse --verify HEAD'); 34 | target_commitish = head_commit.trim(); 35 | } 36 | 37 | const { stdout: body } = await exec('cat ' + changelogName); 38 | 39 | octokit.authenticate({ 40 | type: 'token', 41 | token: token 42 | }); 43 | 44 | console.log('Creating release...'); 45 | let createReleaseResult; 46 | try { 47 | createReleaseResult = await octokit.repos.createRelease({ 48 | owner: 'Microsoft', 49 | repo: 'azure-pipelines-language-server', 50 | tag_name: `${versionTag}`, 51 | target_commitish: target_commitish, 52 | name: `${versionTag}`, 53 | body: body 54 | }); 55 | } catch (e) { 56 | throw e; 57 | } 58 | console.log('Created release.'); 59 | 60 | if (DEBUG_LOGGING) { 61 | console.log(createReleaseResult); 62 | } 63 | 64 | const contentSize = fs.statSync(contentName).size; 65 | 66 | console.log('Uploading Content...'); 67 | let uploadResult; 68 | try { 69 | uploadResult = await octokit.repos.uploadAsset({ 70 | url: createReleaseResult.data.upload_url, 71 | headers: { 72 | 'content-length': contentSize, 73 | 'content-type': 'application/gzip', 74 | }, 75 | name: path.basename(contentName), 76 | file: fs.createReadStream(contentName) 77 | }); 78 | } catch (e) { 79 | throw e; 80 | } 81 | console.log('Uploaded Content.'); 82 | 83 | if (DEBUG_LOGGING) { 84 | console.log(uploadResult); 85 | } 86 | } 87 | 88 | try { 89 | createRelease(); 90 | } catch (err) { 91 | console.error(err); 92 | process.exit(1); 93 | } 94 | -------------------------------------------------------------------------------- /.pipelines/github-release/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-release", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@octokit/rest": { 8 | "version": "15.17.0", 9 | "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.17.0.tgz", 10 | "integrity": "sha512-tN16FJOGBPxt9QtPfl8yVbbuik3bQ7EI66zcX2XDh05Wcs8t+7mVEE3SWtCeK/Qm0RTLCeFQgGzuvkbD2J6cEg==", 11 | "requires": { 12 | "before-after-hook": "^1.1.0", 13 | "btoa-lite": "^1.0.0", 14 | "debug": "^3.1.0", 15 | "http-proxy-agent": "^2.1.0", 16 | "https-proxy-agent": "^2.2.0", 17 | "lodash": "^4.17.4", 18 | "node-fetch": "^2.1.1", 19 | "universal-user-agent": "^2.0.0", 20 | "url-template": "^2.0.8" 21 | } 22 | }, 23 | "agent-base": { 24 | "version": "4.2.1", 25 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", 26 | "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", 27 | "requires": { 28 | "es6-promisify": "^5.0.0" 29 | } 30 | }, 31 | "before-after-hook": { 32 | "version": "1.2.0", 33 | "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.2.0.tgz", 34 | "integrity": "sha512-wI3QtdLppHNkmM1VgRVLCrlWCKk/YexlPicYbXPs4eYdd1InrUCTFsx5bX1iUQzzMsoRXXPpM1r+p7JEJJydag==" 35 | }, 36 | "btoa-lite": { 37 | "version": "1.0.0", 38 | "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", 39 | "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=" 40 | }, 41 | "debug": { 42 | "version": "3.2.7", 43 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 44 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 45 | "requires": { 46 | "ms": "^2.1.1" 47 | } 48 | }, 49 | "es6-promise": { 50 | "version": "4.2.5", 51 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", 52 | "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==" 53 | }, 54 | "es6-promisify": { 55 | "version": "5.0.0", 56 | "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 57 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 58 | "requires": { 59 | "es6-promise": "^4.0.3" 60 | } 61 | }, 62 | "http-proxy-agent": { 63 | "version": "2.1.0", 64 | "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", 65 | "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", 66 | "requires": { 67 | "agent-base": "4", 68 | "debug": "3.1.0" 69 | }, 70 | "dependencies": { 71 | "debug": { 72 | "version": "3.1.0", 73 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 74 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 75 | "requires": { 76 | "ms": "2.0.0" 77 | } 78 | }, 79 | "ms": { 80 | "version": "2.0.0", 81 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 82 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 83 | } 84 | } 85 | }, 86 | "https-proxy-agent": { 87 | "version": "2.2.4", 88 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", 89 | "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", 90 | "requires": { 91 | "agent-base": "^4.3.0", 92 | "debug": "^3.1.0" 93 | }, 94 | "dependencies": { 95 | "agent-base": { 96 | "version": "4.3.0", 97 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", 98 | "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", 99 | "requires": { 100 | "es6-promisify": "^5.0.0" 101 | } 102 | } 103 | } 104 | }, 105 | "lodash": { 106 | "version": "4.17.21", 107 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 108 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 109 | }, 110 | "macos-release": { 111 | "version": "1.1.0", 112 | "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-1.1.0.tgz", 113 | "integrity": "sha512-mmLbumEYMi5nXReB9js3WGsB8UE6cDBWyIO62Z4DNx6GbRhDxHNjA1MlzSpJ2S2KM1wyiPRA0d19uHWYYvMHjA==" 114 | }, 115 | "ms": { 116 | "version": "2.1.1", 117 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 118 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 119 | }, 120 | "node-fetch": { 121 | "version": "2.6.7", 122 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 123 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 124 | "requires": { 125 | "whatwg-url": "^5.0.0" 126 | } 127 | }, 128 | "os-name": { 129 | "version": "2.0.1", 130 | "resolved": "https://registry.npmjs.org/os-name/-/os-name-2.0.1.tgz", 131 | "integrity": "sha1-uaOGNhwXrjohc27wWZQFyajF3F4=", 132 | "requires": { 133 | "macos-release": "^1.0.0", 134 | "win-release": "^1.0.0" 135 | } 136 | }, 137 | "semver": { 138 | "version": "5.7.2", 139 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", 140 | "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" 141 | }, 142 | "tr46": { 143 | "version": "0.0.3", 144 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 145 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 146 | }, 147 | "universal-user-agent": { 148 | "version": "2.0.1", 149 | "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.1.tgz", 150 | "integrity": "sha512-vz+heWVydO0iyYAa65VHD7WZkYzhl7BeNVy4i54p4TF8OMiLSXdbuQe4hm+fmWAsL+rVibaQHXfhvkw3c1Ws2w==", 151 | "requires": { 152 | "os-name": "^2.0.1" 153 | } 154 | }, 155 | "url-template": { 156 | "version": "2.0.8", 157 | "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", 158 | "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" 159 | }, 160 | "webidl-conversions": { 161 | "version": "3.0.1", 162 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 163 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 164 | }, 165 | "whatwg-url": { 166 | "version": "5.0.0", 167 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 168 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 169 | "requires": { 170 | "tr46": "~0.0.3", 171 | "webidl-conversions": "^3.0.0" 172 | } 173 | }, 174 | "win-release": { 175 | "version": "1.1.1", 176 | "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", 177 | "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=", 178 | "requires": { 179 | "semver": "^5.0.1" 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /.pipelines/github-release/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-release", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "github-release.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Microsoft", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@octokit/rest": "^15.17.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.pipelines/pipeline.yml: -------------------------------------------------------------------------------- 1 | # This Yaml Document has been converted by ESAI Yaml Pipeline Conversion Tool. 2 | # This pipeline will be extended to the OneESPT template 3 | 4 | resources: 5 | repositories: 6 | - repository: 1ESPipelineTemplates 7 | type: git 8 | name: 1ESPipelineTemplates/1ESPipelineTemplates 9 | ref: refs/tags/release 10 | extends: 11 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 12 | parameters: 13 | sdl: 14 | sourceAnalysisPool: 15 | name: 1ES-ABTT-Shared-Pool 16 | image: abtt-windows-2022 17 | os: windows 18 | pool: 19 | name: 1ES-ABTT-Shared-Pool 20 | image: abtt-ubuntu-2204 21 | os: linux 22 | customBuildTags: 23 | - ES365AIMigrationTooling 24 | stages: 25 | - stage: stage 26 | jobs: 27 | - template: /.pipelines/build-package.yml@self 28 | parameters: 29 | name: 'build_language_service' 30 | root: 'language-service' 31 | packagename: 'azure-pipelines-language-service' 32 | tarballPath: 'language-service' 33 | configuration: 'ci' 34 | - template: /.pipelines/build-package.yml@self 35 | parameters: 36 | name: 'build_language_server' 37 | root: 'language-server' 38 | packagename: 'azure-pipelines-language-server' 39 | tarballPath: 'language-server' 40 | configuration: 'ci' -------------------------------------------------------------------------------- /.pipelines/release-server.yml: -------------------------------------------------------------------------------- 1 | # This Yaml Document has been converted by ESAI Yaml Pipeline Conversion Tool. 2 | # This pipeline will be extended to the OneESPT template 3 | 4 | # Release build script for language-server 5 | # Only trigger manually 6 | trigger: none 7 | pr: none 8 | resources: 9 | repositories: 10 | - repository: 1ESPipelineTemplates 11 | type: git 12 | name: 1ESPipelineTemplates/1ESPipelineTemplates 13 | ref: refs/tags/release 14 | extends: 15 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 16 | parameters: 17 | sdl: 18 | sourceAnalysisPool: 19 | name: 1ES-ABTT-Shared-Pool 20 | image: abtt-windows-2022 21 | os: windows 22 | pool: 23 | name: 1ES-ABTT-Shared-Pool 24 | image: abtt-ubuntu-2204 25 | os: linux 26 | customBuildTags: 27 | - ES365AIMigrationTooling 28 | stages: 29 | - stage: stage 30 | jobs: 31 | - template: /.pipelines/build-package.yml@self 32 | parameters: 33 | name: 'build_language_server' 34 | root: 'language-server' 35 | packagename: 'azure-pipelines-language-server' 36 | tarballPath: 'language-server' 37 | configuration: 'release' -------------------------------------------------------------------------------- /.pipelines/release-service.yml: -------------------------------------------------------------------------------- 1 | # This Yaml Document has been converted by ESAI Yaml Pipeline Conversion Tool. 2 | # This pipeline will be extended to the OneESPT template 3 | 4 | # Release build script for language-service 5 | # Only trigger manually 6 | trigger: none 7 | pr: none 8 | resources: 9 | repositories: 10 | - repository: 1ESPipelineTemplates 11 | type: git 12 | name: 1ESPipelineTemplates/1ESPipelineTemplates 13 | ref: refs/tags/release 14 | extends: 15 | template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates 16 | parameters: 17 | sdl: 18 | sourceAnalysisPool: 19 | name: 1ES-ABTT-Shared-Pool 20 | image: abtt-windows-2022 21 | os: windows 22 | pool: 23 | name: 1ES-ABTT-Shared-Pool 24 | image: abtt-ubuntu-2204 25 | os: linux 26 | customBuildTags: 27 | - ES365AIMigrationTooling 28 | stages: 29 | - stage: stage 30 | jobs: 31 | - template: /.pipelines/build-package.yml@self 32 | parameters: 33 | name: 'build_language_service' 34 | root: 'language-service' 35 | packagename: 'azure-pipelines-language-service' 36 | tarballPath: 'language-service' 37 | configuration: 'release' -------------------------------------------------------------------------------- /.pipelines/version-ci.yml: -------------------------------------------------------------------------------- 1 | # for convenience, we tag CI-produced packages with a version number 2 | # pointing to the commit which was built. for PRs, also include the PR #. 3 | 4 | parameters: 5 | root: '' # root folder of the package 6 | 7 | steps: 8 | - bash: | 9 | PACKAGE_VERSION=$(cat version.txt) 10 | 11 | if [ -n "$SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" ]; then 12 | VERSION_STRING=${PACKAGE_VERSION}-pr-${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER}-$(git rev-parse --short HEAD) 13 | else 14 | VERSION_STRING=${PACKAGE_VERSION}-ci-$(git rev-parse --short HEAD) 15 | fi 16 | 17 | npm --no-git-tag-version version $VERSION_STRING 18 | echo "##vso[build.updatebuildnumber]${VERSION_STRING}_${BUILD_BUILDID}" 19 | workingDirectory: ${{ parameters.root }} 20 | displayName: Set version number of package and build 21 | -------------------------------------------------------------------------------- /.pipelines/version-release.yml: -------------------------------------------------------------------------------- 1 | # release version should be correctly set in package.json 2 | parameters: 3 | root: '' 4 | 5 | steps: 6 | - bash: | 7 | PACKAGE_VERSION=$(cat version.txt) 8 | echo "##vso[build.updatebuildnumber]${PACKAGE_VERSION}_release_${BUILD_BUILDID}" 9 | workingDirectory: ${{ parameters.root }} 10 | displayName: Set version number of build 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | // List of configurations. Add new configurations or edit existing ones. 4 | "configurations": [ 5 | { 6 | "name": "Attach", 7 | "type": "node", 8 | "request": "attach", 9 | "port": 6009, 10 | "outFiles": [ 11 | "${workspaceFolder}/language-server/out/**/*.ts", 12 | "${workspaceFolder}/language-service/lib/**/*.ts" 13 | ], 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "command": "npm", 4 | "args": ["run", "watch"], 5 | "isBackground": true, 6 | "problemMatcher": "$tsc-watch", 7 | "tasks": [ 8 | { 9 | "label": "npm", 10 | "type": "shell", 11 | "command": "npm", 12 | "args": [ 13 | "run", 14 | "watch" 15 | ], 16 | "isBackground": true, 17 | "problemMatcher": "$tsc-watch", 18 | "group": "build" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributing 3 | 4 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 5 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 6 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 9 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 10 | provided by the bot. You will only need to do this once across all repos using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 14 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Build status 2 | 3 | [![Build Status](https://dev.azure.com/mseng/PipelineTools/_apis/build/status%2Fazure-pipelines-language-server%2FLangserv%20CI?repoName=microsoft%2Fazure-pipelines-language-server&branchName=main)](https://dev.azure.com/mseng/PipelineTools/_build/latest?definitionId=17102&repoName=microsoft%2Fazure-pipelines-language-server&branchName=main) 4 | 5 | ## Features 6 | 7 | 1. YAML validation: 8 | * Detects whether the entire file is valid yaml 9 | 2. Validation: 10 | * Detects errors such as: 11 | * Node is not found 12 | * Node has an invalid key node type 13 | * Node has an invalid type 14 | * Node is not a valid child node 15 | * Detects warnings such as: 16 | * Node is an additional property of parent 17 | 3. Auto completion: 18 | * Auto completes on all commands 19 | * Scalar nodes autocomplete to schema's defaults if they exist 20 | 4. Hover support: 21 | * Hovering over a node shows description *if available* 22 | 5. Document outlining: 23 | * Shows a complete document outline of all nodes in the document 24 | 6. Go to definition for Templates 25 | * Referenced templates can be resolved to a local file (if it exists) 26 | 27 | ## Developer Support 28 | 29 | This repo consists of 2 separate projects/packages: 30 | 1. * [azure-pipelines-language-service](https://github.com/Microsoft/azure-pipelines-language-server/tree/main/language-service) - language service implementation for azure-pipelines 31 | 2. * [azure-pipelines-language-server](https://github.com/Microsoft/azure-pipelines-language-server/tree/main/language-server) - language server implementation that dependes on azure-pipelines-language-service 32 | 33 | In order to tighten the dev loop you can utilize `npm link` that will sync changes to service package without re-installing. 34 | 35 | 1. First, install dependencies in the language service and start watching for changes: 36 | * `cd language-service` 37 | * `npm install` 38 | * `npm run watch` 39 | 2. Next, link the language service to the language server and start watching for changes: 40 | * `cd ../language-server` 41 | * `npm install` 42 | * `npm link ../language-service` 43 | * `npm run watch` 44 | 3. Now, any changes you make in the service will automatically be reflected in the server 45 | 46 | ### Connecting to the language server via stdio 47 | There's an option to connect to the language server via [stdio](https://github.com/redhat-developer/yaml-language-server/blob/681985b5a059c2cb55c8171235b07e1651b6c546/src/server.ts#L46-L51) to help with intergrating the language server into different clients. 48 | 49 | ## Thanks 50 | 51 | This project was forked from the [YAML Language Server](https://github.com/redhat-developer/yaml-language-server) by Red Hat. 52 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | 1. Choose your new target version number. For this, we'll use "0.1.2". 4 | 2. Make a branch to ship from. `git switch -c ship-0.1.2` 5 | 3. Release the service first. `cd language-service` 6 | 1. Bump the language-service version. `npm version --no-git-tag-version 0.1.2` 7 | 2. Ensure there's an entry for this new version in the service `changelog.md`. 8 | 3. Commit and push. `git commit -am "version service" && git push -u origin ship-0.1.2` 9 | 4. **This step creates a GitHub Release** Run the [service release pipeline][release-service], targeting your ship branch. (NB: make sure you're logged into Azure DevOps. It's a public project so you can view it anonymously, and may have to explicitly log in from the upper right.) 10 | 5. Assuming the build works, download the `.tgz` either from **Artifacts** on the build summary page or from the new GitHub release that was created. 11 | 6. Publish this to npm. `npm publish azure-pipelines-language-service-0.1.2.tgz` 12 | 4. Bump the dependency in the server. 13 | 1. Get to the right directory. `cd ../language-server` 14 | 2. Update the dependency. `npm i azure-pipelines-language-service@0.1.2 --save-exact` 15 | 5. Release the server. (You're already in the right directory if you followed the above steps.) 16 | 1. Bump the language-server version. `npm version --no-git-tag-version 0.1.2` 17 | 2. Ensure there's an entry for this new version in the server `changelog.md`. (TODO/improvement: we should hardlink the server/service changelog files, as there's never a time when they should differ.) 18 | 3. Commit and push. `git commit -am "version server" && git push -u origin ship-0.1.2` 19 | 4. **This step creates a GitHub Release** Run the [server release pipeline][release-server], targeting your ship branch. 20 | 5. Assuming the build works, download the `.tgz` either from **Artifacts** on the build summary page or from the new GitHub release that was created. 21 | 6. Publish this to npm. `npm publish azure-pipelines-language-server-0.1.2.tgz` 22 | 6. Create a PR from your ship branch to `main`. Merge it. :tada: you're done! 23 | 24 | [release-service]: https://dev.azure.com/mseng/PipelineTools/_build?definitionId=17100 "Language service release pipeline" 25 | [release-server]: https://dev.azure.com/mseng/PipelineTools/_build?definitionId=17139 "Language server release pipeline" 26 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /language-server/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | .editorconfig 4 | Jenkinsfile 5 | tsconfig.json 6 | -------------------------------------------------------------------------------- /language-server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 0.8.0 2 | - Added "go to definition" support for templates [#PR-157](https://github.com/microsoft/azure-pipelines-language-server/pull/157) - thanks @Stuart-Wilcox! 3 | - Added a standalone `azure-pipelines-language-server` executable [#PR-147](https://github.com/microsoft/azure-pipelines-language-server/pull/147) - thanks @williamboman! 4 | - Updated dependencies 5 | 6 | #### 0.7.0 7 | - Added support for using expressions as values [#PR-138](https://github.com/microsoft/azure-pipelines-language-server/pull/138) 8 | - Fixed badly-indented files crashing the language server [#PR-141](https://github.com/microsoft/azure-pipelines-language-server/pull/141) 9 | 10 | #### 0.6.9 11 | - Fixed loops crashing the language server when the first key/value pair had a dynamic expression as the key [#PR-130](https://github.com/microsoft/azure-pipelines-language-server/pull/116) 12 | 13 | #### 0.6.8 14 | - Updated dependencies 15 | 16 | #### 0.6.7 17 | - Support emojis [#PR-115](https://github.com/microsoft/azure-pipelines-language-server/pull/116) - thanks @PaulTaykalo! 18 | 19 | #### 0.6.6 20 | - Fixed property autocomplete adding unnecessary colons [#PR-113](https://github.com/microsoft/azure-pipelines-language-server/pull/113) 21 | - More conditional expression fixes [#PR-114](https://github.com/microsoft/azure-pipelines-language-server/pull/114) 22 | 23 | #### 0.6.5 24 | - Conditional variable fixes - thanks @50Wliu 25 | 26 | #### 0.6.4 27 | - Handle dynamic variables - thanks @50Wliu 28 | 29 | #### 0.6.3 30 | - Add basic support for expressions 31 | 32 | #### 0.6.2 33 | - Dependency updates 34 | 35 | #### 0.6.1 36 | - Webpack 37 | 38 | #### 0.6.0 39 | - Improved debuggability - thanks @50Wliu 40 | - Several security fixes as recommended by dependabot 41 | 42 | **🚨 BREAKING CHANGE 🚨**: the internal directory structure has changed a bit in this release (hence the bump from 0.5.x -> 0.6.x). 43 | Where you previously said `path.join('node_modules', 'azure-pipelines-language-server', 'server.js')`, you'll now need to say `path.join('node_modules', 'azure-pipelines-language-server', 'out', 'server.js')`. 44 | 45 | #### 0.5.10 46 | Update dependencies for a security issue[#PR-73](https://github.com/microsoft/azure-pipelines-language-server/pull/73) 47 | 48 | #### 0.5.9 49 | Consume version 0.5.9 of the language service [#PR-69](https://github.com/Microsoft/azure-pipelines-language-server/pull/69) 50 | 51 | #### 0.5.4 52 | Consume version 0.5.4 of the language service [#PR-54](https://github.com/Microsoft/azure-pipelines-language-server/pull/54) 53 | 54 | #### 0.5.2 55 | Consume version 0.5.2 of the language service [#PR-48](https://github.com/Microsoft/azure-pipelines-language-server/pull/48) 56 | 57 | #### 0.4.2 58 | Improve build/packaging process [#PR-41](https://github.com/Microsoft/azure-pipelines-language-server/pull/41) 59 | 60 | #### 0.4.1 61 | Consume version 0.4.1 of the language service [#PR-34](https://github.com/Microsoft/azure-pipelines-language-server/pull/34) 62 | 63 | #### 0.4.0 64 | Consume version 0.4.0 of the language service [#PR-27](https://github.com/Microsoft/azure-pipelines-language-server/pull/27) 65 | 66 | #### 0.2.1 67 | Update to coveralls@3.0.2 to get rid of [cryptiles vulenerability](https://github.com/hapijs/cryptiles/issues/34) 68 | 69 | #### 0.2.0 70 | Split the package into azure-pipelines-language-server and azure-pipelines-language-service to promote reusability [#PR-4](https://github.com/Microsoft/azure-pipelines-language-server/pull/4) 71 | 72 | #### 0.1.0 73 | Changed name to reflect fork 74 | 75 | #### 0.0.15 76 | 77 | Updated formatter to use prettier [#Commit](https://github.com/redhat-developer/yaml-language-server/commit/feb604c35b8fb11747dfcb79a5d8570bf81b8f67) 78 | Fixed dynamic registration of formatter [#74](https://github.com/redhat-developer/yaml-language-server/issues/74) 79 | 80 | #### 0.0.14 81 | 82 | Bumped to fix jenkins errors 83 | 84 | #### 0.0.13 85 | - Show errors if schema cannot be grabbed [#73](https://github.com/redhat-developer/yaml-language-server/issues/73) 86 | - The validator should support null values [#72](https://github.com/redhat-developer/yaml-language-server/issues/72) 87 | - Server returning nothing on things such as completion errors Eclipse Che [#66](https://github.com/redhat-developer/yaml-language-server/issues/66) 88 | - Return promises that resolve to null [#PR-71](https://github.com/redhat-developer/yaml-language-server/pull/71) 89 | - Remove unused dependency to deep-equal [#PR-70](https://github.com/redhat-developer/yaml-language-server/pull/70) 90 | - Added custom tags to autocompletion [#Commit](https://github.com/redhat-developer/yaml-language-server/commit/73c244a3efe09ec4250def78068c54af3acaed58) 91 | 92 | #### 0.0.12 93 | - Support for custom tags [#59](https://github.com/redhat-developer/yaml-language-server/issues/59) 94 | - Incorrect duplicate key registered when using YAML anchors [#82](https://github.com/redhat-developer/vscode-yaml/issues/82) 95 | - Automatically insert colon on autocomplete [#78](https://github.com/redhat-developer/vscode-yaml/issues/78) 96 | 97 | #### 0.0.11 98 | - Fix for completion helper if it contains \r [#37](https://github.com/redhat-developer/yaml-language-server/issues/37) 99 | 100 | #### 0.0.10 101 | - Programmatically associate YAML files with schemas by other extensions [#61](https://github.com/redhat-developer/vscode-yaml/issues/61) 102 | - Autocompletion not triggered while typing [#46](https://github.com/redhat-developer/vscode-yaml/issues/46) 103 | 104 | #### 0.0.9 105 | - Remove console.log from jsonSchemaService [#49](https://github.com/redhat-developer/yaml-language-server/issues/49) 106 | - Change "Property {$property_name} is not allowed" error message [#42](https://github.com/redhat-developer/yaml-language-server/issues/42) 107 | - New Kubernetes Schema + Updated support for Kubernetes [#40](https://github.com/redhat-developer/yaml-language-server/issues/40) 108 | 109 | #### 0.0.8 110 | - Added Kedge back in as one of the default schemas 111 | - Added file watch for json schema files in the workspace [#34](https://github.com/redhat-developer/yaml-language-server/issues/34) 112 | - Multi root settings [#50](https://github.com/redhat-developer/vscode-yaml/issues/50) 113 | - Fix for crashing yaml language server when !include is present [#52](https://github.com/redhat-developer/vscode-yaml/issues/52) 114 | - Update tests to work on windows [#30](https://github.com/redhat-developer/yaml-language-server/issues/30) 115 | 116 | #### 0.0.7 117 | - Added validation toggle in settings [#20](https://github.com/redhat-developer/yaml-language-server/issues/20) 118 | - YAML Schemas are pulled from JSON Schema Store [#15](https://github.com/redhat-developer/yaml-language-server/issues/15) 119 | - YAML Diagnostics throw on a single line instead of the entire file [#19](https://github.com/redhat-developer/yaml-language-server/issues/19) 120 | - Fix for getNodeFromOffset [#18](https://github.com/redhat-developer/yaml-language-server/issues/18) 121 | 122 | #### 0.0.6 123 | - Hotfix for making multiple schemas in the settings work again 124 | 125 | #### 0.0.5 126 | - Fixed Schema validation reports errors in valid YAML document [#42](https://github.com/redhat-developer/vscode-yaml/issues/42) 127 | - Fixed Support for multiple YAML documents in single file [#43](https://github.com/redhat-developer/vscode-yaml/issues/43) 128 | 129 | #### 0.0.4 130 | - Fixed support for kubernetes files 131 | - Fixed boolean notation for validation [#40](https://github.com/redhat-developer/vscode-yaml/issues/40) 132 | - Fixed autocompletion for first new list item [#39](https://github.com/redhat-developer/vscode-yaml/issues/39) 133 | 134 | #### 0.0.3 135 | - Added new autocompletion service which is better for json schemas 136 | - Added yamlValidation contribution point [#37](https://github.com/redhat-developer/vscode-yaml/issues/37) 137 | 138 | #### 0.0.1 139 | - Initial release with support for hover, document outlining, validation and auto completion 140 | -------------------------------------------------------------------------------- /language-server/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation and others. All rights reserved. 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 | -------------------------------------------------------------------------------- /language-server/README.md: -------------------------------------------------------------------------------- 1 | ## Features 2 | 3 | 1. YAML validation: 4 | * Detects whether the entire file is valid yaml 5 | 2. Validation: 6 | * Detects errors such as: 7 | * Node is not found 8 | * Node has an invalid key node type 9 | * Node has an invalid type 10 | * Node is not a valid child node 11 | * Detects warnings such as: 12 | * Node is an additional property of parent 13 | 3. Auto completion: 14 | * Auto completes on all commands 15 | * Scalar nodes autocomplete to schema's defaults if they exist 16 | 4. Hover support: 17 | * Hovering over a node shows description *if available* 18 | 5. Document outlining: 19 | * Shows a complete document outline of all nodes in the document 20 | 21 | ## Language Server Settings 22 | 23 | The following settings are supported: 24 | * `yaml.format.enable`: Enable/disable default YAML formatter 25 | * `yaml.validate`: Enable/disable validation feature 26 | * `yaml.schemas`: Helps you associate schemas with files in a glob pattern 27 | * `yaml.customTags`: Array of custom tags that the parser will validate against. It has two ways to be used. Either an item in the array is a custom tag such as "!Ref" or you can specify the type of the object !Ref should be by doing "!Ref scalar". For example: ["!Ref", "!Some-Tag scalar"]. The type of object can be one of scalar, sequence, mapping, map. 28 | 29 | ##### Associating a schema to a glob pattern via yaml.schemas: 30 | When associating a schema it should follow the format below 31 | ``` 32 | yaml.schemas: { 33 | "url": "globPattern", 34 | "Kubernetes": "globPattern", 35 | "kedge": "globPattern" 36 | } 37 | ``` 38 | 39 | e.g. 40 | ``` 41 | yaml.schemas: { 42 | "http://json.schemastore.org/composer": "/*" 43 | } 44 | ``` 45 | 46 | e.g. 47 | 48 | ``` 49 | yaml.schemas: { 50 | "kubernetes": "/myYamlFile.yaml" 51 | } 52 | ``` 53 | e.g. 54 | ``` 55 | yaml.schemas: { 56 | "kedge": "/myKedgeApp.yaml" 57 | } 58 | ``` 59 | 60 | e.g. 61 | ``` 62 | yaml.schemas: { 63 | "http://json.schemastore.org/composer": "/*", 64 | "kubernetes": "/myYamlFile.yaml" 65 | } 66 | ``` 67 | 68 | `yaml.schemas` extension allows you to specify json schemas that you want to validate against the yaml that you write. Kubernetes and kedge are optional fields. They do not require a url as the language server will provide that. You just need the keywords kubernetes/kedge and a glob pattern. 69 | 70 | ## Clients 71 | This repository only contains the server implementation. Here are some known clients consuming this server: 72 | 73 | * [Eclipse Che](https://www.eclipse.org/che/) 74 | * [vscode-yaml](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) for VSCode 75 | * [ide-yaml](https://atom.io/packages/ide-yaml) for Atom editor 76 | 77 | ## Thanks 78 | 79 | This project was forked from the [YAML Language Server](https://github.com/redhat-developer/yaml-language-server) by Red Hat. -------------------------------------------------------------------------------- /language-server/bin/azure-pipelines-language-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../out/server.js'); 4 | -------------------------------------------------------------------------------- /language-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-pipelines-language-server", 3 | "description": "Azure Pipelines language server", 4 | "version": "0.8.0", 5 | "author": "Microsoft", 6 | "license": "MIT", 7 | "contributors": [ 8 | { 9 | "name": "Stephen Franceschelli", 10 | "email": "Stephen.Franceschelli@microsoft.com" 11 | }, 12 | { 13 | "name": "Matt Cooper", 14 | "email": "vtbassmatt@gmail.com" 15 | }, 16 | { 17 | "name": "Ruslan Semenov", 18 | "email": "ruslan@rsemenov.com" 19 | }, 20 | { 21 | "name": "Winston Liu", 22 | "email": "Wliu1402@gmail.com" 23 | } 24 | ], 25 | "engines": { 26 | "node": "*" 27 | }, 28 | "bin": { 29 | "azure-pipelines-language-server": "./bin/azure-pipelines-language-server" 30 | }, 31 | "keywords": [ 32 | "azure-pipelines", 33 | "LSP" 34 | ], 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/microsoft/azure-pipelines-language-server.git" 38 | }, 39 | "dependencies": { 40 | "azure-pipelines-language-service": "0.8.0", 41 | "request-light": "^0.4.0", 42 | "vscode-languageserver": "^7.0.0", 43 | "vscode-languageserver-textdocument": "^1.0.1", 44 | "vscode-languageserver-types": "^3.16.0", 45 | "vscode-nls": "^5.0.0", 46 | "vscode-uri": "^3.0.2" 47 | }, 48 | "devDependencies": { 49 | "@types/mocha": "^8.2.0", 50 | "@types/node": "^14.0.0", 51 | "mocha": "^10.1.0", 52 | "mocha-junit-reporter": "^2.0.0", 53 | "nyc": "^15.1.0", 54 | "source-map-support": "^0.5.19", 55 | "ts-node": "^9.1.1", 56 | "typescript": "~4.4.4" 57 | }, 58 | "scripts": { 59 | "build": "npm run compile && npm pack", 60 | "compile": "tsc -p .", 61 | "watch": "tsc -watch -p .", 62 | "test": "mocha --require ts-node/register --ui bdd ./test/**/*.test.ts", 63 | "coverage": "nyc mocha --require ts-node/register --require source-map-support/register --recursive --ui bdd ./test/**/*.test.ts" 64 | }, 65 | "nyc": { 66 | "extension": [ 67 | ".ts", 68 | ".tsx" 69 | ], 70 | "exclude": [ 71 | "**/*.d.ts", 72 | "test/", 73 | "out" 74 | ], 75 | "all": true 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /language-server/test/autoCompletion.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import { LanguageService, getLanguageService } from 'azure-pipelines-language-service' 7 | import { schemaRequestService, workspaceContext } from './testHelper'; 8 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 9 | import { completionHelper } from 'azure-pipelines-language-service'; 10 | var assert = require('assert'); 11 | 12 | let languageService: LanguageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 13 | 14 | 15 | let uri = 'http://json.schemastore.org/bowerrc'; 16 | let languageSettings = { 17 | schemas: [] 18 | }; 19 | let fileMatch = ["*.yml", "*.yaml"]; 20 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 21 | languageService.configure(languageSettings); 22 | 23 | describe("Auto Completion Tests", () => { 24 | 25 | 26 | describe('yamlCompletion with bowerrc', function(){ 27 | 28 | describe('doComplete', function(){ 29 | 30 | function parseSetup(content: string, position){ 31 | const document = TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 32 | const completion = completionHelper(document, document.positionAt(position)); 33 | const jsonDocument = parseYAML(completion.newText); 34 | return languageService.doComplete(document, completion.newPosition, jsonDocument); 35 | } 36 | 37 | it('Autocomplete on root node without word', (done) => { 38 | const content: string = ""; 39 | const completion = parseSetup(content, content.length); 40 | completion.then(function(result){ 41 | assert.notEqual(result.items.length, 0); 42 | }).then(done, done); 43 | }); 44 | 45 | it('Autocomplete on root node with word', (done) => { 46 | const content: string = "analyt"; 47 | const completion = parseSetup(content, content.length); 48 | completion.then(function(result){ 49 | assert.notEqual(result.items.length, 0); 50 | }).then(done, done); 51 | }); 52 | 53 | it('Autocomplete on default value (without value content)', (done) => { 54 | const content: string = "directory: "; 55 | const completion = parseSetup(content, content.length); 56 | completion.then(function(result){ 57 | assert.notEqual(result.items.length, 0); 58 | }).then(done, done); 59 | }); 60 | 61 | it('Autocomplete on default value (with value content)', (done) => { 62 | const content: string = "directory: bow"; 63 | const completion = parseSetup(content, content.length); 64 | completion.then(function(result){ 65 | assert.notEqual(result.items.length, 0); 66 | }).then(done, done); 67 | }); 68 | 69 | it('Autocomplete on boolean value (without value content)', (done) => { 70 | const content: string = "analytics: "; 71 | const completion = parseSetup(content, content.length); 72 | completion.then(function(result){ 73 | assert.equal(result.items.length, 2); 74 | }).then(done, done); 75 | }); 76 | 77 | it('Autocomplete on boolean value (with value content)', (done) => { 78 | const content: string = "analytics: fal"; 79 | const completion = parseSetup(content, content.length); 80 | completion.then(function(result){ 81 | assert.equal(result.items.length, 2); 82 | }).then(done, done); 83 | }); 84 | 85 | it('Autocomplete on number value (without value content)', (done) => { 86 | const content: string = "timeout: "; 87 | const completion = parseSetup(content, content.length); 88 | completion.then(function(result){ 89 | assert.equal(result.items.length, 1); 90 | }).then(done, done); 91 | }); 92 | 93 | it('Autocomplete on number value (with value content)', (done) => { 94 | const content: string = "timeout: 6"; 95 | const completion = parseSetup(content, content.length); 96 | completion.then(function(result){ 97 | assert.equal(result.items.length, 1); 98 | }).then(done, done); 99 | }); 100 | 101 | it('Autocomplete key in middle of file', (done) => { 102 | const preCursorContent: string = "scripts:\n po"; 103 | const postCursorConent: string = "st"; 104 | const completion = parseSetup(preCursorContent + postCursorConent, preCursorContent.length); 105 | completion.then(function(result){ 106 | assert.notEqual(result.items.length, 0); 107 | }).then(done, done); 108 | }); 109 | 110 | it('Autocomplete key in middle of file 2', (done) => { 111 | const preCursorContent: string = "scripts:\n postinstall: /test\n pr" 112 | const postCursorContent: string = "einsta"; 113 | const completion = parseSetup(preCursorContent + postCursorContent, preCursorContent.length); 114 | completion.then(function(result){ 115 | assert.notEqual(result.items.length, 0); 116 | }).then(done, done); 117 | }); 118 | 119 | it('Autocomplete does not happen right before :', (done) => { 120 | const preCursorContent: string = "analytics"; 121 | const postCursorContent: string = ":"; 122 | const completion = parseSetup(preCursorContent + postCursorContent, preCursorContent.length); 123 | completion.then(function(result){ 124 | assert.notEqual(result.items.length, 0); 125 | }).then(done, done); 126 | }); 127 | 128 | it('Autocomplete does not happen right before : under an object', (done) => { 129 | const preCursorContent: string = "scripts:\n postinstall"; 130 | const postCursorContent: string = ":"; 131 | const completion = parseSetup(preCursorContent + postCursorContent, preCursorContent.length); 132 | completion.then(function(result){ 133 | assert.notEqual(result.items.length, 0); 134 | }).then(done, done); 135 | }); 136 | }); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /language-server/test/autoCompletion2.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import { LanguageService, getLanguageService } from 'azure-pipelines-language-service' 7 | import { schemaRequestService, workspaceContext } from './testHelper'; 8 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 9 | import { completionHelper } from 'azure-pipelines-language-service'; 10 | var assert = require('assert'); 11 | 12 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 13 | 14 | 15 | let uri = 'https://raw.githubusercontent.com/composer/composer/master/res/composer-schema.json'; 16 | let languageSettings = { 17 | schemas: [] 18 | }; 19 | let fileMatch = ["*.yml", "*.yaml"]; 20 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 21 | languageService.configure(languageSettings); 22 | 23 | describe("Auto Completion Tests", () => { 24 | 25 | function parseSetup(content: string, position){ 26 | let document = TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 27 | let completion = completionHelper(document, document.positionAt(position)); 28 | let jsonDocument = parseYAML(completion.newText); 29 | return languageService.doComplete(document, completion.newPosition, jsonDocument); 30 | } 31 | 32 | describe('yamlCompletion with composer', function(){ 33 | 34 | describe('doComplete', function(){ 35 | 36 | it('Array autocomplete without word', (done) => { 37 | let content = "authors:\n - "; 38 | let completion = parseSetup(content, content.length); 39 | completion.then(function(result){ 40 | assert.notEqual(result.items.length, 0); 41 | }).then(done, done); 42 | }); 43 | 44 | it('Array autocomplete with letter', (done) => { 45 | let content = "authors:\n - n"; 46 | let completion = parseSetup(content, content.length); 47 | completion.then(function(result){ 48 | assert.notEqual(result.items.length, 0); 49 | }).then(done, done); 50 | }); 51 | 52 | it('Array autocomplete without word (second item)', (done) => { 53 | let content = "authors:\n - name: test\n "; 54 | let completion = parseSetup(content, content.length); 55 | completion.then(function(result){ 56 | assert.notEqual(result.items.length, 0); 57 | }).then(done, done); 58 | }); 59 | 60 | it('Array autocomplete with letter (second item)', (done) => { 61 | let content = "authors:\n - name: test\n e"; 62 | let completion = parseSetup(content, content.length); 63 | completion.then(function(result){ 64 | assert.notEqual(result.items.length, 0); 65 | }).then(done, done); 66 | }); 67 | 68 | it('Autocompletion after array', (done) => { 69 | let content = "authors:\n - name: test\n" 70 | let completion = parseSetup(content, content.length); 71 | completion.then(function(result){ 72 | assert.notEqual(result.items.length, 0); 73 | }).then(done, done); 74 | }); 75 | 76 | it('Autocompletion after array with depth', (done) => { 77 | let content = "archive:\n exclude:\n - test\n" 78 | let completion = parseSetup(content, content.length); 79 | completion.then(function(result){ 80 | assert.notEqual(result.items.length, 0); 81 | }).then(done, done); 82 | }); 83 | 84 | it('Autocompletion after array with depth', (done) => { 85 | let content = "autoload:\n classmap:\n - test\n exclude-from-classmap:\n - test\n " 86 | let completion = parseSetup(content, content.length); 87 | completion.then(function(result){ 88 | assert.notEqual(result.items.length, 0); 89 | }).then(done, done); 90 | }); 91 | 92 | }); 93 | 94 | describe('Failure tests', function(){ 95 | 96 | it('Autocompletion has no results on value when they are not available', (done) => { 97 | let content = "time: " 98 | let completion = parseSetup(content, content.length); 99 | completion.then(function(result){ 100 | assert.equal(result.items.length, 0); 101 | }).then(done, done); 102 | }); 103 | 104 | it('Autocompletion has no results on value when they are not available (with depth)', (done) => { 105 | let content = "archive:\n exclude:\n - test\n " 106 | let completion = parseSetup(content, content.length); 107 | completion.then(function(result){ 108 | assert.equal(result.items.length, 0); 109 | }).then(done, done); 110 | }); 111 | 112 | it('Autocompletion does not complete on wrong spot in array node', (done) => { 113 | let content = "authors:\n - name: test\n " 114 | let completion = parseSetup(content, content.length); 115 | completion.then(function(result){ 116 | assert.equal(result.items.length, 0); 117 | }).then(done, done); 118 | }); 119 | 120 | }); 121 | 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /language-server/test/autoCompletion3.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "name": "AutoCompleteTest", 4 | "type": "object", 5 | "additionalProperties": false, 6 | "properties": { 7 | "objectWithSingleFirstProperty": { 8 | "description": "Test object that has a single firstProperty", 9 | "type": "object", 10 | "required": [ "req1", "req2" ], 11 | "firstProperty": [ "mustBeFirst1" ], 12 | "properties": { 13 | "req1": { "type": "string" }, 14 | "req2": { "type": "string" }, 15 | "mustBeFirst1": { "type": "string" }, 16 | "otherProperty": { "type": "string" } 17 | }, 18 | "additionalProperties": true 19 | }, 20 | "objectWithMultipleFirstProperty": { 21 | "description": "Test object that has a more than one firstProperty", 22 | "type": "object", 23 | "required": [ "req1", "req2" ], 24 | "firstProperty": [ "mustBeFirst1", "mustBeFirst2" ], 25 | "properties": { 26 | "req1": { "type": "string" }, 27 | "req2": { "type": "string" }, 28 | "mustBeFirst1": { "type": "string" }, 29 | "mustBeFirst2": { "type": "string" }, 30 | "otherProperty": { "type": "string" } 31 | }, 32 | "additionalProperties": true 33 | }, 34 | "objectWithEmptyFirstProperty": { 35 | "description": "Test object that has an empty firstProperty", 36 | "type": "object", 37 | "required": [ "req1", "req2" ], 38 | "firstProperty": [], 39 | "properties": { 40 | "req1": { "type": "string" }, 41 | "req2": { "type": "string" }, 42 | "canBeFirst1": { "type": "string" }, 43 | "canBeFirst2": { "type": "string" }, 44 | "otherProperty": { "type": "string" } 45 | }, 46 | "additionalProperties": true 47 | }, 48 | "deprecatedObject": { 49 | "description": "Test object that has a deprecation message", 50 | "type": "object", 51 | "required": [ "req1", "req2" ], 52 | "firstProperty": [], 53 | "properties": { 54 | "req1": { "type": "string" }, 55 | "req2": { "type": "string" }, 56 | "canBeFirst1": { "type": "string" }, 57 | "canBeFirst2": { "type": "string" }, 58 | "otherProperty": { "type": "string" } 59 | }, 60 | "additionalProperties": true, 61 | "deprecationMessage": "This object is deprecated" 62 | }, 63 | "doNotSuggestObject": { 64 | "description": "Test object that has is marked do not suggest", 65 | "type": "object", 66 | "required": [ "req1", "req2" ], 67 | "firstProperty": [], 68 | "properties": { 69 | "req1": { "type": "string" }, 70 | "req2": { "type": "string" }, 71 | "canBeFirst1": { "type": "string" }, 72 | "canBeFirst2": { "type": "string" }, 73 | "otherProperty": { "type": "string" } 74 | }, 75 | "additionalProperties": true, 76 | "doNotSuggest": true 77 | }, 78 | "objectWithDeprecatedString": { 79 | "description": "Test object that has a property with a deprecation message", 80 | "type": "object", 81 | "required": [ "req1", "req2" ], 82 | "firstProperty": [], 83 | "properties": { 84 | "req1": { "type": "string" }, 85 | "req2": { "type": "string" }, 86 | "canBeFirst1": { "type": "string" }, 87 | "canBeFirst2": { "type": "string" }, 88 | "otherProperty": { 89 | "type": "string", 90 | "deprecationMessage": "This option is deprecated" 91 | } 92 | }, 93 | "additionalProperties": true 94 | }, 95 | "objectWithDeprecatedSubObject": { 96 | "description": "Test object that has a property with a do not suggest", 97 | "type": "object", 98 | "required": [ "req1", "req2" ], 99 | "firstProperty": [], 100 | "properties": { 101 | "req1": { "type": "string" }, 102 | "req2": { "type": "string" }, 103 | "canBeFirst1": { "type": "string" }, 104 | "canBeFirst2": { "type": "string" }, 105 | "otherProperty": { 106 | "type": "object", 107 | "doNotSuggest": true, 108 | "properties": { 109 | "otherProp": { "type": "string" }, 110 | "otherOtherProp": { "type": "string" } 111 | } 112 | } 113 | }, 114 | "additionalProperties": true 115 | }, 116 | "objectWithTwoDeprecatedProperties": { 117 | "description": "Test object that has a property with a deprecation message and do not suggest", 118 | "type": "object", 119 | "required": [ "req1" ], 120 | "firstProperty": [], 121 | "properties": { 122 | "req1": { "type": "string" }, 123 | "noLongerReq2": { 124 | "type": "string", 125 | "deprecationMessage": "This property is deprecated", 126 | "doNotSuggest": true 127 | }, 128 | "canBeFirst1": { "type": "string" }, 129 | "canBeFirst2": { "type": "string" }, 130 | "otherProperty": { 131 | "type": "object", 132 | "doNotSuggest": true, 133 | "properties": { 134 | "otherProp": { "type": "string" }, 135 | "otherOtherProp": { "type": "string" } 136 | } 137 | } 138 | }, 139 | "additionalProperties": true 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /language-server/test/autoCompletion3.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import { LanguageService, getLanguageService } from 'azure-pipelines-language-service' 7 | import { schemaRequestService, workspaceContext } from './testHelper'; 8 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 9 | import { completionHelper } from 'azure-pipelines-language-service'; 10 | var assert = require('assert'); 11 | 12 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 13 | 14 | let dirName = __dirname.replace("\\", "/"); 15 | if (!dirName.startsWith("/")) { 16 | dirName = "/" + dirName; 17 | } 18 | let uri = 'file://' + dirName + '/autoCompletion3.schema.json'; 19 | let languageSettings = { 20 | schemas: [] 21 | }; 22 | let fileMatch = ["*.yml", "*.yaml"]; 23 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 24 | languageService.configure(languageSettings); 25 | 26 | describe("Auto Completion Tests", () => { 27 | 28 | function parseSetup(content: string, position){ 29 | let document = TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 30 | let completion = completionHelper(document, document.positionAt(position)); 31 | let jsonDocument = parseYAML(completion.newText); 32 | return languageService.doComplete(document, completion.newPosition, jsonDocument); 33 | } 34 | 35 | describe('yamlCompletion with firstProperty', function(){ 36 | 37 | describe('doComplete', function(){ 38 | 39 | it('single entry firstProperty', (done) => { 40 | let content = "objectWithSingleFirstProperty:\n "; 41 | let completion = parseSetup(content, content.length); 42 | completion.then(function(result){ 43 | assert.equal(result.items.length, 1); 44 | }).then(done, done); 45 | }); 46 | 47 | it('multiple entry firstProperty', (done) => { 48 | let content = "objectWithMultipleFirstProperty:\n "; 49 | let completion = parseSetup(content, content.length); 50 | completion.then(function (result) { 51 | assert.equal(result.items.length, 2); 52 | }).then(done, done); 53 | }); 54 | 55 | it('empty firstProperty', (done) => { 56 | let content = "objectWithEmptyFirstProperty:\n "; 57 | let completion = parseSetup(content, content.length); 58 | completion.then(function (result) { 59 | assert.equal(result.items.length, 5); 60 | }).then(done, done); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('yamlCompletion with deprecation', function () { 66 | 67 | describe('doComplete', function () { 68 | 69 | it('object with deprecated message', (done) => { 70 | let content = "deprecatedObject:\n "; 71 | let completion = parseSetup(content, content.length); 72 | completion.then(function (result) { 73 | //the user has already created the object, we should still suggest its properties 74 | assert.equal(result.items.length, 5); 75 | }).then(done, done); 76 | }); 77 | 78 | it('object with do not suggest', (done) => { 79 | let content = "doNotSuggestObject:\n "; 80 | let completion = parseSetup(content, content.length); 81 | completion.then(function (result) { 82 | //the user has already created the object, we should still suggest its properties 83 | assert.equal(result.items.length, 5); 84 | }).then(done, done); 85 | }); 86 | 87 | it('property with deprecated message', (done) => { 88 | let content = "objectWithDeprecatedString:\n "; 89 | let completion = parseSetup(content, content.length); 90 | completion.then(function (result) { 91 | assert.equal(result.items.length, 4); 92 | }).then(done, done); 93 | }); 94 | 95 | it('property with do not suggest', (done) => { 96 | let content = "objectWithDeprecatedSubObject:\n "; 97 | let completion = parseSetup(content, content.length); 98 | completion.then(function (result) { 99 | assert.equal(result.items.length, 4); 100 | }).then(done, done); 101 | }); 102 | 103 | it('object with more than one deprecated property', (done) => { 104 | let content = "objectWithTwoDeprecatedProperties:\n "; 105 | let completion = parseSetup(content, content.length); 106 | completion.then(function (result) { 107 | assert.equal(result.items.length, 3); 108 | }).then(done, done); 109 | }); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /language-server/test/documentSymbols.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import {getLanguageService} from 'azure-pipelines-language-service' 7 | import {schemaRequestService, workspaceContext} from './testHelper'; 8 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 9 | var assert = require('assert'); 10 | 11 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 12 | 13 | describe("Document Symbols Tests", () => { 14 | 15 | describe('Document Symbols Tests', function(){ 16 | 17 | function setup(content: string){ 18 | return TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 19 | } 20 | 21 | function parseSetup(content: string){ 22 | let testTextDocument = setup(content); 23 | let jsonDocument = parseYAML(testTextDocument.getText()); 24 | return languageService.findDocumentSymbols(testTextDocument, jsonDocument); 25 | } 26 | 27 | it('Document is empty', (done) => { 28 | let content = ""; 29 | let symbols = parseSetup(content); 30 | assert.equal(symbols, null); 31 | done(); 32 | }) 33 | 34 | it('Simple document symbols', (done) => { 35 | let content = "cwd: test"; 36 | let symbols = parseSetup(content); 37 | assert.equal(symbols.length, 1); 38 | done(); 39 | }); 40 | 41 | it('Document Symbols with number', (done) => { 42 | let content = "node1: 10000"; 43 | let symbols = parseSetup(content); 44 | assert.equal(symbols.length, 1); 45 | done(); 46 | }); 47 | 48 | it('Document Symbols with boolean', (done) => { 49 | let content = "node1: False"; 50 | let symbols = parseSetup(content); 51 | assert.equal(symbols.length, 1); 52 | done(); 53 | }); 54 | 55 | it('Document Symbols with object', (done) => { 56 | let content = "scripts:\n node1: test\n node2: test"; 57 | let symbols = parseSetup(content); 58 | assert.equal(symbols.length, 3); 59 | done(); 60 | }); 61 | 62 | it('Document Symbols with null', (done) => { 63 | let content = "apiVersion: null"; 64 | let symbols = parseSetup(content); 65 | assert.equal(symbols.length, 1); 66 | done(); 67 | }); 68 | 69 | it('Document Symbols with array of strings', (done) => { 70 | let content = "items:\n - test\n - test"; 71 | let symbols = parseSetup(content); 72 | assert.equal(symbols.length, 1); 73 | done(); 74 | }); 75 | 76 | it('Document Symbols with array', (done) => { 77 | let content = "authors:\n - name: Josh\n - email: jp"; 78 | let symbols = parseSetup(content); 79 | assert.equal(symbols.length, 3); 80 | done(); 81 | }); 82 | 83 | it('Document Symbols with object and array', (done) => { 84 | let content = "scripts:\n node1: test\n node2: test\nauthors:\n - name: Josh\n - email: jp"; 85 | let symbols = parseSetup(content); 86 | assert.equal(symbols.length, 6); 87 | done(); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /language-server/test/formatter.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import { getLanguageService } from 'azure-pipelines-language-service' 7 | import { schemaRequestService, workspaceContext } from './testHelper'; 8 | var assert = require('assert'); 9 | 10 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 11 | 12 | 13 | let uri = 'http://json.schemastore.org/bowerrc'; 14 | let languageSettings = { 15 | schemas: [], 16 | validate: true, 17 | customTags: [] 18 | }; 19 | let fileMatch = ["*.yml", "*.yaml"]; 20 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 21 | languageSettings.customTags.push("!Test"); 22 | languageService.configure(languageSettings); 23 | 24 | // Defines a Mocha test suite to group tests of similar kind together 25 | describe("Formatter Tests", () => { 26 | 27 | // Tests for validator 28 | describe('Formatter', function () { 29 | 30 | function setup(content: string) { 31 | return TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 32 | } 33 | 34 | describe('Test that formatter works with custom tags', function () { 35 | 36 | it('Formatting works without custom tags', () => { 37 | let content = `cwd: test`; 38 | let testTextDocument = setup(content); 39 | let edits = languageService.doFormat(testTextDocument, { 40 | insertSpaces: true, 41 | tabSize: 4 42 | }, languageSettings.customTags); 43 | assert.notEqual(edits.length, 0); 44 | assert.equal(edits[0].newText, "cwd: test\n"); 45 | }); 46 | 47 | it('Formatting works without custom tags', () => { 48 | let content = `cwd: !Test test`; 49 | let testTextDocument = setup(content); 50 | let edits = languageService.doFormat(testTextDocument, { 51 | insertSpaces: true, 52 | tabSize: 4 53 | }, languageSettings.customTags); 54 | assert.notEqual(edits.length, 0); 55 | }); 56 | 57 | }); 58 | 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /language-server/test/hover.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Hover, MarkupContent, MarkedString} from 'vscode-languageserver/node'; 6 | import { TextDocument } from 'vscode-languageserver-textdocument'; 7 | import {getLanguageService} from 'azure-pipelines-language-service' 8 | import {schemaRequestService, workspaceContext} from './testHelper'; 9 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 10 | import { type } from 'os'; 11 | var assert = require('assert'); 12 | 13 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 14 | 15 | 16 | let uri = 'http://json.schemastore.org/bowerrc'; 17 | let languageSettings = { 18 | schemas: [] 19 | }; 20 | let fileMatch = ["*.yml", "*.yaml"]; 21 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 22 | languageService.configure(languageSettings); 23 | 24 | export function assertHasContents(result: Hover): void { 25 | assert.ok(result.contents); 26 | if (typeof(result.contents) == "string") { 27 | assert.notEqual(result.contents.length, 0); 28 | } 29 | else if (result.contents.hasOwnProperty('value')) { 30 | let resultValue: any = result.contents['value']; 31 | assert.ok(typeof(resultValue) == "string"); 32 | assert.notEqual(resultValue.length, 0); 33 | } 34 | else { 35 | let hasAnyContent: boolean = false; 36 | let contentArray: MarkedString[] = (result.contents); 37 | for (let contentLine in contentArray) { 38 | if (typeof(contentLine) == "string") { 39 | hasAnyContent = contentLine.length > 0 40 | } 41 | else { 42 | let contentLineValue: any = contentLine['value']; 43 | assert.ok(typeof(contentLineValue) == "string"); 44 | hasAnyContent = contentLineValue.length > 0; 45 | } 46 | } 47 | 48 | assert.ok(hasAnyContent); 49 | } 50 | } 51 | 52 | describe("Hover Tests", () => { 53 | 54 | 55 | describe('Yaml Hover with bowerrc', function(){ 56 | 57 | describe('doComplete', function(){ 58 | 59 | function setup(content: string): TextDocument{ 60 | return TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 61 | } 62 | 63 | function parseSetup(content: string, position): Thenable { 64 | let testTextDocument = setup(content); 65 | let jsonDocument = parseYAML(testTextDocument.getText()); 66 | return languageService.doHover(testTextDocument, testTextDocument.positionAt(position), jsonDocument); 67 | } 68 | 69 | it('Hover on key on root', (done) => { 70 | let content: string = "cwd: test"; 71 | let hover: Thenable = parseSetup(content, 1); 72 | hover.then(function(result: Hover){ 73 | assertHasContents(result); 74 | }).then(done, done); 75 | }); 76 | 77 | it('Hover on value on root', (done) => { 78 | let content: string = "cwd: test"; 79 | let hover: Thenable = parseSetup(content, 6); 80 | hover.then(function(result: Hover){ 81 | assertHasContents(result); 82 | }).then(done, done); 83 | }); 84 | 85 | it('Hover on key with depth', (done) => { 86 | let content: string = "scripts:\n postinstall: test"; 87 | let hover: Thenable = parseSetup(content, 15); 88 | hover.then(function(result: Hover){ 89 | assertHasContents(result); 90 | }).then(done, done); 91 | }); 92 | 93 | it('Hover on value with depth', (done) => { 94 | let content: string = "scripts:\n postinstall: test"; 95 | let hover: Thenable = parseSetup(content, 26); 96 | hover.then(function(result: Hover){ 97 | assertHasContents(result); 98 | }).then(done, done); 99 | }); 100 | 101 | it('Hover works on both root node and child nodes works', (done) => { 102 | let content: string = "scripts:\n postinstall: test"; 103 | 104 | let firstHover: Thenable = parseSetup(content, 3); 105 | firstHover.then(function(result: Hover){ 106 | assertHasContents(result); 107 | }); 108 | 109 | let secondHover: Thenable = parseSetup(content, 15); 110 | secondHover.then(function(result: Hover){ 111 | assertHasContents(result); 112 | }).then(done, done); 113 | }); 114 | 115 | it('Hover does not show results when there isnt description field', (done) => { 116 | let content: string = "analytics: true"; 117 | let hover: Thenable = parseSetup(content, 3); 118 | hover.then(function(result: Hover){ 119 | assertHasContents(result); 120 | }).then(done, done); 121 | }); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /language-server/test/hover2.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import {getLanguageService} from 'azure-pipelines-language-service' 7 | import {schemaRequestService, workspaceContext} from './testHelper'; 8 | import {assertHasContents} from './hover.test' 9 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 10 | var assert = require('assert'); 11 | 12 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 13 | 14 | 15 | let uri = 'http://json.schemastore.org/composer'; 16 | let languageSettings = { 17 | schemas: [] 18 | }; 19 | let fileMatch = ["*.yml", "*.yaml"]; 20 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 21 | languageService.configure(languageSettings); 22 | 23 | describe("Hover Tests", () => { 24 | 25 | 26 | describe('Yaml Hover with composer schema', function(){ 27 | 28 | describe('doComplete', function(){ 29 | 30 | function setup(content: string){ 31 | return TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 32 | } 33 | 34 | function parseSetup(content: string, position){ 35 | let testTextDocument = setup(content); 36 | let jsonDocument = parseYAML(testTextDocument.getText()); 37 | return languageService.doHover(testTextDocument, testTextDocument.positionAt(position), jsonDocument); 38 | } 39 | 40 | it('Hover works on array nodes', (done) => { 41 | let content = "authors:\n - name: Josh"; 42 | let hover = parseSetup(content, 14); 43 | hover.then(function(result){ 44 | assertHasContents(result); 45 | }).then(done, done); 46 | }); 47 | 48 | it('Hover works on array nodes 2', (done) => { 49 | let content = "authors:\n - name: Josh\n - email: jp"; 50 | let hover = parseSetup(content, 28); 51 | hover.then(function(result){ 52 | assertHasContents(result); 53 | }).then(done, done); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /language-server/test/schemaNotFound.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import {getLanguageService} from 'azure-pipelines-language-service' 7 | import {schemaRequestService, workspaceContext} from './testHelper'; 8 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 9 | var assert = require('assert'); 10 | 11 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 12 | 13 | let uri = 'SchemaDoesNotExist'; 14 | let languageSettings = { 15 | schemas: [], 16 | validate: true, 17 | customTags: [] 18 | }; 19 | let fileMatch = ["*.yml", "*.yaml"]; 20 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 21 | languageService.configure(languageSettings); 22 | 23 | // Defines a Mocha test suite to group tests of similar kind together 24 | describe("Validation Tests", () => { 25 | 26 | // Tests for validator 27 | describe('Validation', function() { 28 | 29 | function setup(content: string){ 30 | return TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 31 | } 32 | 33 | function parseSetup(content: string){ 34 | let testTextDocument = setup(content); 35 | let yDoc = parseYAML(testTextDocument.getText(), languageSettings.customTags); 36 | return languageService.doValidation(testTextDocument, yDoc); 37 | } 38 | 39 | //Validating basic nodes 40 | describe('Test that validation throws error when schema is not found', function(){ 41 | 42 | it('Basic test', (done) => { 43 | let content = `testing: true`; 44 | let validator = parseSetup(content); 45 | validator.then(function(result){ 46 | assert.notEqual(result.length, 0); 47 | }).then(done, done); 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /language-server/test/schemaValidation2.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "name": "ValidationTest", 4 | "type": "object", 5 | "oneOf": [ 6 | { 7 | "type": "object", 8 | "firstProperty": ["name"], 9 | "required": ["action"], 10 | "properties": { 11 | "name": { "type": "string", "pattern": "^type1$" }, 12 | "action": {"enum": [ 13 | "type1-value1", 14 | "type1-value2", 15 | "type1-value3" 16 | ], 17 | "ignoreCase": "all" 18 | } 19 | } 20 | }, 21 | { 22 | "type": "object", 23 | "firstProperty": ["name"], 24 | "required": ["action"], 25 | "properties": { 26 | "name": { "type": "string", "pattern": "^type2$" }, 27 | "action": {"enum": [ 28 | "type2-value1", 29 | "type2-value2", 30 | "type2-value3" 31 | ], 32 | "ignoreCase": "value" 33 | } 34 | } 35 | }, 36 | { 37 | "type": "object", 38 | "firstProperty": ["name"], 39 | "required": ["action"], 40 | "properties": { 41 | "name": { "type": "string", "pattern": "^type3$" }, 42 | "action": {"enum": [ 43 | "type3-value1", 44 | "type3-value2", 45 | "type3-value3" 46 | ], 47 | "ignoreCase": "key" 48 | } 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /language-server/test/schemaValidation2.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { TextDocument } from 'vscode-languageserver-textdocument'; 6 | import { LanguageService, getLanguageService, YAMLDocument, Diagnostic } from 'azure-pipelines-language-service' 7 | import { schemaRequestService, workspaceContext } from './testHelper'; 8 | import { parse as parseYAML } from 'azure-pipelines-language-service'; 9 | import { completionHelper } from 'azure-pipelines-language-service'; 10 | var assert = require('assert'); 11 | 12 | let languageService = getLanguageService(schemaRequestService, [], null, workspaceContext); 13 | 14 | let dirName = __dirname.replace("\\", "/"); 15 | if (!dirName.startsWith("/")) { 16 | dirName = "/" + dirName; 17 | } 18 | let uri = 'file://' + dirName + '/schemaValidation2.schema.json'; 19 | let languageSettings = { 20 | schemas: [], 21 | validate: true, 22 | customTags: [] 23 | }; 24 | let fileMatch = ["*.yml", "*.yaml"]; 25 | languageSettings.schemas.push({ uri, fileMatch: fileMatch }); 26 | languageService.configure(languageSettings); 27 | 28 | describe("Non-standard validation Tests", () => { 29 | 30 | function parseSetup(content: string): Thenable { 31 | let document: TextDocument = TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); 32 | let yamlDocument: YAMLDocument = parseYAML(document.getText()); 33 | return languageService.doValidation(document, yamlDocument); 34 | } 35 | 36 | describe('validate matching JSON', function(){ 37 | 38 | it('test with firstProperty', (done) => { 39 | const content: string = `name: type1\naction: type1-value1`; 40 | const validator = parseSetup(content); 41 | validator.then(function(result){ 42 | assert.equal(result.length, 0); 43 | }).then(done, done); 44 | }); 45 | 46 | it('test with different value case', (done) => { 47 | const content: string = `name: type2\naction: Type2-Value1`; 48 | const validator = parseSetup(content); 49 | validator.then(function(result){ 50 | assert.equal(result.length, 0); 51 | }).then(done, done); 52 | }); 53 | 54 | it('test with different key case', (done) => { 55 | const content: string = `name: type3\nAction: type3-value1`; 56 | const validator = parseSetup(content); 57 | validator.then(function(result){ 58 | assert.equal(result.length, 0); 59 | }).then(done, done); 60 | }); 61 | }); 62 | 63 | describe('validate failing JSON', function(){ 64 | 65 | it('test with wrong property order', (done) => { 66 | const content: string = `action: type1-value1\nname: type1`; 67 | const validator = parseSetup(content); 68 | validator.then(function(result){ 69 | assert.equal(result.length, 1); 70 | assert.equal(result[0].message, "The first property must be name"); 71 | }).then(done, done); 72 | }); 73 | 74 | it('test with duplicate keys (same case)', (done) => { 75 | const content: string = `name: type1\naction: type1-value1\naction: type1-value2`; 76 | const validator = parseSetup(content); 77 | validator.then(function(result){ 78 | assert.equal(result.length, 2); 79 | assert.equal(result[0].message, "duplicate key"); 80 | assert.equal(result[1].message, "duplicate key"); 81 | }).then(done, done); 82 | }); 83 | 84 | it('test with duplicate keys (different case)', (done) => { 85 | const content: string = `name: type1\naction: type1-value1\nAction: type1-value2`; 86 | const validator = parseSetup(content); 87 | validator.then(function(result){ 88 | assert.equal(result.length, 2); 89 | assert.equal(result[0].message, "Multiple properties found matching action"); 90 | assert.equal(result[1].message, "Multiple properties found matching action"); 91 | }).then(done, done); 92 | }); 93 | 94 | it('test with invalid key case', (done) => { 95 | const content: string = `name: type2\nAction: type2-value2`; 96 | const validator = parseSetup(content); 97 | validator.then(function(result){ 98 | assert.equal(result.length, 1); 99 | assert.equal(result[0].message, "Missing property \"action\"."); 100 | }).then(done, done); 101 | }); 102 | 103 | it('test with invalid value case', (done) => { 104 | const content: string = `name: type3\naction: Type3-vAlue3`; 105 | const validator = parseSetup(content); 106 | validator.then(function(result){ 107 | assert.equal(result.length, 1); 108 | assert.equal(result[0].message, "Value is not accepted. Valid values: \"type3-value1\", \"type3-value2\", \"type3-value3\"."); 109 | }).then(done, done); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /language-server/test/testHelper.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { 4 | IPCMessageReader, IPCMessageWriter, 5 | createConnection, Connection, TextDocumentSyncKind, 6 | InitializeResult, RequestType 7 | } from 'vscode-languageserver/node'; 8 | import { xhr, XHRResponse, getErrorStatusDescription } from 'request-light'; 9 | import Strings = require( 'azure-pipelines-language-service'); 10 | import { URI } from 'vscode-uri'; 11 | import * as URL from 'url'; 12 | import * as fs from 'fs/promises'; 13 | import { JSONSchema } from "azure-pipelines-language-service"; 14 | import { ParseSchema } from "azure-pipelines-language-service"; 15 | 16 | namespace VSCodeContentRequest { 17 | export const type: RequestType<{}, {}, {}> = new RequestType('vscode/content'); 18 | } 19 | 20 | // Create a connection for the server. 21 | let connection: Connection = null; 22 | if (process.argv.indexOf('--stdio') == -1) { 23 | connection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process)); 24 | } else { 25 | connection = createConnection(); 26 | } 27 | 28 | // After the server has started the client sends an initialize request. The server receives 29 | // in the passed params the rootPath of the workspace plus the client capabilities. 30 | let workspaceRoot: string; 31 | connection.onInitialize((params): InitializeResult => { 32 | workspaceRoot = params.rootPath; 33 | return { 34 | capabilities: { 35 | // Tell the client that the server works in FULL text document sync mode 36 | textDocumentSync: TextDocumentSyncKind.Full, 37 | // Tell the client that the server support code complete 38 | completionProvider: { 39 | resolveProvider: false 40 | } 41 | } 42 | } 43 | }); 44 | 45 | export let workspaceContext = { 46 | resolveRelativePath: (relativePath: string, resource: string) => { 47 | return URL.resolve(resource, relativePath); 48 | } 49 | }; 50 | 51 | export const schemaRequestService = async (uri: string): Promise => { 52 | if (Strings.startsWith(uri, 'file://')) { 53 | const fsPath = URI.parse(uri).fsPath; 54 | const schema = await fs.readFile(fsPath, 'utf-8'); 55 | return ParseSchema(schema); 56 | } else if (Strings.startsWith(uri, 'vscode://')) { 57 | return connection.sendRequest(VSCodeContentRequest.type, uri).then(responseText => { 58 | return responseText; 59 | }, error => { 60 | return error.message; 61 | }); 62 | } 63 | return xhr({ url: uri, followRedirects: 5 }).then(response => { 64 | return ParseSchema(response.responseText); 65 | }, (error: XHRResponse) => { 66 | return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /language-server/thirdpartynotice.txt: -------------------------------------------------------------------------------- 1 | THIRD PARTY SOFTWARE NOTICES AND INFORMATION 2 | Do Not Translate or Localize 3 | 4 | This software incorporates material from third parties. Microsoft makes certain 5 | open source code available at http://3rdpartysource.microsoft.com, or you may 6 | send a check or money order for US $5.00, including the product name, the open 7 | source component name, and version number, to: 8 | 9 | Source Code Compliance Team 10 | Microsoft Corporation 11 | One Microsoft Way 12 | Redmond, WA 98052 13 | USA 14 | 15 | Notwithstanding any other terms, you may reverse engineer this software to the 16 | extent required to debug changes to any libraries licensed under the GNU Lesser 17 | General Public License. 18 | --- 19 | Component. Red Hat YAML Language Server 20 | 21 | Open Source License/Copyright Notice:. MIT License 22 | 23 | Copyright (c) 2017 Red Hat Inc. and others. 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy 26 | of this software and associated documentation files (the "Software"), to deal 27 | in the Software without restriction, including without limitation the rights 28 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | copies of the Software, and to permit persons to whom the Software is 30 | furnished to do so, subject to the following conditions: 31 | 32 | The above copyright notice and this permission notice shall be included in all 33 | copies or substantial portions of the Software. 34 | 35 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 36 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 37 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 38 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 39 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 40 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 41 | SOFTWARE. 42 | 43 | 44 | Additional Attribution: Gorkem Ercan (Red Hat), Joshua Pinkney (joshpinkney@gmail.com) -------------------------------------------------------------------------------- /language-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "forceConsistentCasingInFileNames": true, 4 | "target": "es6", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "lib" : [ "es2016" ], 9 | "outDir": "./out" 10 | }, 11 | "include": [ 12 | "src" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /language-service/.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | *.tgz 3 | **/test 4 | **/unittests 5 | /src 6 | node_modules 7 | tsconfig.json 8 | webpack.config.js 9 | -------------------------------------------------------------------------------- /language-service/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 0.8.0 2 | - Added "go to definition" support for templates [#PR-157](https://github.com/microsoft/azure-pipelines-language-server/pull/157) - thanks @Stuart-Wilcox! 3 | - Added a standalone `azure-pipelines-language-server` executable [#PR-147](https://github.com/microsoft/azure-pipelines-language-server/pull/147) - thanks @williamboman! 4 | - Updated dependencies 5 | 6 | #### 0.7.0 7 | - Added support for using expressions as values [#PR-138](https://github.com/microsoft/azure-pipelines-language-server/pull/138) 8 | - Fixed badly-indented files crashing the language server [#PR-141](https://github.com/microsoft/azure-pipelines-language-server/pull/141) 9 | 10 | #### 0.6.9 11 | - Fixed loops crashing the language server when the first key/value pair had a dynamic expression as the key [#PR-130](https://github.com/microsoft/azure-pipelines-language-server/pull/116) 12 | 13 | #### 0.6.8 14 | - Updated dependencies 15 | 16 | #### 0.6.7 17 | - Support emojis [#PR-115](https://github.com/microsoft/azure-pipelines-language-server/pull/116) - thanks @PaulTaykalo! 18 | 19 | #### 0.6.6 20 | - Fixed property autocomplete adding unnecessary colons [#PR-113](https://github.com/microsoft/azure-pipelines-language-server/pull/113) 21 | - More conditional expression fixes [#PR-114](https://github.com/microsoft/azure-pipelines-language-server/pull/114) 22 | 23 | #### 0.6.5 24 | - Conditional variable fixes - thanks @50Wliu 25 | 26 | #### 0.6.4 27 | - Handle dynamic variables - thanks @50Wliu 28 | 29 | #### 0.6.3 30 | - Add basic support for expressions 31 | 32 | #### 0.6.2 33 | - Dependency updates 34 | 35 | #### 0.6.1 36 | - Webpack 37 | 38 | #### 0.6.0 39 | - Improved debuggability - thanks @50Wliu 40 | - Several security fixes as recommended by dependabot 41 | 42 | #### 0.5.10 43 | Update dependencies for a security issue[#PR-72](https://github.com/microsoft/azure-pipelines-language-server/pull/72) 44 | 45 | #### 0.5.9 46 | Update dependencies for a security issue[#PR-68](https://github.com/microsoft/azure-pipelines-language-server/pull/68) 47 | 48 | #### 0.5.8 49 | Extending language service to make task codelens possible [#PR-66](https://github.com/microsoft/azure-pipelines-language-server/pull/66) 50 | Yaml traversal: export interfaces and return both the start and end positions [#PR-67](https://github.com/microsoft/azure-pipelines-language-server/pull/67/files) 51 | 52 | #### 0.5.7 53 | Allow boolean values to validate against string schema [#PR-62](https://github.com/microsoft/azure-pipelines-language-server/pull/62) 54 | Remove consideration of firstProperty schema element when generating errors [#PR-63](https://github.com/microsoft/azure-pipelines-language-server/pull/63) 55 | Improve positioning of unexpected property errors [#PR-64](https://github.com/microsoft/azure-pipelines-language-server/pull/64) 56 | 57 | #### 0.5.6 58 | Cache schemas when using a custom schema provider to improve performance [#PR-60](https://github.com/Microsoft/azure-pipelines-language-server/pull/60) 59 | update dependencies for security fixes [#PR-61](https://github.com/Microsoft/azure-pipelines-language-server/pull/61) 60 | 61 | #### 0.5.5 62 | Better handling of YAML structure errors [#PR-58](https://github.com/Microsoft/azure-pipelines-language-server/pull/58) 63 | Cache schemas to improve performance [#PR-59](https://github.com/Microsoft/azure-pipelines-language-server/pull/59) 64 | 65 | #### 0.5.4 66 | Change schema service to use a schema object instead of a JSON string [#PR-53](https://github.com/Microsoft/azure-pipelines-language-server/pull/53) 67 | 68 | #### 0.5.3 69 | Improve performance [#PR-51](https://github.com/Microsoft/azure-pipelines-language-server/pull/51) 70 | Improve error messages and suggestions [#PR-52](https://github.com/Microsoft/azure-pipelines-language-server/pull/52) 71 | 72 | #### 0.5.2 73 | Fix a regression with YAML structure errors [#PR-49](https://github.com/Microsoft/azure-pipelines-language-server/pull/49) 74 | Improve performance [#PR-50](https://github.com/Microsoft/azure-pipelines-language-server/pull/50) 75 | 76 | #### 0.5.1 77 | version 0.5.0 was not built correctly 78 | 79 | #### 0.5.0 80 | Add support for property aliases [#PR-33](https://github.com/Microsoft/azure-pipelines-language-server/pull/33) 81 | Improve auto-complete suggestions [#PR-44](https://github.com/Microsoft/azure-pipelines-language-server/pull/44) 82 | Reject multi-document files [#PR-46](https://github.com/Microsoft/azure-pipelines-language-server/pull/46) 83 | 84 | #### 0.4.1 85 | Fix bug where enums that looked like numbers would be marked invalid [#PR-29](https://github.com/Microsoft/azure-pipelines-language-server/pull/29) 86 | do not suggest case insensitive properties when a matching property is present [#PR-28](https://github.com/Microsoft/azure-pipelines-language-server/pull/28) 87 | Allow empty strings to be validated [#PR-30](https://github.com/Microsoft/azure-pipelines-language-server/pull/30) 88 | Fix issue with trailing space when auto-completing [#PR-32](https://github.com/Microsoft/azure-pipelines-language-server/pull/32) 89 | 90 | #### 0.4.0 91 | introduce the "ignoreCase" schema option that can be used to turn off case sensitivity for property keys and/or values 92 | [#PR-22](https://github.com/Microsoft/azure-pipelines-language-server/pull/22) 93 | [#PR-23](https://github.com/Microsoft/azure-pipelines-language-server/pull/23) 94 | use "firstProperty" to improve validation errors and auto-complete suggestions [#PR-26](https://github.com/Microsoft/azure-pipelines-language-server/pull/26) 95 | Always add colon to the completion text for properties [#PR-25](https://github.com/Microsoft/azure-pipelines-language-server/pull/25) 96 | 97 | #### 0.3.0 98 | introduce the "firstProperty" schema option that indicates which property must be listed first in the object [#PR-19](https://github.com/Microsoft/azure-pipelines-language-server/pull/19) 99 | 100 | #### 0.2.3 101 | Fix data returned by findDocumentSymbols [#PR-14](https://github.com/Microsoft/azure-pipelines-language-server/pull/14) 102 | Update to coveralls@3.0.2 to get rid of [cryptiles vulenerability](https://github.com/hapijs/cryptiles/issues/34) 103 | Fix the completion suggestions on file with LF line endings [#13](https://github.com/Microsoft/azure-pipelines-language-server/issues/13) 104 | 105 | #### 0.2.2 106 | Fix the completion suggestions on empty line bug [#PR-12](https://github.com/Microsoft/azure-pipelines-language-server/pull/12) 107 | 108 | #### 0.2.1 109 | Fixes to use consistent types in language-service, added webpack to generate UMD bundle [#PR-9](https://github.com/Microsoft/azure-pipelines-language-server/pull/9) 110 | 111 | #### 0.2.0 112 | azure-pipelines-language-service was split from azure-pipelines-language-server to promote reusability [#PR-4](https://github.com/Microsoft/azure-pipelines-language-server/pull/4) 113 | -------------------------------------------------------------------------------- /language-service/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation and others. All rights reserved. 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 | -------------------------------------------------------------------------------- /language-service/README.md: -------------------------------------------------------------------------------- 1 | ## Features 2 | 3 | 1. YAML validation: 4 | * Detects whether the entire file is valid yaml 5 | 2. Validation: 6 | * Detects errors such as: 7 | * Node is not found 8 | * Node has an invalid key node type 9 | * Node has an invalid type 10 | * Node is not a valid child node 11 | * Detects warnings such as: 12 | * Node is an additional property of parent 13 | 3. Auto completion: 14 | * Auto completes on all commands 15 | * Scalar nodes autocomplete to schema's defaults if they exist 16 | 4. Hover support: 17 | * Hovering over a node shows description *if available* 18 | 5. Document outlining: 19 | * Shows a complete document outline of all nodes in the document 20 | 21 | ## Language Server Settings 22 | 23 | The following settings are supported: 24 | 25 | * `yaml.format.enable`: Enable/disable default YAML formatter 26 | * `yaml.validate`: Enable/disable validation feature 27 | * `yaml.schemas`: Helps you associate schemas with files in a glob pattern 28 | * `yaml.customTags`: Array of custom tags that the parser will validate against. It has two ways to be used. Either an item in the array is a custom tag such as "!Ref" or you can specify the type of the object !Ref should be by doing "!Ref scalar". For example: ["!Ref", "!Some-Tag scalar"]. The type of object can be one of scalar, sequence, mapping, map. 29 | 30 | ##### Associating a schema to a glob pattern via yaml.schemas: 31 | 32 | When associating a schema it should follow the format below 33 | ``` 34 | yaml.schemas: { 35 | "url": "globPattern", 36 | "Kubernetes": "globPattern", 37 | "kedge": "globPattern" 38 | } 39 | ``` 40 | 41 | e.g. 42 | ``` 43 | yaml.schemas: { 44 | "http://json.schemastore.org/composer": "/*" 45 | } 46 | ``` 47 | 48 | e.g. 49 | 50 | ``` 51 | yaml.schemas: { 52 | "kubernetes": "/myYamlFile.yaml" 53 | } 54 | ``` 55 | e.g. 56 | ``` 57 | yaml.schemas: { 58 | "kedge": "/myKedgeApp.yaml" 59 | } 60 | ``` 61 | 62 | e.g. 63 | ``` 64 | yaml.schemas: { 65 | "http://json.schemastore.org/composer": "/*", 66 | "kubernetes": "/myYamlFile.yaml" 67 | } 68 | ``` 69 | 70 | `yaml.schemas` extension allows you to specify json schemas that you want to validate against the yaml that you write. Kubernetes and kedge are optional fields. They do not require a url as the language server will provide that. You just need the keywords kubernetes/kedge and a glob pattern. 71 | 72 | ## Dependents/Clients 73 | This repository only contains the server implementation. Here are some known clients consuming this server: 74 | 75 | * [azure-pipelines-language-server](https://github.com/Microsoft/azure-pipelines-language-server/tree/main/language-server) 76 | 77 | ## Thanks 78 | 79 | This project was forked from the [YAML Language Server](https://github.com/redhat-developer/yaml-language-server) by Red Hat. -------------------------------------------------------------------------------- /language-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-pipelines-language-service", 3 | "description": "Azure Pipelines language service", 4 | "version": "0.8.0", 5 | "author": "Microsoft", 6 | "license": "MIT", 7 | "main": "./lib/index.js", 8 | "typings": "./lib/index", 9 | "contributors": [ 10 | { 11 | "name": "Stephen Franceschelli", 12 | "email": "Stephen.Franceschelli@microsoft.com" 13 | }, 14 | { 15 | "name": "Matt Cooper", 16 | "email": "vtbassmatt@gmail.com" 17 | }, 18 | { 19 | "name": "Ruslan Semenov", 20 | "email": "ruslan@rsemenov.com" 21 | }, 22 | { 23 | "name": "Winston Liu", 24 | "email": "Wliu1402@gmail.com" 25 | } 26 | ], 27 | "engines": { 28 | "node": "*" 29 | }, 30 | "keywords": [ 31 | "azure-pipelines", 32 | "LSP" 33 | ], 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/microsoft/azure-pipelines-language-server.git" 37 | }, 38 | "dependencies": { 39 | "js-yaml": "3.13.1", 40 | "jsonc-parser": "2.0.2", 41 | "vscode-json-languageservice": "^4.0.2", 42 | "vscode-languageserver-types": "^3.16.0", 43 | "vscode-nls": "^5.0.0", 44 | "vscode-uri": "^3.0.2", 45 | "yaml-ast-parser": "0.0.43" 46 | }, 47 | "devDependencies": { 48 | "@types/mocha": "^8.2.0", 49 | "@types/node": "^14.0.0", 50 | "buffer": "^6.0.3", 51 | "mocha": "^10.1.0", 52 | "mocha-junit-reporter": "^2.0.0", 53 | "nyc": "^15.1.0", 54 | "os-browserify": "^0.3.0", 55 | "path-browserify": "^1.0.1", 56 | "rimraf": "^3.0.2", 57 | "source-map-support": "^0.5.19", 58 | "ts-loader": "^8.0.17", 59 | "ts-node": "^9.1.1", 60 | "typescript": "~4.4.4", 61 | "vscode-languageserver-textdocument": "^1.0.1", 62 | "webpack": "^5.21.2", 63 | "webpack-cli": "^4.5.0" 64 | }, 65 | "scripts": { 66 | "build": "npm run clean && npm run compile && webpack --mode production && npm pack", 67 | "compile": "tsc -p .", 68 | "watch": "tsc -watch -p .", 69 | "clean": "rimraf lib _bundles", 70 | "test": "mocha --require ts-node/register --ui bdd ./test/**/*.test.ts", 71 | "coverage": "nyc mocha --require ts-node/register --require source-map-support/register --recursive --ui bdd ./test/**/*.test.ts" 72 | }, 73 | "nyc": { 74 | "extension": [ 75 | ".ts", 76 | ".tsx" 77 | ], 78 | "exclude": [ 79 | "**/*.d.ts", 80 | "test/", 81 | "out" 82 | ], 83 | "all": true 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /language-service/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jsonSchema"; 2 | export * from "./parser/jsonParser"; 3 | export * from "./parser/yamlParser"; 4 | export * from "./services/jsonSchemaService"; 5 | export * from "./utils/arrUtils"; 6 | export * from "./utils/strings"; 7 | export * from "./utils/yamlServiceUtils"; 8 | export * from "./yamlLanguageService"; 9 | export * from "./services/yamlTraversal"; 10 | export * from "vscode-languageserver-types"; -------------------------------------------------------------------------------- /language-service/src/jsonContributions.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | import {Thenable, MarkedString, CompletionItem} from 'vscode-json-languageservice'; 9 | 10 | export interface JSONWorkerContribution { 11 | getInfoContribution(uri: string, location: JSONPath): Thenable; 12 | collectPropertyCompletions(uri: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Thenable; 13 | collectValueCompletions(uri: string, location: JSONPath, propertyKey: string, result: CompletionsCollector): Thenable; 14 | collectDefaultCompletions(uri: string, result: CompletionsCollector): Thenable; 15 | resolveCompletion?(item: CompletionItem): Thenable; 16 | } 17 | export type Segment = string | number; 18 | export type JSONPath = Segment[]; 19 | 20 | export interface CompletionsCollector { 21 | add(suggestion: CompletionItem): void; 22 | error(message: string): void; 23 | log(message: string): void; 24 | setAsIncomplete(): void; 25 | getNumberOfProposals(): number; 26 | } -------------------------------------------------------------------------------- /language-service/src/jsonSchema.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | export interface JSONSchema { 9 | id?: string; 10 | $schema?: string; 11 | type?: string | string[]; 12 | title?: string; 13 | default?: any; 14 | definitions?: JSONSchemaMap; 15 | description?: string; 16 | properties?: JSONSchemaMap; 17 | patternProperties?: JSONSchemaMap; 18 | additionalProperties?: any; 19 | minProperties?: number; 20 | maxProperties?: number; 21 | dependencies?: JSONSchemaMap | string[]; 22 | items?: any; 23 | minItems?: number; 24 | maxItems?: number; 25 | uniqueItems?: boolean; 26 | additionalItems?: boolean; 27 | pattern?: string; 28 | minLength?: number; 29 | maxLength?: number; 30 | minimum?: number; 31 | maximum?: number; 32 | exclusiveMinimum?: boolean; 33 | exclusiveMaximum?: boolean; 34 | multipleOf?: number; 35 | required?: string[]; 36 | firstProperty?: string[]; // VSCode extension 37 | $ref?: string; 38 | anyOf?: JSONSchema[]; 39 | allOf?: JSONSchema[]; 40 | oneOf?: JSONSchema[]; 41 | not?: JSONSchema; 42 | enum?: any[]; 43 | format?: string; 44 | errorMessage?: string; // VSCode extension 45 | patternErrorMessage?: string; // VSCode extension 46 | deprecationMessage?: string; // VSCode extension 47 | doNotSuggest?: boolean; // VSCode extension 48 | enumDescriptions?: string[]; // VSCode extension 49 | ignoreCase?: string; // VSCode extension 50 | aliases?: string[]; // VSCode extension 51 | "x-kubernetes-group-version-kind"?; //Kubernetes extension 52 | } 53 | 54 | export interface JSONSchemaMap { 55 | [name: string]:JSONSchema; 56 | } 57 | -------------------------------------------------------------------------------- /language-service/src/services/documentSymbols.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | import * as Parser from "../parser/jsonParser"; 9 | import { YAMLDocument } from "../parser/yamlParser"; 10 | 11 | import { SymbolInformation, SymbolKind, TextDocument, Range, Location } from 'vscode-languageserver-types'; 12 | 13 | export class YAMLDocumentSymbols { 14 | 15 | public findDocumentSymbols(document: TextDocument, doc: YAMLDocument): SymbolInformation[] { 16 | 17 | if (!doc || !doc.documents || doc.documents.length === 0) { 18 | return null; 19 | } 20 | 21 | const collectOutlineEntries = (result: SymbolInformation[], node: Parser.ASTNode, containerName: string): SymbolInformation[] => { 22 | if (node.type === 'array') { 23 | (node).items.forEach((node: Parser.ASTNode) => { 24 | collectOutlineEntries(result, node, containerName); 25 | }); 26 | } else if (node.type === 'object') { 27 | const objectNode = node; 28 | 29 | objectNode.properties.forEach((property: Parser.PropertyASTNode) => { 30 | const location = Location.create(document.uri, Range.create(document.positionAt(property.start), document.positionAt(property.end))); 31 | const valueNode = property.value; 32 | if (valueNode) { 33 | const childContainerName = containerName ? containerName + '.' + property.key.value : property.key.value; 34 | result.push({ name: property.key.getValue(), kind: this.getSymbolKind(valueNode.type), location: location, containerName: containerName }); 35 | collectOutlineEntries(result, valueNode, childContainerName); 36 | } 37 | }); 38 | } 39 | return result; 40 | }; 41 | 42 | const results: SymbolInformation[] = []; 43 | 44 | doc.documents.forEach((yamlDocument: Parser.JSONDocument) => { 45 | if (yamlDocument.root) { 46 | const result = collectOutlineEntries([], yamlDocument.root, void 0); 47 | results.push(...result); 48 | } 49 | }); 50 | 51 | return results; 52 | } 53 | 54 | private getSymbolKind(nodeType: string): SymbolKind { 55 | switch (nodeType) { 56 | case 'object': 57 | return SymbolKind.Module; 58 | case 'string': 59 | return SymbolKind.String; 60 | case 'number': 61 | return SymbolKind.Number; 62 | case 'array': 63 | return SymbolKind.Array; 64 | case 'boolean': 65 | return SymbolKind.Boolean; 66 | default: // 'null' 67 | return SymbolKind.Variable; 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /language-service/src/services/yamlDefinition.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | import * as path from "path"; 9 | import { PromiseConstructor, Thenable } from "vscode-json-languageservice"; 10 | import { TextDocument, Position, Definition, Location, Range } from "vscode-languageserver-types"; 11 | import { URI, Utils } from "vscode-uri"; 12 | 13 | import { StringASTNode } from "../parser/jsonParser"; 14 | import { YAMLDocument } from "../parser/yamlParser"; 15 | 16 | export class YAMLDefinition { 17 | 18 | private promise: PromiseConstructor; 19 | 20 | constructor(promiseConstructor: PromiseConstructor) { 21 | this.promise = promiseConstructor || Promise; 22 | } 23 | 24 | public doDefinition(document: TextDocument, position: Position, yamlDocument: YAMLDocument, workspaceRoot: URI | undefined): Thenable { 25 | const offset = document.offsetAt(position); 26 | 27 | const jsonDocument = yamlDocument.documents.length > 0 ? yamlDocument.documents[0] : null; 28 | if(jsonDocument === null){ 29 | return this.promise.resolve(void 0); 30 | } 31 | 32 | const node = jsonDocument.getNodeFromOffset(offset); 33 | 34 | // can only jump to definition for template declaration, which means: 35 | // * we must be on a string node that is acting as a value (vs a key) 36 | // * the key (location) must be "template" 37 | // 38 | // In other words... 39 | // - template: my_cool_template.yml 40 | // ^^^^^^^^^^^^^^^^^^^^ this part 41 | if (!(node instanceof StringASTNode) || node.location !== 'template' || node.isKey) { 42 | return this.promise.resolve(void 0); 43 | } 44 | 45 | let [location, resource] = node 46 | .value 47 | .split('@'); 48 | 49 | // cannot jump to external resources 50 | if (resource && resource !== 'self') { 51 | return this.promise.resolve(void 0); 52 | } 53 | 54 | // Azure Pipelines accepts both forward and back slashes as path separators, 55 | // even when running on non-Windows. 56 | // To make things easier, normalize all path separators into this platform's path separator. 57 | // That way, vscode-uri will operate on the separators as expected. 58 | location = location 59 | .replaceAll(path.posix.sep, path.sep) 60 | .replaceAll(path.win32.sep, path.sep); 61 | 62 | // determine if abs path (from root) or relative path 63 | // NOTE: Location.create takes in a string, even though the parameter is called 'uri'. 64 | // So create an actual URI, then .toString() it and skip the unnecessary encoding. 65 | let definitionUri = ''; 66 | if (location.startsWith(path.sep)) { 67 | if (workspaceRoot !== undefined) { 68 | // Substring to strip the leading separator. 69 | definitionUri = Utils.joinPath(workspaceRoot, location.substring(1)).toString(true); 70 | } else { 71 | // Can't form an absolute path without a workspace root. 72 | return this.promise.resolve(void 0); 73 | } 74 | } 75 | else { 76 | definitionUri = Utils.resolvePath( 77 | Utils.dirname(URI.parse(document.uri, true)), 78 | location 79 | ).toString(true); 80 | } 81 | 82 | const definition = Location.create(definitionUri, Range.create(0, 0, 0, 0)); 83 | 84 | return this.promise.resolve(definition); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /language-service/src/services/yamlFormatter.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Adam Voss. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | import * as jsyaml from 'js-yaml'; 9 | import * as Yaml from 'yaml-ast-parser' 10 | import { EOL } from 'os'; 11 | import { TextDocument, Range, Position, FormattingOptions, TextEdit } from 'vscode-languageserver-types'; 12 | 13 | export function format(document: TextDocument, options: FormattingOptions, customTags: Array): TextEdit[] { 14 | const text = document.getText(); 15 | 16 | let schemaWithAdditionalTags = jsyaml.Schema.create(customTags.map((tag) => { 17 | const typeInfo = tag.split(' '); 18 | return new jsyaml.Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' }); 19 | })); 20 | 21 | //We need compiledTypeMap to be available from schemaWithAdditionalTags before we add the new custom properties 22 | customTags.map((tag) => { 23 | const typeInfo = tag.split(' '); 24 | schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new jsyaml.Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' }); 25 | }); 26 | 27 | let additionalOptions: Yaml.LoadOptions = { 28 | schema: schemaWithAdditionalTags 29 | } 30 | 31 | const documents = [] 32 | jsyaml.loadAll(text, doc => documents.push(doc), additionalOptions) 33 | 34 | const dumpOptions = { indent: options.tabSize, noCompatMode: true }; 35 | 36 | let newText; 37 | if (documents.length == 1) { 38 | const yaml = documents[0] 39 | newText = jsyaml.safeDump(yaml, dumpOptions) 40 | } 41 | else { 42 | const formatted = documents.map(d => jsyaml.safeDump(d, dumpOptions)) 43 | newText = '%YAML 1.2' + EOL + '---' + EOL + formatted.join('...' + EOL + '---' + EOL) + '...' + EOL 44 | } 45 | 46 | return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(text.length)), newText)] 47 | } -------------------------------------------------------------------------------- /language-service/src/services/yamlHover.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | import { JSONWorkerContribution } from "../jsonContributions"; 9 | import * as Parser from "../parser/jsonParser"; 10 | import { YAMLDocument } from "../parser/yamlParser"; 11 | import * as SchemaService from "./jsonSchemaService"; 12 | import { PromiseConstructor, Thenable } from "vscode-json-languageservice"; 13 | import { Hover, TextDocument, Position, Range, MarkedString } from "vscode-languageserver-types"; 14 | import { toMarkdown } from "../utils/strings"; 15 | 16 | export class YAMLHover { 17 | 18 | private schemaService: SchemaService.IJSONSchemaService; 19 | private contributions: JSONWorkerContribution[]; 20 | private promise: PromiseConstructor; 21 | 22 | constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor: PromiseConstructor) { 23 | this.schemaService = schemaService; 24 | this.contributions = contributions; 25 | this.promise = promiseConstructor || Promise; 26 | } 27 | 28 | public doHover(document: TextDocument, position: Position, yamlDocument: YAMLDocument): Thenable { 29 | 30 | if(!document){ 31 | this.promise.resolve(void 0); 32 | } 33 | 34 | const offset: number = document.offsetAt(position); 35 | const jsonDocument = yamlDocument.documents.length > 0 ? yamlDocument.documents[0] : null; 36 | if(jsonDocument === null){ 37 | return this.promise.resolve(void 0); 38 | } 39 | let node = jsonDocument.getNodeFromOffset(offset); 40 | if (!node || (node.type === 'object' || node.type === 'array') && offset > node.start + 1 && offset < node.end - 1) { 41 | return this.promise.resolve(void 0); 42 | } 43 | let hoverRangeNode = node; 44 | 45 | // use the property description when hovering over an object key 46 | if (node.type === 'string') { 47 | let stringNode = node; 48 | if (stringNode.isKey) { 49 | let propertyNode = node.parent; 50 | node = propertyNode.value; 51 | if (!node) { 52 | return this.promise.resolve(void 0); 53 | } 54 | } 55 | } 56 | 57 | let hoverRange = Range.create(document.positionAt(hoverRangeNode.start), document.positionAt(hoverRangeNode.end)); 58 | 59 | var createHover = (contents: MarkedString[]) => { 60 | let result: Hover = { 61 | contents: contents, 62 | range: hoverRange 63 | }; 64 | return result; 65 | }; 66 | 67 | let location = node.getPath(); 68 | for (let i = this.contributions.length - 1; i >= 0; i--) { 69 | let contribution = this.contributions[i]; 70 | let promise = contribution.getInfoContribution(document.uri, location); 71 | if (promise) { 72 | return promise.then(htmlContent => createHover(htmlContent)); 73 | } 74 | } 75 | 76 | return this.schemaService.getSchemaForResource(document.uri).then((schema) => { 77 | if (schema) { 78 | 79 | let matchingSchemas = jsonDocument.getMatchingSchemas(schema.schema, node.start); 80 | 81 | let title: string = null; 82 | let markdownDescription: string = null; 83 | let markdownEnumValueDescription: string = null; 84 | let enumValue: string = null; 85 | let deprecatedDescription: string = null; 86 | matchingSchemas.every((s) => { 87 | if (s.node === node && !s.inverted && s.schema) { 88 | title = title || s.schema.title; 89 | markdownDescription = markdownDescription || s.schema["markdownDescription"] || toMarkdown(s.schema.description); 90 | deprecatedDescription = deprecatedDescription || s.schema["deprecationMessage"]; 91 | if (s.schema.enum) { 92 | let idx = s.schema.enum.indexOf(node.getValue()); 93 | if (s.schema["markdownEnumDescriptions"]) { 94 | markdownEnumValueDescription = s.schema["markdownEnumDescriptions"][idx]; 95 | } else if (s.schema.enumDescriptions) { 96 | markdownEnumValueDescription = toMarkdown(s.schema.enumDescriptions[idx]); 97 | } 98 | if (markdownEnumValueDescription) { 99 | enumValue = s.schema.enum[idx]; 100 | if (typeof enumValue !== 'string') { 101 | enumValue = JSON.stringify(enumValue); 102 | } 103 | } 104 | } 105 | } 106 | return true; 107 | }); 108 | 109 | let result = ''; 110 | if (deprecatedDescription) { 111 | result = toMarkdown(deprecatedDescription); 112 | } 113 | 114 | if (title) { 115 | if (result.length > 0) { 116 | result += "\n\n"; 117 | } 118 | result = toMarkdown(title); 119 | } 120 | 121 | if (markdownDescription) { 122 | if (result.length > 0) { 123 | result += "\n\n"; 124 | } 125 | result += markdownDescription; 126 | } 127 | 128 | if (markdownEnumValueDescription) { 129 | if (result.length > 0) { 130 | result += "\n\n"; 131 | } 132 | result += `\`${toMarkdown(enumValue)}\`: ${markdownEnumValueDescription}`; 133 | } 134 | 135 | return createHover([result]); 136 | } 137 | return void 0; 138 | }); 139 | } 140 | } 141 | 142 | -------------------------------------------------------------------------------- /language-service/src/services/yamlTraversal.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as Parser from "../parser/jsonParser"; 4 | import { YAMLDocument } from "../parser/yamlParser"; 5 | import { PromiseConstructor, Thenable } from "vscode-json-languageservice"; 6 | import { TextDocument, Position } from "vscode-languageserver-types"; 7 | 8 | export interface YamlNodeInfo { 9 | startPosition: Position; 10 | endPosition: Position; 11 | key: string; 12 | value: string; 13 | } 14 | 15 | export interface YamlNodePropertyValues { 16 | values: {[key: string]: string}; 17 | } 18 | 19 | export class YAMLTraversal { 20 | 21 | private promise: PromiseConstructor; 22 | 23 | constructor(promiseConstructor: PromiseConstructor) { 24 | this.promise = promiseConstructor || Promise; 25 | } 26 | 27 | public findNodes(document: TextDocument, yamlDocument: YAMLDocument, key: string): Thenable { 28 | if(!document){ 29 | this.promise.resolve([]); 30 | } 31 | 32 | const jsonDocument = yamlDocument.documents.length > 0 ? yamlDocument.documents[0] : null; 33 | if(jsonDocument === null){ 34 | return this.promise.resolve([]); 35 | } 36 | 37 | let nodes: YamlNodeInfo[] = []; 38 | jsonDocument.visit((node => { 39 | const propertyNode = node as Parser.PropertyASTNode; 40 | if (propertyNode.key && propertyNode.key.value === key) { 41 | nodes.push({ 42 | startPosition: document.positionAt(node.parent.start), 43 | endPosition: document.positionAt(node.parent.end), 44 | key: propertyNode.key.value, 45 | value: propertyNode.value.getValue() 46 | }); 47 | } 48 | return true; 49 | })); 50 | 51 | return this.promise.resolve(nodes); 52 | } 53 | 54 | public getNodePropertyValues(document: TextDocument, yamlDocument: YAMLDocument, position: Position, propertyName: string): YamlNodePropertyValues { 55 | if(!document){ 56 | return { values: null }; 57 | } 58 | 59 | const offset: number = document.offsetAt(position); 60 | const jsonDocument = yamlDocument.documents.length > 0 ? yamlDocument.documents[0] : null; 61 | if(jsonDocument === null){ 62 | return { values: null }; 63 | } 64 | 65 | // get the node by position and then walk up until we find an object node with properties 66 | let node = jsonDocument.getNodeFromOffset(offset); 67 | while (node !== null && !(node instanceof Parser.ObjectASTNode)) { 68 | node = node.parent; 69 | } 70 | 71 | if (!node) { 72 | return { values: null }; 73 | } 74 | 75 | // see if this object has an inputs property 76 | const propertiesArray = (node as Parser.ObjectASTNode).properties.filter(p => p.key.value === propertyName); 77 | if (!propertiesArray || propertiesArray.length !== 1) { 78 | return { values: null }; 79 | } 80 | 81 | // get the values contained within inputs 82 | let valueMap: {[key: string]: string} = {}; 83 | const parameterValueArray = (propertiesArray[0].value as Parser.ObjectASTNode).properties; 84 | parameterValueArray && parameterValueArray.forEach(p => { 85 | valueMap[p.key.value] = p.value.getValue(); 86 | }); 87 | 88 | return { 89 | values: valueMap 90 | }; 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /language-service/src/services/yamlValidation.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 'use strict'; 7 | 8 | import { JSONSchemaService } from './jsonSchemaService'; 9 | import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types'; 10 | import { PromiseConstructor, Thenable, LanguageSettings} from '../yamlLanguageService'; 11 | import { TextDocument, Position } from "vscode-languageserver-types"; 12 | import { YAMLDocument, SingleYAMLDocument } from "../parser/yamlParser"; 13 | import { IProblem, ProblemSeverity } from '../parser/jsonParser'; 14 | 15 | import * as nls from 'vscode-nls'; 16 | const localize = nls.loadMessageBundle(); 17 | 18 | export class YAMLValidation { 19 | 20 | private jsonSchemaService: JSONSchemaService; 21 | private promise: PromiseConstructor; 22 | private validationEnabled: boolean; 23 | 24 | public constructor(jsonSchemaService: JSONSchemaService, promiseConstructor: PromiseConstructor) { 25 | this.jsonSchemaService = jsonSchemaService; 26 | this.promise = promiseConstructor; 27 | this.validationEnabled = true; 28 | } 29 | 30 | public configure(settings: LanguageSettings): void { 31 | if (settings) { 32 | this.validationEnabled = settings.validate; 33 | } 34 | } 35 | 36 | public doValidation(textDocument: TextDocument, yamlDocument: YAMLDocument): Thenable { 37 | 38 | if(!this.validationEnabled){ 39 | return this.promise.resolve([]); 40 | } 41 | 42 | if (yamlDocument.documents.length === 0) { 43 | //this is strange... 44 | return this.promise.resolve([]); 45 | } 46 | 47 | if (yamlDocument.documents.length > 1) { 48 | //The YAML parser is a little over-eager to call things different documents 49 | // see https://github.com/Microsoft/azure-pipelines-vscode/issues/219 50 | //so search for a specific error so that we can offer the user better guidance 51 | for (let document of yamlDocument.documents) { 52 | for (let docError of document.errors) { 53 | if (docError.getMessage().includes("end of the stream or a document separator is expected")) { 54 | const docErrorPosition : Position = textDocument.positionAt(docError.start); 55 | const errorLine: number = (docErrorPosition.line > 0) ? docErrorPosition.line - 1 : docErrorPosition.line; 56 | 57 | return this.promise.resolve([{ 58 | severity: DiagnosticSeverity.Error, 59 | range: { 60 | start: { 61 | line: errorLine, 62 | character: 0 63 | }, 64 | end: { 65 | line: errorLine + 1, 66 | character: 0 67 | } 68 | }, 69 | message: localize('documentFormatError', 'Invalid YAML structure') 70 | }]); 71 | } 72 | } 73 | } 74 | 75 | return this.promise.resolve([{ 76 | severity: DiagnosticSeverity.Error, 77 | range: { 78 | start: { 79 | line: 0, 80 | character: 0 81 | }, 82 | end: textDocument.positionAt(textDocument.getText().length) 83 | }, 84 | message: localize('multiDocumentError', 'Only single-document files are supported') 85 | }]); 86 | } 87 | 88 | const translateSeverity = (problemSeverity: ProblemSeverity): DiagnosticSeverity => { 89 | if (problemSeverity === ProblemSeverity.Error) { 90 | return DiagnosticSeverity.Error; 91 | } 92 | if (problemSeverity == ProblemSeverity.Warning) { 93 | return DiagnosticSeverity.Warning; 94 | } 95 | 96 | return DiagnosticSeverity.Hint; 97 | }; 98 | 99 | return this.jsonSchemaService.getSchemaForResource(textDocument.uri).then(function (schema) { 100 | var diagnostics: Diagnostic[] = []; 101 | 102 | let jsonDocument: SingleYAMLDocument = yamlDocument.documents[0]; 103 | 104 | jsonDocument.errors.forEach(err => { 105 | diagnostics.push({ 106 | severity: DiagnosticSeverity.Error, 107 | range: { 108 | start: textDocument.positionAt(err.start), 109 | end: textDocument.positionAt(err.end) 110 | }, 111 | message: err.getMessage() 112 | }); 113 | }); 114 | 115 | jsonDocument.warnings.forEach(warn => { 116 | diagnostics.push({ 117 | severity: DiagnosticSeverity.Warning, 118 | range: { 119 | start: textDocument.positionAt(warn.start), 120 | end: textDocument.positionAt(warn.end) 121 | }, 122 | message: warn.getMessage() 123 | }); 124 | }); 125 | 126 | if (schema) { 127 | var added: {[key:string]: boolean} = {}; 128 | const problems: IProblem[] = jsonDocument.getValidationProblems(schema.schema); 129 | problems.forEach(function (problem: IProblem, index: number) { 130 | const message: string = problem.getMessage(); 131 | const signature: string = '' + problem.location.start + ' ' + problem.location.end + ' ' + message 132 | if (!added[signature]) { 133 | added[signature] = true; 134 | diagnostics.push({ 135 | severity: translateSeverity(problem.severity), 136 | range: { 137 | start: textDocument.positionAt(problem.location.start), 138 | end: textDocument.positionAt(problem.location.end) 139 | }, 140 | message: message 141 | }) 142 | } 143 | }); 144 | 145 | if (schema.errors.length > 0) { 146 | for(let curDiagnostic of schema.errors){ 147 | diagnostics.push({ 148 | severity: DiagnosticSeverity.Error, 149 | range: { 150 | start: { 151 | line: 0, 152 | character: 0 153 | }, 154 | end: { 155 | line: 0, 156 | character: 1 157 | } 158 | }, 159 | message: curDiagnostic 160 | }); 161 | } 162 | } 163 | } 164 | 165 | return diagnostics; 166 | }); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /language-service/src/utils/arrUtils.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | export function removeDuplicates(arr, prop) { 8 | var new_arr = []; 9 | var lookup = {}; 10 | 11 | for (var i in arr) { 12 | lookup[arr[i][prop]] = arr[i]; 13 | } 14 | 15 | for (i in lookup) { 16 | new_arr.push(lookup[i]); 17 | } 18 | 19 | return new_arr; 20 | } 21 | 22 | export function getLineOffsets(textDocString: String): number[] { 23 | let lineOffsets: number[] = []; 24 | let text: String = textDocString; 25 | let isLineStart: boolean = true; 26 | for (let i = 0; i < text.length; i++) { 27 | if (isLineStart) { 28 | lineOffsets.push(i); 29 | isLineStart = false; 30 | } 31 | let ch: string = text.charAt(i); 32 | isLineStart = (ch === '\r' || ch === '\n'); 33 | if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { 34 | i++; 35 | } 36 | } 37 | if (isLineStart && text.length > 0) { 38 | lineOffsets.push(text.length); 39 | } 40 | 41 | return lineOffsets; 42 | } 43 | 44 | export function removeDuplicatesObj(objArray){ 45 | let nonDuplicateSet = new Set(); 46 | let nonDuplicateArr = []; 47 | for(let obj in objArray){ 48 | 49 | let currObj = objArray[obj]; 50 | let stringifiedObj = JSON.stringify(currObj); 51 | if(!nonDuplicateSet.has(stringifiedObj)){ 52 | nonDuplicateArr.push(currObj); 53 | nonDuplicateSet.add(stringifiedObj); 54 | } 55 | 56 | } 57 | 58 | return nonDuplicateArr; 59 | } 60 | -------------------------------------------------------------------------------- /language-service/src/utils/documentPositionCalculator.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | "use strict" 7 | 8 | export function insertionPointReturnValue(pt: number) { 9 | return ((-pt) - 1) 10 | } 11 | 12 | export function binarySearch(array: number[], sought: number): number { 13 | 14 | let lower = 0 15 | let upper = array.length - 1 16 | 17 | while (lower <= upper) { 18 | let idx = Math.floor((lower + upper) / 2) 19 | const value = array[idx] 20 | 21 | if (value === sought) { 22 | return idx; 23 | } 24 | 25 | if (lower === upper) { 26 | const insertionPoint = (value < sought) ? idx + 1 : idx 27 | return insertionPointReturnValue(insertionPoint) 28 | } 29 | 30 | if (sought > value) { 31 | lower = idx + 1; 32 | } else if (sought < value) { 33 | upper = idx - 1; 34 | } 35 | } 36 | 37 | //This shouldn't happen 38 | return lower; 39 | } 40 | 41 | export function getLineStartPositions(text: string): number[] { 42 | const lineStartPositions: number[] = [0]; 43 | for (var i = 0; i < text.length; i++) { 44 | const c = text[i]; 45 | 46 | if (c === '\r') { 47 | // Check for Windows encoding, otherwise we are old Mac 48 | if (i + 1 < text.length && text[i + 1] == '\n') { 49 | i++; 50 | } 51 | 52 | lineStartPositions.push(i + 1); 53 | } else if (c === '\n'){ 54 | lineStartPositions.push(i + 1); 55 | } 56 | } 57 | 58 | return lineStartPositions; 59 | } 60 | 61 | export interface ILineColumn { 62 | line: number; 63 | column: number; 64 | } 65 | 66 | export function getPosition(pos: number, lineStartPositions: number[]): ILineColumn { 67 | let line = binarySearch(lineStartPositions, pos) 68 | 69 | if (line < 0){ 70 | const insertionPoint = -1 * line - 1; 71 | line = insertionPoint - 1; 72 | } 73 | 74 | return {line, column: pos - lineStartPositions[line]} 75 | } -------------------------------------------------------------------------------- /language-service/src/utils/errorHandler.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export class ErrorHandler { 7 | private errorResultsList; 8 | private textDocument; 9 | 10 | constructor(textDocument){ 11 | this.errorResultsList = []; 12 | this.textDocument = textDocument; 13 | } 14 | 15 | public addErrorResult(errorNode, errorMessage, errorType){ 16 | this.errorResultsList.push({ 17 | severity: errorType, 18 | range: { 19 | start: this.textDocument.positionAt(errorNode.startPosition), 20 | end: this.textDocument.positionAt(errorNode.endPosition) 21 | }, 22 | message: errorMessage 23 | }); 24 | 25 | } 26 | 27 | public getErrorResultsList(){ 28 | return this.errorResultsList; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /language-service/src/utils/objects.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | export function equals(one: any, other: any): boolean { 8 | if (one === other) { 9 | return true; 10 | } 11 | if (one === null || one === undefined || other === null || other === undefined) { 12 | return false; 13 | } 14 | if (typeof one !== typeof other) { 15 | return false; 16 | } 17 | if (typeof one !== 'object') { 18 | return false; 19 | } 20 | if ((Array.isArray(one)) !== (Array.isArray(other))) { 21 | return false; 22 | } 23 | 24 | var i: number, 25 | key: string; 26 | 27 | if (Array.isArray(one)) { 28 | if (one.length !== other.length) { 29 | return false; 30 | } 31 | for (i = 0; i < one.length; i++) { 32 | if (!equals(one[i], other[i])) { 33 | return false; 34 | } 35 | } 36 | } else { 37 | var oneKeys: string[] = []; 38 | 39 | for (key in one) { 40 | oneKeys.push(key); 41 | } 42 | oneKeys.sort(); 43 | var otherKeys: string[] = []; 44 | for (key in other) { 45 | otherKeys.push(key); 46 | } 47 | otherKeys.sort(); 48 | if (!equals(oneKeys, otherKeys)) { 49 | return false; 50 | } 51 | for (i = 0; i < oneKeys.length; i++) { 52 | if (!equals(one[oneKeys[i]], other[oneKeys[i]])) { 53 | return false; 54 | } 55 | } 56 | } 57 | return true; 58 | } -------------------------------------------------------------------------------- /language-service/src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | 7 | export function startsWith(haystack: string, needle: string): boolean { 8 | if (haystack.length < needle.length) { 9 | return false; 10 | } 11 | 12 | for (let i = 0; i < needle.length; i++) { 13 | if (haystack[i] !== needle[i]) { 14 | return false; 15 | } 16 | } 17 | 18 | return true; 19 | } 20 | 21 | /** 22 | * Determines if haystack ends with needle. 23 | */ 24 | export function endsWith(haystack: string, needle: string): boolean { 25 | let diff = haystack.length - needle.length; 26 | if (diff > 0) { 27 | return haystack.lastIndexOf(needle) === diff; 28 | } else if (diff === 0) { 29 | return haystack === needle; 30 | } else { 31 | return false; 32 | } 33 | } 34 | 35 | export function convertSimple2RegExp(pattern: string): RegExp { 36 | var match = pattern.match(new RegExp('^/(.*?)/([gimy]*)$')); 37 | return match ? convertRegexString2RegExp(match[1], match[2]) 38 | : convertGlobalPattern2RegExp(pattern) 39 | } 40 | 41 | function convertGlobalPattern2RegExp(pattern: string): RegExp { 42 | return new RegExp(pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*') + '$'); 43 | } 44 | 45 | function convertRegexString2RegExp(pattern: string, flag: string): RegExp { 46 | return new RegExp(pattern, flag); 47 | } 48 | 49 | export function toMarkdown(plain: string) { 50 | if (plain) { 51 | let res = plain.replace(/([^\n\r])(\r?\n)([^\n\r])/gm, '$1\n\n$3'); // single new lines to \n\n (Markdown paragraph) 52 | return res.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash 53 | } 54 | return void 0; 55 | } -------------------------------------------------------------------------------- /language-service/src/utils/yamlServiceUtils.ts: -------------------------------------------------------------------------------- 1 | import { getLineOffsets } from "./arrUtils"; 2 | import { TextDocument, Position } from "vscode-languageserver-types"; 3 | 4 | export const nodeHolder = "~"; //This won't conflict with any legal Pipelines nodes 5 | const nodeLineEnding = ":\r\n"; 6 | const nodeHolderWithEnding = nodeHolder + nodeLineEnding; 7 | 8 | function is_EOL(c: number) { 9 | return (c === 0x0A/* LF */) || (c === 0x0D/* CR */); 10 | } 11 | 12 | export interface CompletionAdjustment { 13 | newText: string, 14 | newPosition: Position 15 | } 16 | 17 | export function completionHelper(document: TextDocument, textDocumentPosition: Position): CompletionAdjustment { 18 | // Get the string we are looking at via a substring 19 | const lineNumber: number = textDocumentPosition.line; 20 | const lineOffsets: number[] = getLineOffsets(document.getText()); 21 | const start: number = lineOffsets[lineNumber]; // Start of where the autocompletion is happening 22 | let end = 0; // End of where the autocompletion is happening 23 | 24 | if (lineOffsets[lineNumber + 1] !== undefined) { 25 | end = lineOffsets[lineNumber + 1]; 26 | } else { 27 | end = document.getText().length; 28 | } 29 | 30 | while (end - 1 >= start && is_EOL(document.getText().charCodeAt(end - 1))) { 31 | end--; 32 | } 33 | 34 | const textLine = document.getText().substring(start, end); 35 | 36 | // Check if the string we are looking at is a node 37 | if (textLine.indexOf(":") === -1) { 38 | // We need to add the ":" to load the nodes 39 | const documentText = document.getText(); 40 | 41 | let newText = ""; 42 | 43 | // This is for the empty line case 44 | const trimmedText = textLine.trim(); 45 | if (trimmedText.length === 0 || (trimmedText.length === 1 && trimmedText[0] === '-')) { 46 | // Add a temp node that is in the document but we don't use at all. 47 | newText = documentText.substring(0, start + textDocumentPosition.character) + nodeHolderWithEnding + documentText.substr(start + textDocumentPosition.character); 48 | } else { 49 | // Add a colon to the end of the current line so we can validate the node 50 | newText = documentText.substring(0, start + textLine.length) + nodeLineEnding + documentText.substr(lineOffsets[lineNumber + 1] || documentText.length); 51 | } 52 | 53 | return { 54 | newText: newText, 55 | newPosition: textDocumentPosition, 56 | }; 57 | 58 | } else { 59 | // All the nodes are loaded 60 | textDocumentPosition.character = textDocumentPosition.character - 1; 61 | return { 62 | newText: document.getText(), 63 | newPosition: textDocumentPosition, 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /language-service/src/yamlLanguageService.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat, Inc. All rights reserved. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | import { JSONSchemaService, CustomSchemaProvider } from './services/jsonSchemaService' 7 | import { TextDocument, Position, CompletionList, FormattingOptions, Diagnostic, 8 | CompletionItem, TextEdit, Hover, SymbolInformation, Definition 9 | } from 'vscode-languageserver-types'; 10 | import { URI } from "vscode-uri"; 11 | import { JSONSchema } from './jsonSchema'; 12 | import { YAMLDocumentSymbols } from './services/documentSymbols'; 13 | import { YAMLCompletion } from "./services/yamlCompletion"; 14 | import { YAMLHover } from "./services/yamlHover"; 15 | import { YAMLDefinition } from "./services/yamlDefinition"; 16 | import { YAMLValidation } from "./services/yamlValidation"; 17 | import { format } from './services/yamlFormatter'; 18 | import { JSONWorkerContribution } from './jsonContributions'; 19 | import { YAMLDocument } from './parser/yamlParser'; 20 | import { YAMLTraversal, YamlNodeInfo, YamlNodePropertyValues } from './services/yamlTraversal'; 21 | 22 | export interface LanguageSettings { 23 | validate?: boolean; //Setting for whether we want to validate the schema 24 | isKubernetes?: boolean; //If true then its validating against kubernetes 25 | schemas?: any[]; //List of schemas, 26 | customTags?: Array; //Array of Custom Tags 27 | } 28 | 29 | export interface PromiseConstructor { 30 | /** 31 | * Creates a new Promise. 32 | * @param executor A callback used to initialize the promise. This callback is passed two arguments: 33 | * a resolve callback used resolve the promise with a value or the result of another promise, 34 | * and a reject callback used to reject the promise with a provided reason or error. 35 | */ 36 | new (executor: (resolve: (value?: T | Thenable) => void, reject: (reason?: any) => void) => void): Thenable; 37 | 38 | /** 39 | * Creates a Promise that is resolved with an array of results when all of the provided Promises 40 | * resolve, or rejected when any Promise is rejected. 41 | * @param values An array of Promises. 42 | * @returns A new Promise. 43 | */ 44 | all(values: Array>): Thenable; 45 | /** 46 | * Creates a new rejected promise for the provided reason. 47 | * @param reason The reason the promise was rejected. 48 | * @returns A new rejected Promise. 49 | */ 50 | reject(reason: any): Thenable; 51 | 52 | /** 53 | * Creates a new resolved promise for the provided value. 54 | * @param value A promise. 55 | * @returns A promise whose internal state matches the provided promise. 56 | */ 57 | resolve(value: T | Thenable): Thenable; 58 | 59 | } 60 | 61 | export interface Thenable { 62 | /** 63 | * Attaches callbacks for the resolution and/or rejection of the Promise. 64 | * @param onfulfilled The callback to execute when the Promise is resolved. 65 | * @param onrejected The callback to execute when the Promise is rejected. 66 | * @returns A Promise for the completion of which ever callback is executed. 67 | */ 68 | then(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; 69 | then(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; 70 | } 71 | 72 | export interface WorkspaceContextService { 73 | resolveRelativePath(relativePath: string, resource: string): string; 74 | } 75 | /** 76 | * The schema request service is used to fetch schemas. The result should the schema file comment, or, 77 | * in case of an error, a displayable error string 78 | */ 79 | export interface SchemaRequestService { 80 | (uri: string): Thenable; 81 | } 82 | 83 | export interface SchemaConfiguration { 84 | /** 85 | * The URI of the schema, which is also the identifier of the schema. 86 | */ 87 | uri: string; 88 | /** 89 | * A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json' 90 | */ 91 | fileMatch?: string[]; 92 | /** 93 | * The schema for the given URI. 94 | * If no schema is provided, the schema will be fetched with the schema request service (if available). 95 | */ 96 | schema?: JSONSchema; 97 | } 98 | 99 | export interface LanguageService { 100 | configure(settings: LanguageSettings): void; 101 | doComplete(document: TextDocument, position: Position, yamlDocument: YAMLDocument): Thenable; 102 | doValidation(document: TextDocument, yamlDocument: YAMLDocument): Thenable; 103 | doHover(document: TextDocument, position: Position, doc: YAMLDocument): Thenable; 104 | doDefinition(document: TextDocument, position: Position, doc: YAMLDocument, workspaceRoot: URI): Thenable; 105 | findDocumentSymbols(document: TextDocument, doc: YAMLDocument): SymbolInformation[]; 106 | doResolve(completionItem: CompletionItem): Thenable; 107 | resetSchema(uri: string): boolean; 108 | doFormat(document: TextDocument, options: FormattingOptions, customTags: Array): TextEdit[]; 109 | findNodes(document: TextDocument, doc: YAMLDocument, key: string): Thenable; 110 | getNodePropertyValues(document: TextDocument, doc: YAMLDocument, position: Position, propertyName: string): YamlNodePropertyValues; 111 | } 112 | 113 | export function getLanguageService( 114 | schemaRequestService: SchemaRequestService, 115 | contributions: JSONWorkerContribution[], 116 | customSchemaProvider: CustomSchemaProvider, 117 | workspaceContext?: WorkspaceContextService, 118 | promiseConstructor?: PromiseConstructor): LanguageService { 119 | 120 | let promise = promiseConstructor || Promise; 121 | 122 | let schemaService = new JSONSchemaService(schemaRequestService, workspaceContext, customSchemaProvider); 123 | 124 | let completer = new YAMLCompletion(schemaService, contributions, promise); 125 | let hover = new YAMLHover(schemaService, contributions, promise); 126 | let definition = new YAMLDefinition(promise); 127 | let yamlDocumentSymbols = new YAMLDocumentSymbols(); 128 | let yamlValidation = new YAMLValidation(schemaService, promise); 129 | let yamlTraversal = new YAMLTraversal(promise); 130 | 131 | return { 132 | configure: (settings) => { 133 | schemaService.clearExternalSchemas(); 134 | if (settings.schemas) { 135 | settings.schemas.forEach(schema => { 136 | schemaService.registerExternalSchema(schema.uri, schema.fileMatch, schema.schema); 137 | }); 138 | } 139 | yamlValidation.configure(settings); 140 | let customTagsSetting = settings && settings["customTags"] ? settings["customTags"] : []; 141 | completer.configure(customTagsSetting); 142 | }, 143 | doComplete: completer.doComplete.bind(completer), 144 | doResolve: completer.doResolve.bind(completer), 145 | doValidation: yamlValidation.doValidation.bind(yamlValidation), 146 | doHover: hover.doHover.bind(hover), 147 | doDefinition: definition.doDefinition.bind(definition), 148 | findDocumentSymbols: yamlDocumentSymbols.findDocumentSymbols.bind(yamlDocumentSymbols), 149 | resetSchema: (uri: string) => schemaService.onResourceChange(uri), 150 | doFormat: format, 151 | findNodes: yamlTraversal.findNodes.bind(yamlTraversal), 152 | getNodePropertyValues: yamlTraversal.getNodePropertyValues.bind(yamlTraversal) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /language-service/test/arrUtils.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import {removeDuplicates, getLineOffsets, removeDuplicatesObj} from '../src/utils/arrUtils'; 6 | var assert = require('assert'); 7 | 8 | describe("Array Utils Tests", () => { 9 | 10 | describe('Server - Array Utils', function(){ 11 | 12 | describe('removeDuplicates', function(){ 13 | 14 | it('Remove one duplicate with property', () => { 15 | 16 | var obj1 = { 17 | "test_key": "test_value" 18 | } 19 | 20 | var obj2 = { 21 | "test_key": "test_value" 22 | } 23 | 24 | var arr = [obj1, obj2]; 25 | var prop = "test_key"; 26 | 27 | var result = removeDuplicates(arr, prop); 28 | assert.equal(result.length, 1); 29 | 30 | }); 31 | 32 | it('Remove multiple duplicates with property', () => { 33 | var obj1 = { 34 | "test_key": "test_value" 35 | } 36 | 37 | var obj2 = { 38 | "test_key": "test_value" 39 | } 40 | 41 | var obj3 = { 42 | "test_key": "test_value" 43 | } 44 | 45 | var obj4 = { 46 | "another_key_too": "test_value" 47 | } 48 | 49 | var arr = [obj1, obj2, obj3, obj4]; 50 | var prop = "test_key"; 51 | 52 | var result = removeDuplicates(arr, prop); 53 | assert.equal(result.length, 2); 54 | }); 55 | 56 | it('Do NOT remove items without duplication', () => { 57 | 58 | var obj1 = { 59 | "first_key": "test_value" 60 | } 61 | 62 | var obj2 = { 63 | "second_key": "test_value" 64 | } 65 | 66 | var arr = [obj1, obj2]; 67 | var prop = "first_key"; 68 | 69 | var result = removeDuplicates(arr, prop); 70 | assert.equal(result.length, 2); 71 | 72 | }); 73 | 74 | }); 75 | 76 | describe('getLineOffsets', function(){ 77 | 78 | it('No offset', () => { 79 | var offsets = getLineOffsets(""); 80 | assert.equal(offsets.length, 0); 81 | }); 82 | 83 | it('One offset', () => { 84 | var offsets = getLineOffsets("test_offset"); 85 | assert.equal(offsets.length, 1); 86 | assert.equal(offsets[0], 0); 87 | }); 88 | 89 | it('One offset with \\r\\n', () => { 90 | var offsets = getLineOffsets("first_offset\r\n"); 91 | assert.equal(offsets.length, 2); 92 | assert.equal(offsets[0], 0); 93 | }); 94 | 95 | it('Multiple offsets', () => { 96 | var offsets = getLineOffsets("first_offset\n second_offset\n third_offset"); 97 | assert.equal(offsets.length, 3); 98 | assert.equal(offsets[0], 0); 99 | assert.equal(offsets[1], 13); 100 | assert.equal(offsets[2], 29); 101 | }); 102 | 103 | }); 104 | 105 | describe('removeDuplicatesObj', function(){ 106 | 107 | it('Remove one duplicate with property', () => { 108 | 109 | var obj1 = { 110 | "test_key": "test_value" 111 | } 112 | 113 | var obj2 = { 114 | "test_key": "test_value" 115 | } 116 | 117 | var arr = [obj1, obj2]; 118 | var result = removeDuplicatesObj(arr); 119 | assert.equal(result.length, 1); 120 | 121 | }); 122 | 123 | it('Does not remove anything unneccessary', () => { 124 | var obj1 = { 125 | "test_key": "test_value" 126 | } 127 | 128 | var obj2 = { 129 | "other_key": "test_value" 130 | } 131 | 132 | var arr = [obj1, obj2]; 133 | 134 | var result = removeDuplicatesObj(arr); 135 | assert.equal(result.length, 2); 136 | }); 137 | 138 | }); 139 | 140 | }); 141 | 142 | }); 143 | -------------------------------------------------------------------------------- /language-service/test/documentPositionCalculator.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import {binarySearch, getLineStartPositions, getPosition } from '../src/utils/documentPositionCalculator'; 6 | var assert = require('assert'); 7 | 8 | describe("DocumentPositionCalculator Tests", () => { 9 | 10 | describe('binarySearch', function(){ 11 | 12 | it('Binary Search where we are looking for element to the left of center', () => { 13 | 14 | let arr = [1,2,3,4,5,6,7,8,9,10]; 15 | let find = 2; 16 | 17 | var result = binarySearch(arr, find); 18 | assert.equal(result, 1); 19 | 20 | }); 21 | 22 | it('Binary Search where we are looking for element to the right of center', () => { 23 | 24 | let arr = [1,2,3,4,5,6,7,8,9,10]; 25 | let find = 8; 26 | 27 | var result = binarySearch(arr, find); 28 | assert.equal(result, 7); 29 | 30 | }); 31 | 32 | it('Binary Search found at first check', () => { 33 | 34 | let arr = [1,2,3,4,5,6,7,8,9,10]; 35 | let find = 5; 36 | 37 | var result = binarySearch(arr, find); 38 | assert.equal(result, 4); 39 | 40 | }); 41 | 42 | it('Binary Search item not found', () => { 43 | 44 | let arr = [1]; 45 | let find = 5; 46 | 47 | var result = binarySearch(arr, find); 48 | assert.equal(result, -2); 49 | 50 | }); 51 | 52 | }); 53 | 54 | describe('getLineStartPositions', function(){ 55 | 56 | it('getLineStartPositions with windows newline', () => { 57 | 58 | let test_str = "test: test\r\ntest: test"; 59 | 60 | var result = getLineStartPositions(test_str); 61 | assert.equal(result[0], 0); 62 | assert.equal(result[1], 12); 63 | 64 | }); 65 | 66 | it('getLineStartPositions with normal newline', () => { 67 | 68 | let test_str = "test: test\ntest: test"; 69 | 70 | var result = getLineStartPositions(test_str); 71 | assert.equal(result[0], 0); 72 | assert.equal(result[1], 11); 73 | 74 | }); 75 | 76 | }); 77 | 78 | describe('getPosition', function(){ 79 | 80 | it('getPosition', () => { 81 | 82 | let test_str = "test: test\r\ntest: test"; 83 | 84 | var startPositions = getLineStartPositions(test_str); 85 | var result = getPosition(0, startPositions); 86 | assert.notEqual(result, undefined); 87 | assert.equal(result.line, 0); 88 | assert.equal(result.column, 0); 89 | 90 | }); 91 | 92 | it('getPosition when not found', () => { 93 | 94 | let test_str = "test: test\ntest: test"; 95 | 96 | var startPositions = getLineStartPositions(test_str); 97 | var result = getPosition(5, startPositions); 98 | assert.notEqual(result, undefined); 99 | assert.equal(result.line, 0); 100 | assert.equal(result.column, 5); 101 | 102 | }); 103 | 104 | }); 105 | 106 | 107 | }); 108 | -------------------------------------------------------------------------------- /language-service/test/fixtures/Microsoft.Authorization.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Authorization.json", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "title": "Microsoft.Authorization", 5 | "description": "Microsoft Microsoft.Authorization Resource Types", 6 | "definitions": { 7 | "locks": { 8 | "type": "object", 9 | "properties": { 10 | "type": { 11 | "enum": [ 12 | "Microsoft.Authorization/locks" 13 | ] 14 | }, 15 | "apiVersion": { 16 | "enum": [ 17 | "2015-01-01" 18 | ] 19 | }, 20 | "name": { 21 | "type": "string", 22 | "maxLength": 64, 23 | "description": "Name of the lock" 24 | }, 25 | "dependsOn": { 26 | "type": "array", 27 | "items": { 28 | "type": "string" 29 | }, 30 | "description": "Collection of resources this resource depends on" 31 | }, 32 | "properties": { 33 | "type": "object", 34 | "properties": { 35 | "level": { 36 | "enum": [ 37 | "CannotDelete", 38 | "ReadOnly" 39 | ], 40 | "description": "Microsoft.Authorization/locks: level - specifies the type of lock to apply to the scope. CanNotDelete allows modification but prevents deletion, ReadOnly prevents modification or deletion." 41 | }, 42 | "notes": { 43 | "type": "string", 44 | "maxLength": 512, 45 | "description": "Microsoft.Authorization/locks: notes - user defined notes for the lock" 46 | } 47 | }, 48 | "required": [ 49 | "level" 50 | ] 51 | } 52 | }, 53 | "required": [ 54 | "name", 55 | "type", 56 | "apiVersion", 57 | "properties" 58 | ], 59 | "description": "Microsoft.Authorization/locks resource" 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /language-service/test/fixtures/Microsoft.Resources.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "title": "Microsoft.Resources", 5 | "description": "Microsoft Resources Resource Types", 6 | "definitions": { 7 | "deployments": { 8 | "type": "object", 9 | "properties": { 10 | "type": { 11 | "enum": [ 12 | "Microsoft.Resources/deployments" 13 | ] 14 | }, 15 | "apiVersion": { 16 | "enum": [ 17 | "2015-01-01" 18 | ] 19 | }, 20 | "name": { 21 | "type": "string", 22 | "description": "Name of the deployment" 23 | }, 24 | "dependsOn": { 25 | "type": "array", 26 | "items": { 27 | "type": "string" 28 | }, 29 | "description": "Collection of resources this deployment depends on" 30 | }, 31 | "properties": { 32 | "allOf": [ 33 | { 34 | "type": "object", 35 | "properties": { 36 | "mode": { 37 | "enum": [ "Incremental" ], 38 | "description": "Deployment mode" 39 | } 40 | }, 41 | "required": [ "mode" ] 42 | }, 43 | { 44 | "anyOf": [ 45 | { 46 | "type": "object", 47 | "properties": { 48 | "templateLink": { 49 | "$ref": "#/definitions/templateLink" 50 | } 51 | } 52 | }, 53 | { 54 | "type": "object", 55 | "properties": { 56 | "template": { 57 | "type": "string" 58 | } 59 | } 60 | } 61 | ] 62 | }, 63 | { 64 | "anyOf": [ 65 | { 66 | "type": "object", 67 | "properties": { 68 | "parametersLink": { 69 | "$ref": "#/definitions/parametersLink" 70 | } 71 | } 72 | }, 73 | { 74 | "type": "object", 75 | "properties": { 76 | "parameters": { 77 | "type": "object", 78 | "additionalProperties": { 79 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#/definitions/parameter" 80 | } 81 | } 82 | } 83 | } 84 | ] 85 | } 86 | ] 87 | } 88 | }, 89 | "required": [ 90 | "type", 91 | "apiVersion", 92 | "name", 93 | "properties" 94 | ] 95 | }, 96 | "templateLink": { 97 | "type": "object", 98 | "properties": { 99 | "uri": { 100 | "type": "string", 101 | "description": "URI referencing the deployment template" 102 | }, 103 | "contentVersion": { 104 | "type": "string", 105 | "pattern": "(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)", 106 | "description": "If included it must match the contentVersion in the template" 107 | } 108 | }, 109 | "required": [ "uri" ], 110 | "description": "Template file reference in a deployment" 111 | }, 112 | "parametersLink": { 113 | "type": "object", 114 | "properties": { 115 | "uri": { 116 | "type": "string", 117 | "description": "URI referencing the deployment template parameters" 118 | }, 119 | "contentVersion": { 120 | "type": "string", 121 | "pattern": "(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)", 122 | "description": "If included it must match the contentVersion in the parameters file" 123 | } 124 | }, 125 | "required": [ "uri" ], 126 | "description": "Parameter file reference in a deployment" 127 | }, 128 | "links": { 129 | "type": "object", 130 | "properties": { 131 | "type": { 132 | "enum": [ 133 | "Microsoft.Resources/links" 134 | ] 135 | }, 136 | "apiVersion": { 137 | "enum": [ 138 | "2015-01-01" 139 | ] 140 | }, 141 | "name": { 142 | "type": "string", 143 | "maxLength": 64, 144 | "description": "Name of the link" 145 | }, 146 | "dependsOn": { 147 | "type": "array", 148 | "items": { 149 | "type": "string" 150 | }, 151 | "description": "Collection of resources this link depends on" 152 | }, 153 | "properties": { 154 | "type": "object", 155 | "properties": { 156 | "targetId": { 157 | "type": "string", 158 | "description": "Target resource id to link to" 159 | }, 160 | "notes": { 161 | "type": "string", 162 | "maxLength": 512, 163 | "description": "Notes for this link" 164 | } 165 | }, 166 | "required": [ 167 | "targetId" 168 | ] 169 | } 170 | }, 171 | "required": [ 172 | "type", 173 | "apiVersion", 174 | "name", 175 | "properties" 176 | ] 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /language-service/test/fixtures/Microsoft.Web.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://schema.management.azure.com/schemas/2014-06-01/Microsoft.Web.json", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "title": "Microsoft.Web", 5 | "description": "Microsoft Web Resource Types", 6 | "definitions": { 7 | "serverfarms": { 8 | "type": "object", 9 | "properties": { 10 | "type": { 11 | "enum": [ 12 | "Microsoft.Web/serverfarms" 13 | ] 14 | }, 15 | "apiVersion": { 16 | "enum": [ 17 | "2014-06-01" 18 | ] 19 | }, 20 | "properties": { 21 | "type": "object", 22 | "properties": { 23 | "name": { 24 | "type": "string", 25 | "description": "Microsoft.Web/serverfarms: Name of the server farm." 26 | }, 27 | "sku": { 28 | "oneOf": [ 29 | { 30 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" 31 | }, 32 | { 33 | "enum": [ 34 | "Free", 35 | "Shared", 36 | "Basic", 37 | "Standard" 38 | ] 39 | } 40 | ], 41 | "description": "Microsoft.Web/serverfarms: Server farm sku." 42 | }, 43 | "workerSize": { 44 | "oneOf": [ 45 | { 46 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" 47 | }, 48 | { 49 | "enum": [ 50 | "Small", 51 | "Medium", 52 | "Large" 53 | ] 54 | }, 55 | { 56 | "type": "integer", 57 | "minimum": 0, 58 | "maximum": 2 59 | } 60 | ], 61 | "description": "Microsoft.Web/serverfarms: The instance size." 62 | }, 63 | "numberOfWorkers": { 64 | "oneOf": [ 65 | { 66 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" 67 | }, 68 | { 69 | "type": "integer", 70 | "minimum": 0, 71 | "maximum": 10 72 | } 73 | ], 74 | "description": "Microsoft.Web/serverfarms: The instance count, which is the number of virtual machines dedicated to the farm. Supported values are 1-10." 75 | } 76 | } 77 | } 78 | }, 79 | "required": [ 80 | "type", 81 | "apiVersion", 82 | "properties" 83 | ] 84 | }, 85 | "config": { 86 | "type": "object", 87 | "properties": { 88 | "type": { 89 | "enum": [ 90 | "Microsoft.Web/sites/config", 91 | "config" 92 | ] 93 | }, 94 | "apiVersion": { 95 | "enum": [ 96 | "2014-06-01" 97 | ] 98 | }, 99 | "properties": { 100 | "type": "object", 101 | "properties": { 102 | "connectionStrings": { 103 | "type": "array", 104 | "items": { 105 | "type": "object", 106 | "properties": { 107 | "ConnectionString": { 108 | "type": "string", 109 | "description": "Microsoft.Web/sites/config: connection string" 110 | }, 111 | "Name": { 112 | "type": "string", 113 | "description": "Microsoft.Web/sites/config: connection string name" 114 | }, 115 | "Type": { 116 | "type": "integer", 117 | "description": "Microsoft.Web/sites/config: connection string type" 118 | } 119 | } 120 | }, 121 | "uniqueItems": true, 122 | "description": "Microsoft.Web/sites/config: Connection strings for database and other external resources." 123 | }, 124 | "phpVersion": { 125 | "type": "string", 126 | "description": "Microsoft.Web/sites/config: PHP version (an empty string disables PHP)." 127 | }, 128 | "netFrameworkVersion": { 129 | "type": "string", 130 | "description": "Microsoft.Web/sites/config: The .Net Framework version." 131 | } 132 | } 133 | } 134 | }, 135 | "description": "Microsoft.Web/sites: Configuration settings for a web site.", 136 | "required": [ 137 | "type", 138 | "apiVersion", 139 | "properties" 140 | ] 141 | }, 142 | "extensions": { 143 | "type": "object", 144 | "properties": { 145 | "type": { 146 | "enum": [ 147 | "Microsoft.Web/sites/extensions", 148 | "extensions" 149 | ] 150 | }, 151 | "apiVersion": { 152 | "enum": [ 153 | "2014-06-01" 154 | ] 155 | }, 156 | "properties": { 157 | "type": "object", 158 | "properties": { 159 | "packageUri": { 160 | "type": "string", 161 | "description": "Microsoft.Web/sites/extensions: uri of package" 162 | }, 163 | "dbType": { 164 | "type": "string", 165 | "description": "Microsoft.Web/sites/extensions: type of database" 166 | }, 167 | "connectionString": { 168 | "type": "string", 169 | "description": "Microsoft.Web/sites/extensions: connection string" 170 | }, 171 | "setParameters": { 172 | "type": "object", 173 | "description": "Microsoft.Web/sites/extensions: parameters" 174 | } 175 | } 176 | } 177 | }, 178 | "required": [ 179 | "type", 180 | "apiVersion", 181 | "properties" 182 | ] 183 | }, 184 | "sites": { 185 | "type": "object", 186 | "properties": { 187 | "type": { 188 | "enum": [ 189 | "Microsoft.Web/sites" 190 | ] 191 | }, 192 | "apiVersion": { 193 | "enum": [ 194 | "2014-06-01" 195 | ] 196 | }, 197 | "properties": { 198 | "type": "object", 199 | "properties": { 200 | "name": { 201 | "type": "string", 202 | "description": "Microsoft.Web/sites: The name of web site." 203 | }, 204 | "serverFarm": { 205 | "type": "string", 206 | "description": "Microsoft.Web/sites: The name of server farm site belongs to." 207 | }, 208 | "hostnames": { 209 | "type": "array", 210 | "items": { 211 | "type": "string" 212 | }, 213 | "description": "Microsoft.Web/sites: An array of strings that contains the public hostnames for the site, including custom domains." 214 | }, 215 | "enabledHostnames": { 216 | "type": "array", 217 | "items": { 218 | "type": "string" 219 | }, 220 | "description": "Microsoft.Web/sites: An array of strings that contains enabled hostnames for the site. By default, these are .azurewebsites.net and .scm.azurewebsites.net." 221 | }, 222 | "hostNameSslStates": { 223 | "type": "array", 224 | "items": { 225 | "type": "object", 226 | "properties": { 227 | "name": { 228 | "type": "string", 229 | "description": "Microsoft.Web/sites/hostNameSslStates: The URL of the web site." 230 | }, 231 | "sslState": { 232 | "oneOf": [ 233 | { 234 | "enum": [ 235 | "Disabled", 236 | "IpBasedEnabled", 237 | "SniEnabled" 238 | ] 239 | }, 240 | { 241 | "type": "integer", 242 | "minimum": 0, 243 | "maximum": 2 244 | }, 245 | { 246 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" 247 | } 248 | ], 249 | "description": "Microsoft.Web/sites/hostNameSslStates. The SSL state." 250 | }, 251 | "thumbprint": { 252 | "type": "string", 253 | "description": "Microsoft.Web/sites/hostNameSslStates: A string that contains the thumbprint of the SSL certificate." 254 | }, 255 | "ipBasedSslState": { 256 | "oneOf": [ 257 | { 258 | "enum": [ 259 | "Disabled", 260 | "IpBasedEnabled", 261 | "SniEnabled" 262 | ] 263 | }, 264 | { 265 | "type": "integer", 266 | "minimum": 0, 267 | "maximum": 2 268 | }, 269 | { 270 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" 271 | } 272 | ], 273 | "description": "Microsoft.Web/sites/hostNameSslStates: IP Based SSL state" 274 | } 275 | } 276 | }, 277 | "description": "Microsoft.Web/sites: Container for SSL states." 278 | } 279 | } 280 | }, 281 | "resources": { 282 | "type": "array", 283 | "items": { 284 | "allOf": [ 285 | { "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/resourceBase" }, 286 | { 287 | "oneOf": [ 288 | {"$ref": "#/definitions/config"}, 289 | {"$ref": "#/definitions/extensions"} 290 | ] 291 | } 292 | ], 293 | "description": "Microsoft.Web/sites: Child resources to define configuration and extensions." 294 | } 295 | } 296 | }, 297 | "required": [ 298 | "type", 299 | "apiVersion", 300 | "properties" 301 | ] 302 | }, 303 | "certificates": { 304 | "type": "object", 305 | "properties": { 306 | "type": { 307 | "enum": [ 308 | "Microsoft.Web/certificates" 309 | ] 310 | }, 311 | "apiVersion": { 312 | "enum": [ 313 | "2014-06-01" 314 | ] 315 | }, 316 | "properties": { 317 | "type": "object", 318 | "properties": { 319 | "pfxBlob": { 320 | "type": "string", 321 | "description": "Microsoft.Web/certificates: A base64Binary value that contains the PfxBlob of the certificate." 322 | }, 323 | "password": { 324 | "type": "string", 325 | "description": "Microsoft.Web/certficates: A string that contains the password for the certificate." 326 | } 327 | } 328 | } 329 | } 330 | } 331 | } 332 | } -------------------------------------------------------------------------------- /language-service/test/fixtures/SuccessBricks.ClearDB.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://schema.management.azure.com/schemas/2014-04-01/SuccessBricks.ClearDB.json", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "title": "SuccessBricks.ClearDB", 5 | "description": "SuccessBricks ClearDB Resource Types", 6 | "definitions": { 7 | "databases": { 8 | "type":"object", 9 | "properties": { 10 | "type": { 11 | "enum": [ 12 | "SuccessBricks.ClearDB/databases" 13 | ] 14 | }, 15 | "apiVersion": { 16 | "enum": [ 17 | "2014-04-01" 18 | ] 19 | }, 20 | "plan": { 21 | "type": "object", 22 | "properties": { 23 | "name": { 24 | "oneOf": [ 25 | { 26 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression" 27 | }, 28 | { 29 | "enum": [ 30 | "Free", 31 | "Jupiter", 32 | "Saturn", 33 | "Venus" 34 | ] 35 | } 36 | ], 37 | "description": "Name of the plan" 38 | } 39 | }, 40 | "required": ["name"], 41 | "description": "ClearDB database plan" 42 | } 43 | }, 44 | "required": [ 45 | "type", 46 | "apiVersion", 47 | "plan" 48 | ] 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /language-service/test/fixtures/deploymentParameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters#", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "title": "Parameters", 5 | "description": "An Azure deployment parameter file", 6 | "type": "object", 7 | "properties": { 8 | "$schema": { 9 | "type": "string" 10 | }, 11 | "contentVersion": { 12 | "type": "string", 13 | "pattern": "(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)", 14 | "description": "A 4 number format for the version number of this parameter file. For example, 1.0.0.0" 15 | }, 16 | "parameters": { 17 | "type": "object", 18 | "additionalProperties": { 19 | "$ref": "#/definitions/parameter" 20 | }, 21 | "description": "Collection of parameters to pass into a template" 22 | } 23 | }, 24 | "required": [ 25 | "$schema", 26 | "contentVersion", 27 | "parameters" 28 | ], 29 | "definitions": { 30 | "parameter": { 31 | "type": "object", 32 | "properties": { 33 | "value": { 34 | "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/parameterValueTypes", 35 | "description": "Input value to template" 36 | }, 37 | "metadata": { 38 | "type": "object", 39 | "description": "Client specific metadata" 40 | } 41 | }, 42 | "required": [ 43 | "value" 44 | ] 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /language-service/test/objects.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import {equals} from '../src/utils/objects'; 6 | var assert = require('assert'); 7 | 8 | describe("Object Equals Tests", () => { 9 | 10 | describe('Equals', function(){ 11 | 12 | it('Both are null', () => { 13 | 14 | let one = null; 15 | let other = null; 16 | 17 | var result = equals(one, other); 18 | assert.equal(result, true); 19 | 20 | }); 21 | 22 | it('One is null the other is true', () => { 23 | 24 | let one = null; 25 | let other = true; 26 | 27 | var result = equals(one, other); 28 | assert.equal(result, false); 29 | 30 | }); 31 | 32 | it('One is string the other is boolean', () => { 33 | 34 | let one = "test"; 35 | let other = false; 36 | 37 | var result = equals(one, other); 38 | assert.equal(result, false); 39 | 40 | }); 41 | 42 | it('One is not object', () => { 43 | 44 | let one = "test"; 45 | let other = false; 46 | 47 | var result = equals(one, other); 48 | assert.equal(result, false); 49 | 50 | }); 51 | 52 | it('One is array the other is not', () => { 53 | 54 | let one = new Proxy([], {}); 55 | let other = Object.keys({ 56 | 1: "2", 57 | 2: "3" 58 | }); 59 | var result = equals(one, other); 60 | assert.equal(result, false); 61 | 62 | }); 63 | 64 | it('Both are arrays of different length', () => { 65 | 66 | let one = [1,2,3]; 67 | let other = [1,2,3,4]; 68 | 69 | var result = equals(one, other); 70 | assert.equal(result, false); 71 | 72 | }); 73 | 74 | it('Both are arrays of same elements but in different order', () => { 75 | 76 | let one = [1,2,3]; 77 | let other = [3,2,1]; 78 | 79 | var result = equals(one, other); 80 | assert.equal(result, false); 81 | 82 | }); 83 | 84 | it('Arrays that are equal', () => { 85 | 86 | let one = [1,2,3]; 87 | let other = [1,2,3]; 88 | 89 | var result = equals(one, other); 90 | assert.equal(result, true); 91 | 92 | }); 93 | 94 | it('Objects that are equal', () => { 95 | 96 | let one = { 97 | "test": 1 98 | }; 99 | let other = { 100 | "test": 1 101 | }; 102 | 103 | var result = equals(one, other); 104 | assert.equal(result, true); 105 | 106 | }); 107 | 108 | it('Objects that have same keys but different values', () => { 109 | 110 | let one = { 111 | "test": 1 112 | }; 113 | let other = { 114 | "test": 5 115 | }; 116 | 117 | var result = equals(one, other); 118 | assert.equal(result, false); 119 | 120 | }); 121 | 122 | it('Objects that have different keys', () => { 123 | 124 | let one = { 125 | "test_one": 1 126 | }; 127 | let other = { 128 | "test_other": 1 129 | }; 130 | 131 | var result = equals(one, other); 132 | assert.equal(result, false); 133 | 134 | }); 135 | 136 | }); 137 | 138 | }); 139 | -------------------------------------------------------------------------------- /language-service/test/pipelinesTests/yamlTraversal.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as yamlparser from '../../src/parser/yamlParser' 3 | import { YAMLTraversal, YamlNodeInfo, YamlNodePropertyValues } from '../../src/services/yamlTraversal'; 4 | import { TextDocument } from 'vscode-languageserver-textdocument'; 5 | import { Position } from 'vscode-languageserver-types'; 6 | 7 | describe("Yaml Traversal Service Tests", function () { 8 | this.timeout(20000); 9 | 10 | it('Empty file should have no results', async function () { 11 | const results = await findNodes("", "testnode"); 12 | assert.equal(results.length, 0, "length"); 13 | }); 14 | 15 | it('find a node', async function () { 16 | const results = await findNodes("- testnode: test", "testnode"); 17 | assert.equal(results.length, 1, "length"); 18 | assert.equal(results[0].key, "testnode", "key"); 19 | assert.equal(results[0].value, "test", "value"); 20 | assert.equal(results[0].startPosition.character, 2, "character"); 21 | assert.equal(results[0].startPosition.line, 0, "line"); 22 | assert.equal(results[0].endPosition.character, 16, "character"); 23 | assert.equal(results[0].endPosition.line, 0, "line"); 24 | }); 25 | 26 | it('find a node in the middle of the document', async function () { 27 | const results = await findNodes("- somecontent: content\n- testnode: test\n- morecontent: content", "testnode"); 28 | assert.equal(results.length, 1, "length"); 29 | assert.equal(results[0].key, "testnode", "key"); 30 | assert.equal(results[0].value, "test", "value"); 31 | assert.equal(results[0].startPosition.character, 2, "character"); 32 | assert.equal(results[0].startPosition.line, 1, "line"); 33 | assert.equal(results[0].endPosition.character, 16, "character"); 34 | assert.equal(results[0].endPosition.line, 1, "line"); 35 | }); 36 | 37 | it('find multiple nodes', async function () { 38 | const results = await findNodes("- somecontent: content\n- testnode: test\n- morecontent: content\n- testnode: test2\n- testnode: test3", "testnode"); 39 | assert.equal(results.length, 3, "length"); 40 | assert.equal(results[0].key, "testnode", "key0"); 41 | assert.equal(results[0].value, "test", "value0"); 42 | assert.equal(results[0].startPosition.character, 2, "character0"); 43 | assert.equal(results[0].startPosition.line, 1, "line0"); 44 | assert.equal(results[0].endPosition.character, 16, "character0"); 45 | assert.equal(results[0].endPosition.line, 1, "line0"); 46 | assert.equal(results[1].key, "testnode", "key1"); 47 | assert.equal(results[1].value, "test2", "value1"); 48 | assert.equal(results[1].startPosition.character, 2, "character1"); 49 | assert.equal(results[1].startPosition.line, 3, "line1"); 50 | assert.equal(results[1].endPosition.character, 17, "character1"); 51 | assert.equal(results[1].endPosition.line, 3, "line1"); 52 | assert.equal(results[2].key, "testnode", "key2"); 53 | assert.equal(results[2].value, "test3", "value2"); 54 | assert.equal(results[2].startPosition.character, 2, "character2"); 55 | assert.equal(results[2].startPosition.line, 4, "line2"); 56 | assert.equal(results[2].endPosition.character, 17, "character2"); 57 | assert.equal(results[2].endPosition.line, 4, "line2"); 58 | }); 59 | 60 | it('does not include expressions', async function () { 61 | const results = await findNodes(` 62 | steps: 63 | - \${{ if succeeded() }}: 64 | - task: npmAuthenticate@0 65 | `, "${{ if succeeded() }}"); 66 | assert.equal(results.length, 0); 67 | }); 68 | 69 | it('realistic example', async function () { 70 | const results = await findNodes(testYaml(), "task"); 71 | assert.equal(results.length, 4, "length"); 72 | assert.equal(results[0].key, "task", "key0"); 73 | assert.equal(results[0].value, "DotNetCoreCLI@2", "value0"); 74 | assert.equal(results[0].startPosition.character, 8, "character0"); 75 | assert.equal(results[0].startPosition.line, 5, "line0"); 76 | assert.equal(results[0].endPosition.character, 34, "character0"); 77 | assert.equal(results[0].endPosition.line, 11, "line0"); 78 | assert.equal(results[1].key, "task", "key1"); 79 | assert.equal(results[1].value, "DotNetCoreCLI@2", "value1"); 80 | assert.equal(results[1].startPosition.character, 8, "character1"); 81 | assert.equal(results[1].startPosition.line, 13, "line1"); 82 | assert.equal(results[1].endPosition.character, 103, "character1"); 83 | assert.equal(results[1].endPosition.line, 17, "line1"); 84 | assert.equal(results[2].key, "task", "key2"); 85 | assert.equal(results[2].value, "PublishBuildArtifacts@1", "value2"); 86 | assert.equal(results[2].startPosition.character, 8, "character2"); 87 | assert.equal(results[2].startPosition.line, 19, "line2"); 88 | assert.equal(results[2].endPosition.character, 30, "character2"); 89 | assert.equal(results[2].endPosition.line, 24, "line2"); 90 | assert.equal(results[3].key, "task", "key3"); 91 | assert.equal(results[3].value, null); 92 | assert.equal(results[3].startPosition.character, 8, "character3"); 93 | assert.equal(results[3].startPosition.line, 29, "line3"); 94 | assert.equal(results[3].endPosition.character, 13, "character3"); 95 | assert.equal(results[3].endPosition.line, 29, "line3"); 96 | }); 97 | 98 | it('get inputs', async function () { 99 | const results = findInputs(testYaml(), { line: 5, character: 8 }); 100 | assert.equal(Object.keys(results.values).length, 4); 101 | assert.equal(results.values["command"], "test"); 102 | assert.equal(results.values["projects"], "**/*Test*/*.csproj"); 103 | assert.equal(results.values["arguments"], "--configuration $(BuildConfiguration)"); 104 | assert.equal(results.values["publishTestResults"], true); 105 | }); 106 | 107 | it('fail to get inputs', async function () { 108 | const results = findInputs(testYaml(), { line: 3, character: 2 }); 109 | assert.equal(results.values, null); 110 | }); 111 | }); 112 | 113 | async function findNodes(content: string, key: string): Promise { 114 | const schemaUri: string = "test/pipelinesTests/schema.json"; 115 | const yamlTraversal = new YAMLTraversal(Promise); 116 | const textDocument: TextDocument = TextDocument.create(schemaUri, "azure-pipelines", 1, content); 117 | const yamlDoc = yamlparser.parse(content); 118 | return yamlTraversal.findNodes(textDocument, yamlDoc, key); 119 | } 120 | 121 | function findInputs(content: string, position: Position): YamlNodePropertyValues { 122 | const schemaUri: string = "test/pipelinesTests/schema.json"; 123 | const yamlTraversal = new YAMLTraversal(Promise); 124 | const textDocument: TextDocument = TextDocument.create(schemaUri, "azure-pipelines", 1, content); 125 | const yamlDoc = yamlparser.parse(content); 126 | return yamlTraversal.getNodePropertyValues(textDocument, yamlDoc, position, "inputs"); 127 | } 128 | 129 | function testYaml(): string { 130 | return `variables: 131 | BuildConfiguration: Release 132 | 133 | steps: 134 | 135 | - task: DotNetCoreCLI@2 136 | name: Test 137 | inputs: 138 | command: test 139 | projects: "**/*Test*/*.csproj" 140 | arguments: "--configuration $(BuildConfiguration)" 141 | publishTestResults: true 142 | 143 | - task: DotNetCoreCLI@2 144 | name: Publish 145 | inputs: 146 | command: publish 147 | arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)" 148 | 149 | - task: PublishBuildArtifacts@1 150 | name: Artifacts 151 | inputs: 152 | PathToPublish: "$(Build.ArtifactStagingDirectory)" 153 | ArtifactType: Container 154 | ArtifactName: "drop" 155 | 156 | - script: '- task: fake' 157 | 158 | # incomplete task 159 | - task: 160 | 161 | # - task: AzureRmWebAppDeployment@3 162 | # name: Deploy 163 | # inputs: 164 | # ConnectedServiceName: "My Azure Sub" 165 | # Package: "$(Build.ArtifactStagingDirectory)/**/*.zip"` 166 | } 167 | -------------------------------------------------------------------------------- /language-service/test/pipelinesTests/yamlcompletion.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs/promises'; 2 | import { YAMLCompletion } from '../../src/services/yamlCompletion'; 3 | import * as JSONSchemaService from '../../src/services/jsonSchemaService'; 4 | import { JSONSchema } from '../../src/jsonSchema'; 5 | import * as URL from 'url'; 6 | import { TextDocument } from 'vscode-languageserver-textdocument'; 7 | import { Position, CompletionList, Range } from 'vscode-languageserver-types'; 8 | import * as yamlparser from '../../src/parser/yamlParser' 9 | import * as assert from 'assert'; 10 | import { completionHelper } from '../../src/utils/yamlServiceUtils'; 11 | 12 | interface Suggestions { 13 | expected?: number, 14 | minimum?: number, 15 | maximum?: number 16 | } 17 | 18 | describe("Yaml Completion Service Tests", function () { 19 | this.timeout(20000); 20 | 21 | it('Given empty file completion should give suggestions', async function () { 22 | const list = await runTaskCompletionItemsTest("", {line: 0, character: 0}, {}); 23 | const labels = list.items.map(item => item.label); 24 | assert.equal(labels.filter(l => l === "name").length, 1); 25 | assert.equal(labels.filter(l => l === "steps").length, 1); 26 | assert.equal(labels.filter(l => l === "variables").length, 1); 27 | assert.equal(labels.filter(l => l === "server").length, 0, "obsolete should not be suggested"); 28 | }); 29 | 30 | it('Given steps context completion should give suggestions of possible steps', async function () { 31 | await runTaskCompletionItemsTest("steps:\n- ", {line: 1, character: 2}, {expected: 7}); 32 | }); 33 | 34 | it('Given an already valid file with task name, autocomplete should still give all suggestions', async function () { 35 | await runTaskCompletionItemsTest('steps:\n- task: npmAuthenticate@0', {line: 1, character: 26}, {minimum: 100}); 36 | }); 37 | 38 | it('Given a new file with steps and task, autocomplete should give suggestions', async function() { 39 | await runTaskCompletionItemsTest('steps:\n - task: ', {line: 1, character: 10}, {minimum: 100}); 40 | }); 41 | 42 | it('All completion text for properties should end with `:`', async function() { 43 | const list = await runTaskCompletionItemsTest('', {line: 0, character: 0}, {}); 44 | list.items.forEach(item => { 45 | assert.equal(item.textEdit.newText.search(":") > 0, true, "new text should contain `:`"); 46 | }); 47 | }); 48 | 49 | it('String properties replacement range should include colon', async function() { 50 | const list = await runTaskCompletionItemsTest('steps:\n- scrip: ', {line: 1, character: 5}, {}); 51 | const expectedReplacementLength = "scrip:".length; 52 | list.items.forEach(item => { 53 | let actualRange: Range = item.textEdit['range']; 54 | let actualLength = actualRange.end.character - actualRange.start.character; 55 | assert.equal(actualLength, expectedReplacementLength); 56 | }); 57 | }); 58 | 59 | it('trailing whitespace does not affect suggestions', async function() { 60 | await runTaskCompletionItemsTest('strategy:\n ', {line: 1, character: 2}, {expected: 3}); 61 | }); 62 | 63 | it('case insensitive matching keys are not suggested', async function() { 64 | //first make sure that the azureAppServiceManage is still in the schema and has an Action input 65 | { 66 | const list = await runTaskCompletionItemsTest('steps:\n- task: azureAppServiceManage@0\n inputs:\n ', {line: 3, character: 4}, {minimum: 6}); 67 | const labels = list.items.map(item => item.label); 68 | assert.equal(labels.filter(l => l.toUpperCase() === "Action".toUpperCase()).length, 1); 69 | } 70 | 71 | //now make sure that it isn't suggested if an off-case version is present 72 | { 73 | const list = await runTaskCompletionItemsTest('steps:\n- task: azureAppServiceManage@0\n inputs:\n ACTION: Restart Azure App Service\n ', {line: 4, character: 4}, {minimum: 6}); 74 | const labels = list.items.map(item => item.label); 75 | assert.equal(labels.filter(l => l.toUpperCase() === "Action".toUpperCase()).length, 0); 76 | } 77 | }); 78 | 79 | it('alias matching keys are not suggested', async function() { 80 | //first make sure that azureSubscription is still in the schema 81 | { 82 | const list = await runTaskCompletionItemsTest('steps:\n- task: azureAppServiceManage@0\n inputs:\n ACTION: Restart Azure App Service\n ', {line: 4, character: 4}, {minimum: 6}); 83 | const labels = list.items.map(item => item.label); 84 | assert.equal(labels.filter(l => l.toUpperCase() === "azureSubscription".toUpperCase()).length, 1); 85 | } 86 | 87 | //now make sure it is not suggested when an alias is present 88 | { 89 | const list = await runTaskCompletionItemsTest('steps:\n- task: azureAppServiceManage@0\n inputs:\n ConnectedServiceName: some_service\n ', {line: 4, character: 4}, {minimum: 6}); 90 | const labels = list.items.map(item => item.label); 91 | assert.equal(labels.filter(l => l.toUpperCase() === "azureSubscription".toUpperCase()).length, 0); 92 | } 93 | }); 94 | 95 | it('suggests tasks under expressions', async function() { 96 | const list = await runTaskCompletionItemsTest(` 97 | steps: 98 | - \${{ if succeeded() }}: 99 | - `, {line: 3, character: 4}, {expected: 7}); 100 | const labels = list.items.map(item => item.label); 101 | assert.ok(labels.includes('task')); 102 | }); 103 | 104 | it('suggests properties under expressions', async function() { 105 | const list = await runTaskCompletionItemsTest(` 106 | steps: 107 | - task: azureAppServiceManage@0 108 | inputs: 109 | \${{ if succeeded() }}: 110 | a`, {line: 5, character: 7}, {minimum: 1}); 111 | const labels = list.items.map(item => item.label); 112 | assert.ok(labels.includes('azureSubscription')); 113 | }); 114 | }); 115 | 116 | const workspaceContext = { 117 | resolveRelativePath: (relativePath: string, resource: string) => { 118 | return URL.resolve(resource, relativePath); 119 | } 120 | }; 121 | 122 | const requestService = (path: string): Promise => { 123 | return fs.readFile(path, 'utf-8'); 124 | }; 125 | 126 | const schemaResolver = (url: string): Promise => { 127 | return Promise.resolve(JSONSchemaService.ParseSchema(url)); 128 | } 129 | 130 | 131 | // Given a file and a position, this test expects the task list to show as completion items. 132 | async function runTaskCompletionItemsTest(content: string, position: Position, suggestions: Suggestions): Promise { 133 | // Arrange 134 | const schemaUri: string = "test/pipelinesTests/schema.json"; 135 | const schemaService = new JSONSchemaService.JSONSchemaService(schemaResolver, workspaceContext, requestService); 136 | 137 | const yamlCompletion = new YAMLCompletion(schemaService, [], Promise); 138 | const textDocument: TextDocument = TextDocument.create(schemaUri, "azure-pipelines", 1, content); 139 | 140 | const completionFix = completionHelper(textDocument, position); 141 | const newText = completionFix.newText; 142 | const yamlDoc = yamlparser.parse(newText); 143 | 144 | // Act 145 | const completionList = await yamlCompletion.doComplete(textDocument, position, yamlDoc); 146 | 147 | // Assert 148 | 149 | if (typeof suggestions.expected != 'undefined') { 150 | assert.equal(completionList.items.length, suggestions.expected); 151 | } 152 | else { 153 | if (typeof suggestions.minimum != 'undefined') { 154 | assert.ok(completionList.items.length >= suggestions.minimum); 155 | } 156 | 157 | if (typeof suggestions.maximum != 'undefined') { 158 | assert.ok(completionList.items.length <= suggestions.maximum); 159 | } 160 | } 161 | 162 | return completionList; 163 | } 164 | -------------------------------------------------------------------------------- /language-service/test/pipelinesTests/yamlvalidation.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs/promises'; 2 | import { YAMLValidation } from '../../src/services/yamlValidation'; 3 | import * as JSONSchemaService from '../../src/services/jsonSchemaService'; 4 | import { JSONSchema } from '../../src/jsonSchema'; 5 | import * as URL from 'url'; 6 | import { TextDocument } from 'vscode-languageserver-textdocument'; 7 | import { Diagnostic } from 'vscode-languageserver-types'; 8 | import * as yamlparser from '../../src/parser/yamlParser' 9 | import * as assert from 'assert'; 10 | 11 | describe("Yaml Validation Service Tests", function () { 12 | this.timeout(20000); 13 | 14 | it('validates empty files', async function () { 15 | const diagnostics = await runValidationTest(""); 16 | assert.equal(diagnostics.length, 0); 17 | }); 18 | 19 | it('validates files with emojis', async function () { 20 | const diagnostics = await runValidationTest(` 21 | steps: 22 | - pwsh: Write-Output 😊 23 | `); 24 | assert.equal(diagnostics.length, 0); 25 | }); 26 | 27 | it('rejects multi-document files with only one error', async function () { 28 | const diagnostics = await runValidationTest(` 29 | --- 30 | jobs: 31 | - job: some_job 32 | invalid_parameter: bad value 33 | invalid_parameter: duplicate key 34 | another_invalid_parameter: whatever 35 | === 36 | --- 37 | jobs: 38 | - job: some_job 39 | invalid_parameter: bad value 40 | invalid_parameter: duplicate key 41 | another_invalid_parameter: whatever 42 | === 43 | `); 44 | assert.equal(diagnostics.length, 1); 45 | assert.ok(diagnostics[0].message.indexOf("single-document") >= 0); 46 | }); 47 | 48 | // In truth, these tests should probably all be rewritten to test parser/yamlParser, 49 | // not services/yamlValidation. 50 | it('validates pipelines with expressions', async function () { 51 | const diagnostics = await runValidationTest(` 52 | steps: 53 | - \${{ if succeeded() }}: 54 | - task: npmAuthenticate@0 55 | inputs: 56 | \${{ if ne(variables['Build.Reason'], 'PullRequest') }}: 57 | workingFile: .npmrc 58 | \${{ if eq(variables['Build.Reason'], 'PullRequest') }}: 59 | workingFile: .other_npmrc 60 | `); 61 | assert.equal(diagnostics.length, 0); 62 | }); 63 | 64 | it('validates pipelines with dynamically-generated variables', async function () { 65 | const diagnostics = await runValidationTest(` 66 | variables: 67 | \${{ parameters.environment }}Release: true 68 | `); 69 | assert.equal(diagnostics.length, 0); 70 | }); 71 | 72 | it('validates pipelines with unfinished conditional variable checks', async function () { 73 | // Note: the real purpose of this test is to ensure we don't throw, 74 | // but I can't figure out how to assert that yet. 75 | // diagnostics.length can be whatever, as long as we get to that point :). 76 | const diagnostics = await runValidationTest(` 77 | variables: 78 | \${{ if eq(variables['Build.SourceBranch'], 'main') }}: 79 | `); 80 | assert.equal(diagnostics.length, 0); 81 | }); 82 | 83 | it('validates pipelines with unfinished conditionally-inserted variables', async function () { 84 | // Note: the real purpose of this test is to ensure we don't throw, 85 | // but I can't figure out how to assert that yet. 86 | // diagnostics.length can be whatever, as long as we get to that point :). 87 | const diagnostics = await runValidationTest(` 88 | variables: 89 | \${{ if eq(variables['Build.SourceBranch'], 'main') }}: 90 | j 91 | `); 92 | assert.equal(diagnostics.length, 0); 93 | }); 94 | 95 | it('validates pipelines with multiple levels of expression nesting', async function () { 96 | // Note: the real purpose of this test is to ensure we don't throw, 97 | // but I can't figure out how to assert that yet. 98 | // diagnostics.length can be whatever, as long as we get to that point :). 99 | const diagnostics = await runValidationTest(` 100 | steps: 101 | - \${{ each step in parameters.buildSteps }}: 102 | - \${{ each pair in step }}: 103 | \${{ if ne(pair.value, 'CmdLine@2') }}: 104 | \${{ pair.key }}: \${{ pair.value }} 105 | \${{ if eq(pair.value, 'CmdLine@2') }}: 106 | '\${{ pair.value }}': error 107 | `); 108 | assert.equal(diagnostics.length, 0); 109 | }); 110 | 111 | it('validates pipelines that have an object with a dynamic key and scalar value as the first property', async function () { 112 | // Note: the real purpose of this test is to ensure we don't throw, 113 | // but I can't figure out how to assert that yet. 114 | // diagnostics.length can be whatever, as long as we get to that point :). 115 | const diagnostics = await runValidationTest(` 116 | steps: 117 | - \${{ each shorthand in parameters.taskShorthands }}: 118 | - \${{ shorthand }}: echo 'Hi' 119 | `); 120 | assert.equal(diagnostics.length, 2); 121 | }); 122 | 123 | it('validates pipelines that are using flow-style mappings in an array', async function () { 124 | // Note: the real purpose of this test is to ensure we don't throw, 125 | // but I can't figure out how to assert that yet. 126 | // diagnostics.length can be whatever, as long as we get to that point :). 127 | const diagnostics = await runValidationTest(` 128 | variables: 129 | - { } 130 | `); 131 | assert.equal(diagnostics.length, 1); 132 | }); 133 | 134 | it('validates incorrectly-indented pipelines that look like they have an array property', async function () { 135 | // Note: the real purpose of this test is to ensure we don't throw, 136 | // but I can't figure out how to assert that yet. 137 | // diagnostics.length can be whatever, as long as we get to that point :). 138 | const diagnostics = await runValidationTest(` 139 | steps: 140 | - task: PowerShellOnTargetMachines@3 141 | inputs: 142 | Machines: EXAMPLE 143 | InlineScript: | 144 | [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null 145 | CommunicationProtocol: Http); 146 | `); 147 | assert.equal(diagnostics.length, 4); 148 | }); 149 | }); 150 | 151 | const workspaceContext = { 152 | resolveRelativePath: (relativePath: string, resource: string) => { 153 | return URL.resolve(resource, relativePath); 154 | } 155 | }; 156 | 157 | const requestService = (path: string): Promise => { 158 | return fs.readFile(path, 'utf-8'); 159 | }; 160 | 161 | const schemaResolver = (url: string): Promise => { 162 | return Promise.resolve(JSONSchemaService.ParseSchema(url)); 163 | } 164 | 165 | // Given a file's content, returns the diagnostics found. 166 | async function runValidationTest(content: string): Promise { 167 | const schemaUri: string = "test/pipelinesTests/schema.json"; 168 | const schemaService = new JSONSchemaService.JSONSchemaService(schemaResolver, workspaceContext, requestService); 169 | 170 | const yamlValidation = new YAMLValidation(schemaService, Promise); 171 | const textDocument: TextDocument = TextDocument.create(schemaUri, "azure-pipelines", 1, content); 172 | const yamlDoc = yamlparser.parse(content); 173 | 174 | return await yamlValidation.doValidation(textDocument, yamlDoc); 175 | } 176 | -------------------------------------------------------------------------------- /language-service/test/strings.test.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Red Hat. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import {startsWith, endsWith, convertSimple2RegExp} from '../src/utils/strings'; 6 | var assert = require('assert'); 7 | 8 | describe("String Tests", () => { 9 | 10 | describe('startsWith', function(){ 11 | 12 | it('String with different lengths', () => { 13 | 14 | let one = "hello"; 15 | let other = "goodbye"; 16 | 17 | var result = startsWith(one, other); 18 | assert.equal(result, false); 19 | 20 | }); 21 | 22 | it('String with same length different first letter', () => { 23 | 24 | let one = "hello"; 25 | let other = "jello"; 26 | 27 | var result = startsWith(one, other); 28 | assert.equal(result, false); 29 | 30 | }); 31 | 32 | it('Same string', () => { 33 | 34 | let one = "hello"; 35 | let other = "hello"; 36 | 37 | var result = startsWith(one, other); 38 | assert.equal(result, true); 39 | 40 | }); 41 | 42 | }); 43 | 44 | describe('endsWith', function(){ 45 | 46 | it('String with different lengths', () => { 47 | 48 | let one = "hello"; 49 | let other = "goodbye"; 50 | 51 | var result = endsWith(one, other); 52 | assert.equal(result, false); 53 | 54 | }); 55 | 56 | it('Strings that are the same', () => { 57 | 58 | let one = "hello"; 59 | let other = "hello"; 60 | 61 | var result = endsWith(one, other); 62 | assert.equal(result, true); 63 | 64 | }); 65 | 66 | it('Other is smaller then one', () => { 67 | 68 | let one = "hello"; 69 | let other = "hi"; 70 | 71 | var result = endsWith(one, other); 72 | assert.equal(result, false); 73 | 74 | }); 75 | 76 | }); 77 | 78 | describe('convertSimple2RegExp', function(){ 79 | 80 | it('Test of convertRegexString2RegExp', () => { 81 | 82 | var result = convertSimple2RegExp("/toc\\.yml/i").test("TOC.yml"); 83 | assert.equal(result, true); 84 | 85 | }); 86 | 87 | it('Test of convertGlobalPattern2RegExp', () => { 88 | 89 | var result = convertSimple2RegExp("toc.yml").test("toc.yml"); 90 | assert.equal(result, true); 91 | 92 | result = convertSimple2RegExp("toc.yml").test("TOC.yml"); 93 | assert.equal(result, false); 94 | 95 | }); 96 | 97 | }); 98 | 99 | }); 100 | -------------------------------------------------------------------------------- /language-service/test/uri.test.ts: -------------------------------------------------------------------------------- 1 | // import URI from 'vscode-uri'; 2 | // var path = require('path'); 3 | // var assert = require('assert'); 4 | 5 | describe("URI Tests", () => { 6 | 7 | // describe('URI Parse', function(){ 8 | // it('Basic', () => { 9 | // var result = URI.parse("http://www.foo.com/bar.html?name=hello#123"); 10 | // assert.equal(result.authority, "www.foo.com"); 11 | // assert.equal(result.fragment, "123"); 12 | // assert.equal(result.fsPath, path.sep + "bar.html"); 13 | // assert.equal(result.path, "/bar.html"); 14 | // assert.equal(result.query, "name=hello"); 15 | // assert.equal(result.scheme, "http"); 16 | // }); 17 | // }); 18 | 19 | // describe('URI Create', function(){ 20 | // it('Basic', () => { 21 | // var result = URI.create("http", "www.foo.com", "/bar.html", "name=hello", "123"); 22 | // assert.equal(result.authority, "www.foo.com"); 23 | // assert.equal(result.fragment, "123"); 24 | // assert.equal(result.fsPath, path.sep + "bar.html"); 25 | // assert.equal(result.path, "/bar.html"); 26 | // assert.equal(result.query, "name=hello"); 27 | // assert.equal(result.scheme, "http"); 28 | // }); 29 | // }); 30 | 31 | // describe('URI File', function(){ 32 | // it('Basic', () => { 33 | // var result = URI.file("../uri.test.ts"); 34 | // assert.equal(result.fragment, ""); 35 | // assert.equal(result.fsPath, path.sep + ".." + path.sep + "uri.test.ts"); 36 | // assert.equal(result.path, "/../uri.test.ts"); 37 | // assert.equal(result.query, ""); 38 | // assert.equal(result.scheme, "file"); 39 | // }); 40 | 41 | // it('File with UNC share', () => { 42 | // var result = URI.file("//server/share"); 43 | // assert.equal(result.fragment, ""); 44 | // assert.equal(result.path, "/share"); 45 | // assert.equal(result.query, ""); 46 | // assert.equal(result.scheme, "file"); 47 | // assert.equal(result.authority, "server"); 48 | // }); 49 | 50 | // it('File with location', () => { 51 | // var result = URI.file("//server"); 52 | // assert.equal(result.fragment, ""); 53 | // assert.equal(result.path, "/"); 54 | // assert.equal(result.query, ""); 55 | // assert.equal(result.scheme, "file"); 56 | // assert.equal(result.authority, "server"); 57 | // }); 58 | // }); 59 | 60 | // describe('URI toString', function(){ 61 | // it('toString with encoding', () => { 62 | // var result = URI.parse("http://www.foo.com:8080/bar.html?name=hello#123").toString(); 63 | // assert.equal("http://www.foo.com:8080/bar.html?name%3Dhello#123", result); 64 | // }); 65 | 66 | // it('toString without encoding', () => { 67 | // var result = URI.parse("http://www.foo.com/bar.html?name=hello#123").toString(true); 68 | // assert.equal("http://www.foo.com/bar.html?name=hello#123", result); 69 | // }); 70 | 71 | // it('toString with system file', () => { 72 | // var result = URI.parse("file:///C:/test.txt").toString(true); 73 | // assert.equal("file:///c:/test.txt", result); 74 | // }); 75 | // }); 76 | 77 | // describe('URI toJson', function(){ 78 | // it('toJson with system file', () => { 79 | // var result = URI.parse("file:///C:/test.txt").toJSON(); 80 | // assert.equal(result["authority"], ""); 81 | // assert.equal(result["external"], "file:///c%3A/test.txt"); 82 | // assert.equal(result["fragment"], ""); 83 | // assert.equal(result["fsPath"], "c:" + path.sep + "test.txt"); 84 | // assert.equal(result["path"], "/C:/test.txt"); 85 | // assert.equal(result["query"], ""); 86 | // assert.equal(result["scheme"], "file"); 87 | // }); 88 | // }); 89 | 90 | }); 91 | -------------------------------------------------------------------------------- /language-service/thirdpartynotice.txt: -------------------------------------------------------------------------------- 1 | THIRD PARTY SOFTWARE NOTICES AND INFORMATION 2 | Do Not Translate or Localize 3 | 4 | This software incorporates material from third parties. Microsoft makes certain 5 | open source code available at http://3rdpartysource.microsoft.com, or you may 6 | send a check or money order for US $5.00, including the product name, the open 7 | source component name, and version number, to: 8 | 9 | Source Code Compliance Team 10 | Microsoft Corporation 11 | One Microsoft Way 12 | Redmond, WA 98052 13 | USA 14 | 15 | Notwithstanding any other terms, you may reverse engineer this software to the 16 | extent required to debug changes to any libraries licensed under the GNU Lesser 17 | General Public License. 18 | --- 19 | Component. Red Hat YAML Language Server 20 | 21 | Open Source License/Copyright Notice:. MIT License 22 | 23 | Copyright (c) 2017 Red Hat Inc. and others. 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy 26 | of this software and associated documentation files (the "Software"), to deal 27 | in the Software without restriction, including without limitation the rights 28 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | copies of the Software, and to permit persons to whom the Software is 30 | furnished to do so, subject to the following conditions: 31 | 32 | The above copyright notice and this permission notice shall be included in all 33 | copies or substantial portions of the Software. 34 | 35 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 36 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 37 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 38 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 39 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 40 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 41 | SOFTWARE. 42 | 43 | 44 | Additional Attribution: Gorkem Ercan (Red Hat), Joshua Pinkney (joshpinkney@gmail.com) -------------------------------------------------------------------------------- /language-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "forceConsistentCasingInFileNames": true, 4 | "target": "es5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "declaration": true, 9 | "lib": [ 10 | "es2021", 11 | "dom" 12 | ], 13 | "outDir": "./lib", 14 | "noUnusedLocals": true 15 | }, 16 | "include": [ 17 | "src" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /language-service/webpack.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | const TerserPlugin = require('terser-webpack-plugin'); 7 | 8 | /** @type {import('webpack').Configuration} */ 9 | module.exports = { 10 | entry: { 11 | 'azure-pipelines-language-service': './src/index.ts', 12 | 'azure-pipelines-language-service.min': './src/index.ts' 13 | }, 14 | output: { 15 | path: path.resolve(__dirname, '_bundles'), 16 | filename: '[name].js', 17 | libraryTarget: 'umd', 18 | library: 'AzurePipelinesLanguageService', 19 | umdNamedDefine: true 20 | }, 21 | resolve: { 22 | extensions: ['.ts', '.tsx', '.js'], 23 | fallback: { 24 | buffer: require.resolve('buffer/'), 25 | fs: false, 26 | os: require.resolve('os-browserify/browser'), 27 | path: require.resolve('path-browserify'), 28 | } 29 | }, 30 | mode: 'production', 31 | devtool: 'source-map', 32 | optimization: { 33 | minimize: true, 34 | minimizer: [new TerserPlugin({ 35 | include: /\.min\.js$/, 36 | })], 37 | }, 38 | module: { 39 | rules: [ 40 | { 41 | test: /\.tsx?$/, 42 | loader: "ts-loader" 43 | } 44 | ] 45 | } 46 | } 47 | --------------------------------------------------------------------------------