From 2915eadee9dfbca6c64092e4b3d5f059c40ab859 Mon Sep 17 00:00:00 2001 From: ChristopherHX Date: Wed, 9 Oct 2024 22:18:34 +0200 Subject: [PATCH] azp: vscode extension completions (#403) * semantic highlighting * fix schema of task step to include target --- src/Sdk/Actions/ActionsDescriptions.cs | 38 + src/Sdk/Actions/descriptions.json | 282 +++ src/Sdk/AzurePipelines/AutoCompleteHelper.cs | 959 +++++++++ src/Sdk/AzurePipelines/AzureDevops.cs | 232 ++- src/Sdk/AzurePipelines/CompletionItem.cs | 62 + src/Sdk/AzurePipelines/Context.cs | 8 + src/Sdk/AzurePipelines/azurepiplines.json | 1787 ++++++++++++----- .../AzurePipelines/json-schema-transform.js | 38 +- .../ObjectTemplating/IObjectReader.cs | 4 +- .../ObjectTemplating/Schema/Definition.cs | 4 + .../Schema/OneOfDefinition.cs | 2 +- .../ObjectTemplating/TemplateContext.cs | 49 + .../ObjectTemplating/TemplateEvaluator.cs | 21 + .../ObjectTemplating/TemplateMemory.cs | 2 + .../ObjectTemplating/TemplateReader.cs | 351 +++- .../ObjectTemplating/Tokens/LiteralToken.cs | 2 + .../ObjectTemplating/Tokens/TemplateToken.cs | 5 + .../Tokens/TemplateTokenExtensions.cs | 5 +- .../ObjectTemplating/JsonObjectReader.cs | 4 +- .../ObjectTemplating/YamlObjectReader.cs | 153 +- src/Sdk/Sdk.csproj | 4 + src/azure-pipelines-vscode-ext/CHANGELOG.md | 8 +- src/azure-pipelines-vscode-ext/README.md | 16 + .../ext-core/Interop.cs | 5 + .../ext-core/Program.cs | 46 +- src/azure-pipelines-vscode-ext/index.js | 89 +- .../package-lock.json | 211 +- src/azure-pipelines-vscode-ext/package.json | 25 +- 28 files changed, 3717 insertions(+), 695 deletions(-) create mode 100644 src/Sdk/Actions/ActionsDescriptions.cs create mode 100644 src/Sdk/Actions/descriptions.json create mode 100644 src/Sdk/AzurePipelines/AutoCompleteHelper.cs create mode 100644 src/Sdk/AzurePipelines/CompletionItem.cs diff --git a/src/Sdk/Actions/ActionsDescriptions.cs b/src/Sdk/Actions/ActionsDescriptions.cs new file mode 100644 index 00000000000..a7641fcbe0a --- /dev/null +++ b/src/Sdk/Actions/ActionsDescriptions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; + +using Newtonsoft.Json; + +namespace Sdk.Actions { + + public class ActionsDescriptions + { + + public static Dictionary ToOrdinalIgnoreCaseDictionary(IEnumerable> source) { + var ret = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach(var kv in source) { + ret[kv.Key] = kv.Value; + } + return ret; + } + + public string Description { get; set; } + + public Dictionary Versions { get; set; } + public static Dictionary> LoadDescriptions() { + var assembly = Assembly.GetExecutingAssembly(); + var json = default(String); + using (var stream = assembly.GetManifestResourceStream("descriptions.json")) + using (var streamReader = new StreamReader(stream)) + { + json = streamReader.ReadToEnd(); + } + + return ToOrdinalIgnoreCaseDictionary(JsonConvert.DeserializeObject>>(json).Select(kv => new KeyValuePair>(kv.Key, ToOrdinalIgnoreCaseDictionary(kv.Value)))); + } + } + +} \ No newline at end of file diff --git a/src/Sdk/Actions/descriptions.json b/src/Sdk/Actions/descriptions.json new file mode 100644 index 00000000000..f29f67884dc --- /dev/null +++ b/src/Sdk/Actions/descriptions.json @@ -0,0 +1,282 @@ +{ + "root": { + "github": { + "description": "Information about the workflow run. For more information, see [`github` context](https://docs.github.com/actions/learn-github-actions/contexts#github-context)." + }, + "env": { + "description": "Contains variables set in a workflow, job, or step. For more information, see [`env` context](https://docs.github.com/actions/learn-github-actions/contexts#env-context)." + }, + "vars": { + "description": "Contains variables set at the repository, organization, or environment levels. For more information, see [`vars` context](https://docs.github.com/actions/learn-github-actions/contexts#vars-context)." + }, + "job": { + "description": "Information about the currently running job. For more information, see [`job` context](https://docs.github.com/actions/learn-github-actions/contexts#job-context)." + }, + "jobs": { + "description": "For reusable workflows only, contains outputs of jobs from the reusable workflow. For more information, see [`jobs` context](https://docs.github.com/actions/learn-github-actions/contexts#jobs-context)." + }, + "steps": { + "description": "Information about the steps that have been run in the current job. For more information, see [`steps` context](https://docs.github.com/actions/learn-github-actions/contexts#steps-context)." + }, + "runner": { + "description": "Information about the runner that is running the current job. For more information, see [`runner` context](https://docs.github.com/actions/learn-github-actions/contexts#runner-context)." + }, + "secrets": { + "description": "Contains the names and values of secrets that are available to a workflow run. For more information, see [`secrets` context](https://docs.github.com/actions/learn-github-actions/contexts#secrets-context)." + }, + "strategy": { + "description": "Information about the matrix execution strategy for the current job. For more information, see [`strategy` context](https://docs.github.com/actions/learn-github-actions/contexts#strategy-context)." + }, + "matrix": { + "description": "Contains the matrix properties defined in the workflow that apply to the current job. For more information, see [`matrix` context](https://docs.github.com/actions/learn-github-actions/contexts#matrix-context)." + }, + "needs": { + "description": "Contains the outputs of all jobs that are defined as a dependency of the current job. For more information, see [`needs` context](https://docs.github.com/actions/learn-github-actions/contexts#needs-context)." + }, + "inputs": { + "description": "Contains the inputs of a reusable or manually triggered workflow. For more information, see [`inputs` context](https://docs.github.com/actions/learn-github-actions/contexts#inputs-context)." + } + }, + "functions": { + "success": { + "description": "Returns `true` if all transitive dependencies are successful" + }, + "always": { + "description": "Causes the step to always execute, and returns `true`, even when canceled. The `always` expression is best used at the step level or on tasks that you expect to run even when a job is canceled. For example, you can use `always` to send logs even when a job is canceled." + }, + "cancelled": { + "description": "Returns `true` if the workflow was canceled." + }, + "failure": { + "description": "Returns `true` when any previous step of a job fails. If you have a chain of dependent jobs, `failure()` returns `true` if any ancestor job fails." + }, + "hashFiles": { + "description": "Returns a single hash for the set of files that matches the `path` pattern. You can provide a single `path` pattern or multiple `path` patterns separated by commas. The `path` is relative to the `GITHUB_WORKSPACE` directory and can only include files inside of the `GITHUB_WORKSPACE`. This function calculates an individual SHA-256 hash for each matched file, and then uses those hashes to calculate a final SHA-256 hash for the set of files. If the `path` pattern does not match any files, this returns an empty string. For more information about SHA-256, see \"[SHA-2](https://wikipedia.org/wiki/SHA-2).\"\n\nYou can use pattern matching characters to match file names. Pattern matching is case-insensitive on Windows. For more information about supported pattern matching characters, see \"[Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet).\"" + }, + "contains": { + "description": "`contains( search, item )`\n\nReturns `true` if `search` contains `item`. If `search` is an array, this function returns `true` if the `item` is an element in the array. If `search` is a string, this function returns `true` if the `item` is a substring of `search`. This function is not case sensitive. Casts values to a string." + }, + "endswith": { + "description": "`endsWith( searchString, searchValue )`\n\nReturns `true` if `searchString` ends with `searchValue`. This function is not case sensitive. Casts values to a string." + }, + "format": { + "description": "`format( string, replaceValue0, replaceValue1, ..., replaceValueN)`\n\nReplaces values in the `string`, with the variable `replaceValueN`. Variables in the `string` are specified using the `{N}` syntax, where `N` is an integer. You must specify at least one `replaceValue` and `string`. There is no maximum for the number of variables (`replaceValueN`) you can use. Escape curly braces using double braces." + }, + "fromjson": { + "description": "`fromJSON(value)`\n\nReturns a JSON object or JSON data type for `value`. You can use this function to provide a JSON object as an evaluated expression or to convert environment variables from a string." + }, + "join": { + "description": "`join( array, optionalSeparator )`\n\nThe value for `array` can be an array or a string. All values in `array` are concatenated into a string. If you provide `optionalSeparator`, it is inserted between the concatenated values. Otherwise, the default separator `,` is used. Casts values to a string." + }, + "startswith": { + "description": "`startsWith( searchString, searchValue )`\n\nReturns `true` when `searchString` starts with `searchValue`. This function is not case sensitive. Casts values to a string." + }, + "tojson": { + "description": "`toJSON(value)`\n\nReturns a pretty-print JSON representation of `value`. You can use this function to debug the information provided in contexts." + } + }, + "github": { + "action": { + "description": "The name of the action currently running, or the [`id`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsid) of a step. GitHub removes special characters, and uses the name `__run` when the current step runs a script without an `id`. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name `__run`, and the second script will be named `__run_2`. Similarly, the second invocation of `actions/checkout` will be `actionscheckout2`." + }, + "action_path": { + "description": "The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action, for example by changing directories to the path: `cd ${{ github.action_path }}`." + }, + "action_ref": { + "description": "For a step executing an action, this is the ref of the action being executed. For example, `v2`." + }, + "action_repository": { + "description": "For a step executing an action, this is the owner and repository name of the action. For example, `actions/checkout`." + }, + "action_status": { + "description": "For a composite action, the current result of the composite action." + }, + "actor": { + "description": "The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from `github.triggering_actor`. Any workflow re-runs will use the privileges of `github.actor`, even if the actor initiating the re-run (`github.triggering_actor`) has different privileges." + }, + "actor_id": { + "description": "The account ID of the person or app that triggered the initial workflow run. For example, `1234567`. Note that this is different from the actor username.", + "versions": { + "ghes": ">=3.9", + "ghae": ">=3.9" + } + }, + "api_url": { + "description": "The URL of the GitHub REST API." + }, + "base_ref": { + "description": "The `base_ref` or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either `pull_request` or `pull_request_target`." + }, + "env": { + "description": "Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see \"[Workflow commands for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable).\"" + }, + "event": { + "description": "The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in \"[Events that trigger workflows](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows).\" For example, for a workflow run triggered by the [`push` event](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows#push), this object contains the contents of the [push webhook payload](https://docs.github.com/webhooks-and-events/webhooks/webhook-events-and-payloads#push)." + }, + "event_name": { + "description": "The name of the event that triggered the workflow run." + }, + "event_path": { + "description": "The path to the file on the runner that contains the full event webhook payload." + }, + "graphql_url": { + "description": "The URL of the GitHub GraphQL API." + }, + "head_ref": { + "description": "The `head_ref` or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either `pull_request` or `pull_request_target`." + }, + "job": { + "description": "The [`job_id`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_id) of the current job.\nNote: This context property is set by the Actions runner, and is only available within the execution `steps` of a job. Otherwise, the value of this property will be `null`." + }, + "job_workflow_sha": { + "description": "For jobs using a reusable workflow, the commit SHA for the reusable workflow file.", + "versions": { + "ghes": ">=3.9", + "ghae": ">=3.9" + } + }, + "path": { + "description": "Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see \"[Workflow commands for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path).\"" + }, + "ref": { + "description": "The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by `push`, this is the branch or tag ref that was pushed. For workflows triggered by `pull_request`, this is the pull request merge branch. For workflows triggered by `release`, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is `refs/heads/`, for pull requests it is `refs/pull//merge`, and for tags it is `refs/tags/`. For example, `refs/heads/feature-branch-1`." + }, + "ref_name": { + "description": "The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, `feature-branch-1`." + }, + "ref_protected": { + "description": "`true` if branch protections are configured for the ref that triggered the workflow run." + }, + "ref_type": { + "description": "The type of ref that triggered the workflow run. Valid values are `branch` or `tag`." + }, + "repository": { + "description": "The owner and repository name. For example, `octocat/Hello-World`." + }, + "repository_id": { + "description": "The ID of the repository. For example, `123456789`. Note that this is different from the repository name.", + "versions": { + "ghes": ">=3.9", + "ghae": ">=3.9" + } + }, + "repository_owner": { + "description": "The repository owner's username. For example, `octocat`." + }, + "repository_owner_id": { + "description": "The repository owner's account ID. For example, `1234567`. Note that this is different from the owner's name.", + "versions": { + "ghes": ">=3.9", + "ghae": ">=3.9" + } + }, + "repositoryUrl": { + "description": "The Git URL to the repository. For example, `git://github.com/octocat/hello-world.git`." + }, + "retention_days": { + "description": "The number of days that workflow run logs and artifacts are kept." + }, + "run_id": { + "description": "A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run." + }, + "run_number": { + "description": "A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run." + }, + "run_attempt": { + "description": "A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run." + }, + "secret_source": { + "description": "The source of a secret used in a workflow. Possible values are `None`, `Actions`, `Dependabot`, or `Codespaces`." + }, + "server_url": { + "description": "The URL of the GitHub server. For example: `https://github.com`." + }, + "sha": { + "description": "The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see \"[Events that trigger workflows](https://docs.github.com/actions/using-workflows/events-that-trigger-workflows).\" For example, `ffac537e6cbbf934b08745a378932722df287a53`." + }, + "token": { + "description": "A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the `GITHUB_TOKEN` secret. For more information, see \"[Automatic token authentication](https://docs.github.com/actions/security-guides/automatic-token-authentication).\"\nNote: This context property is set by the Actions runner, and is only available within the execution `steps` of a job. Otherwise, the value of this property will be `null`." + }, + "triggering_actor": { + "description": "The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from `github.actor`. Any workflow re-runs will use the privileges of `github.actor`, even if the actor initiating the re-run (`github.triggering_actor`) has different privileges." + }, + "workflow": { + "description": "The name of the workflow. If the workflow file doesn't specify a `name`, the value of this property is the full path of the workflow file in the repository." + }, + "workflow_ref": { + "description": "The ref path to the workflow. For example, `octocat/hello-world/.github/workflows/my-workflow.yml@refs/heads/my_branch`.", + "versions": { + "ghes": ">=3.9", + "ghae": ">=3.9" + } + }, + "workflow_sha": { + "description": "The commit SHA for the workflow file.", + "versions": { + "ghes": ">=3.9", + "ghae": ">=3.9" + } + }, + "workspace": { + "description": "The default working directory on the runner for steps, and the default location of your repository when using the [`checkout`](https://github.com/actions/checkout) action." + } + }, + "secrets": { + "GITHUB_TOKEN": { + "description": "Automatically created token for each workflow run. For more information, see \"[Automatic token authentication](https://docs.github.com/actions/security-guides/automatic-token-authentication).\"" + } + }, + "jobs": { + "outputs": { + "description": "The set of outputs of a job in a reusable workflow." + }, + "result": { + "description": "The result of a job in the reusable workflow. Possible values are `success`, `failure`, `cancelled`, or `skipped`." + } + }, + "steps": { + "outputs": { + "description": "The set of outputs defined for the step. For more information, see \"[Metadata syntax for GitHub Actions](https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#outputs-for-docker-container-and-javascript-actions).\"" + }, + "conclusion": { + "description": "The result of a completed step after [`continue-on-error`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error) is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`." + }, + "outcome": { + "description": "The result of a completed step before [`continue-on-error`](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error) is applied. Possible values are `success`, `failure`, `cancelled`, or `skipped`. When a `continue-on-error` step fails, the `outcome` is `failure`, but the final conclusion is `success`." + } + }, + "runner": { + "name": { + "description": "The name of the runner executing the job." + }, + "os": { + "description": "The operating system of the runner executing the job. Possible values are `Linux`, `Windows`, or `macOS`." + }, + "arch": { + "description": "The architecture of the runner executing the job. Possible values are `X86`, `X64`, `ARM`, or `ARM64`." + }, + "temp": { + "description": "The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner's user account does not have permission to delete them." + }, + "tool_cache": { + "description": "The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see \"[About GitHub-hosted runners](https://docs.github.com/actions/reference/specifications-for-github-hosted-runners/#supported-software).\"" + }, + "debug": { + "description": "This is set only if [debug logging](https://docs.github.com/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging) is enabled, and always has the value of `1`. It can be useful as an indicator to enable additional debugging or verbose logging in your own job steps." + } + }, + "strategy": { + "fail-fast": { + "description": "When `true`, all in-progress jobs are canceled if any job in a matrix fails. For more information, see \"[Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast).\"" + }, + "job-index": { + "description": "The index of the current job in the matrix. **Note:** This number is a zero-based number. The first job's index in the matrix is `0`." + }, + "job-total": { + "description": "The total number of jobs in the matrix. **Note:** This number **is not** a zero-based number. For example, for a matrix with four jobs, the value of `job-total` is `4`." + }, + "max-parallel": { + "description": "The maximum number of jobs that can run simultaneously when using a matrix job strategy. For more information, see \"[Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymax-parallel).\"" + } + } +} \ No newline at end of file diff --git a/src/Sdk/AzurePipelines/AutoCompleteHelper.cs b/src/Sdk/AzurePipelines/AutoCompleteHelper.cs new file mode 100644 index 00000000000..cdd7ec9c05d --- /dev/null +++ b/src/Sdk/AzurePipelines/AutoCompleteHelper.cs @@ -0,0 +1,959 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +using GitHub.DistributedTask.ObjectTemplating; + +using GitHub.DistributedTask.ObjectTemplating.Schema; +using GitHub.DistributedTask.ObjectTemplating.Tokens; + +using Sdk.Actions; + +namespace Runner.Server.Azure.Devops +{ + public class AutoCompletetionHelper { + internal static IEnumerable AddSuggestion(Context context, int column, int row, TemplateSchema schema, AutoCompleteEntry bestMatch, Definition? def, DefinitionType[]? allowed, bool flowStyle) + { + // if(allowed != null && !allowed.Contains(def.DefinitionType)) { + // yield break; + // } + if(bestMatch.Tokens != null) { + var validTokens = new [] { + GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Separator, + GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Function, + GitHub.DistributedTask.Expressions2.Tokens.TokenKind.NamedValue, + GitHub.DistributedTask.Expressions2.Tokens.TokenKind.StartGroup, + GitHub.DistributedTask.Expressions2.Tokens.TokenKind.StartIndex, + GitHub.DistributedTask.Expressions2.Tokens.TokenKind.StartParameters, + GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Separator, + }; + if(/*bestMatch.Tokens.Count == 0 || validTokens.Contains(bestMatch.Tokens.Last().Kind)*/ true) { + + var desc = ActionsDescriptions.LoadDescriptions(); + var i = bestMatch.Tokens.FindLastIndex(t => t.Index <= bestMatch.Index); + var last = bestMatch; + if(i >= 0 && last.Tokens[i].Kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Dereference) { + i++; + } + if(i >= 2 && (i >= last.Tokens.Count || last.Tokens[i].Index + last.Tokens[i].RawValue.Length >= bestMatch.Index) && last.Tokens[i - 2].Kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.NamedValue && last.Tokens[i - 1].Kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Dereference && new [] { "github", "runner", "strategy" }.Contains(last.Tokens[i - 2].RawValue.ToLower())) { + foreach(var k in desc[last.Tokens[i - 2].RawValue]) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = k.Key, + }, + Kind = 5, + Documentation = new MarkdownString { + Value = k.Value.Description + }, + Range = i < last.Tokens.Count && last.Tokens[i].Index <= bestMatch.Index ? new InsertReplaceRange { + Replacing = new Range { + Start = new Position { + Line = row - 1, + Character = column - (bestMatch.Index - last.Tokens[i].Index) - 1 + }, + End = new Position { + Line = row - 1, + Character = column - (bestMatch.Index - last.Tokens[i].Index) + last.Tokens[i].RawValue.Length - 1 + } + }, + Inserting = new Range { + Start = new Position { + Line = row - 1, + Character = column - (bestMatch.Index - last.Tokens[i].Index) - 1 + }, + End = new Position { + Line = row - 1, + Character = column - 1 + } + } + } : null + }; + } + } + else + { + foreach(var k in bestMatch.AllowedContext) { + if(k.Contains("(")) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = k.Substring(0, k.IndexOf("(")), + }, + InsertText = new SnippedString { + Value = $"{k.Substring(0, k.IndexOf("("))}($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = desc["functions"].TryGetValue(k, out var d) ? d.Description : "**Additional func** Item" + } + }; + } else { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = k, + }, + Kind = 5, + Documentation = new MarkdownString { + Value = desc["root"].TryGetValue(k, out var d) ? d.Description : "**Context** Item" + } + }; + } + } + } + var adoFunctions = (context.Flags & GitHub.DistributedTask.Expressions2.ExpressionFlags.ExtendedFunctions) != GitHub.DistributedTask.Expressions2.ExpressionFlags.None + || (context.Flags & GitHub.DistributedTask.Expressions2.ExpressionFlags.DTExpressionsV1) != GitHub.DistributedTask.Expressions2.ExpressionFlags.None; + if(bestMatch.AllowedContext.Length > 0 && adoFunctions) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "True", + }, + Kind = 5, + Documentation = new MarkdownString { + Value = "Boolean Literal" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "False", + }, + Kind = 5, + Documentation = new MarkdownString { + Value = "Boolean Literal" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "and", + Detail = "(lhs, rhs...)" + }, + InsertText = new SnippedString { + Value = "and($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates to True if all parameters are True +Min parameters: 2. Max parameters: N +Casts parameters to Boolean for evaluation +Short-circuits after first False +Example: `and(eq(variables.letters, 'ABC'), eq(variables.numbers, 123))` +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "coalesce", + Detail = "(lhs, rhs...)" + }, + InsertText = new SnippedString { + Value = "coalesce($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates the parameters in order (left to right), and returns the first value that doesn't equal null or empty-string. +No value is returned if the parameter values all are null or empty strings. +Min parameters: 2. Max parameters: N +Example: `coalesce(variables.couldBeNull, variables.couldAlsoBeNull, 'literal so it always works')` +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "contains", + Detail = "(lhs, rhs...)" + }, + InsertText = new SnippedString { + Value = "contains($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter String contains right parameter +Min parameters: 2. Max parameters: 2 +Casts parameters to String for evaluation +Performs ordinal ignore-case comparison +Example: `contains('ABCDE', 'BCD')` (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "containsValue", + Detail = "(lhs, rhs...)" + }, + InsertText = new SnippedString { + Value = "containsValue($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if the left parameter is an array, and any item equals the right parameter. Also evaluates True if the left parameter is an object, and the value of any property equals the right parameter. +Min parameters: 2. Max parameters: 2 +If the left parameter is an array, convert each item to match the type of the right parameter. If the left parameter is an object, convert the value of each property to match the type of the right parameter. The equality comparison for each specific item evaluates False if the conversion fails. +Ordinal ignore-case comparison for Strings +Short-circuits after the first match +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "convertToJson", + Detail = "(lhs)" + }, + InsertText = new SnippedString { + Value = "convertToJson($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Take a complex object and outputs it as JSON. +Min parameters: 1. Max parameters: 1. +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "endsWith", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "endsWith($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter String ends with right parameter +Min parameters: 2. Max parameters: 2 +Casts parameters to String for evaluation +Performs ordinal ignore-case comparison +Example: `endsWith('ABCDE', 'DE')` (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "eq", + Detail = "(lhs, rhs)" + }, + Kind = 2, + InsertText = new SnippedString { + Value = "eq($1, $2)" + }, + Documentation = new MarkdownString { + Value = "Compares two objects for equality. _This operation is case insensitive_." + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "format", + Detail = "(fmt...)" + }, + Kind = 2, + InsertText = new SnippedString { + Value = "format($1, $2)" + }, + Documentation = new MarkdownString { + Value = "Formats the string according to the fmt string placeholder `{0}` are get replaced by the additional parameters." + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "ge", + Detail = "(lhs, rhs)" + }, + Kind = 2, + InsertText = new SnippedString { + Value = "ge($1, $2)" + }, + Documentation = new MarkdownString { + Value = "Compares two objects for ge. _This operation is case insensitive_." + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "gt", + Detail = "(lhs, rhs)" + }, + Kind = 2, + InsertText = new SnippedString { + Value = "gt($1, $2)" + }, + Documentation = new MarkdownString { + Value = "Compares two objects for gt. _This operation is case insensitive_." + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "in", + Detail = "(lhs...)" + }, + InsertText = new SnippedString { + Value = "in($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter is equal to any right parameter +Min parameters: 1. Max parameters: N +Converts right parameters to match type of left parameter. Equality comparison evaluates False if conversion fails. +Ordinal ignore-case comparison for Strings +Short-circuits after first match +Example: in('B', 'A', 'B', 'C') (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "join", + Detail = "(sep, array)" + }, + InsertText = new SnippedString { + Value = "join($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Concatenates all elements in the right parameter array, separated by the left parameter string. +Min parameters: 2. Max parameters: 2 +Each element in the array is converted to a string. Complex objects are converted to empty string. +If the right parameter isn't an array, the result is the right parameter converted to a string. +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "le", + Detail = "(lhs, rhs)" + }, + Kind = 2, + InsertText = new SnippedString { + Value = "le($1, $2)" + }, + Documentation = new MarkdownString { + Value = "Compares two objects for less or equal. _This operation is case insensitive_." + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "length", + Detail = "(str)" + }, + InsertText = new SnippedString { + Value = "length($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Returns the length of a string or an array, either one that comes from the system or that comes from a parameter +Min parameters: 1. Max parameters 1 +Example: length('fabrikam') returns 8 +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "lower", + Detail = "(str)" + }, + InsertText = new SnippedString { + Value = "lower($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Converts a string or variable value to all lowercase characters +Min parameters: 1. Max parameters 1 +Returns the lowercase equivalent of a string +Example: lower('FOO') returns foo +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "le", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "le($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter is less than the right parameter +Min parameters: 2. Max parameters: 2 +Converts right parameter to match type of left parameter. Errors if conversion fails. +Ordinal ignore-case comparison for Strings +Example: le(2, 5) (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "lt", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "lt($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter is less than the right parameter +Min parameters: 2. Max parameters: 2 +Converts right parameter to match type of left parameter. Errors if conversion fails. +Ordinal ignore-case comparison for Strings +Example: lt(2, 5) (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "ne", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "ne($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if parameters are not equal +Min parameters: 2. Max parameters: 2 +Converts right parameter to match type of left parameter. Returns True if conversion fails. +Ordinal ignore-case comparison for Strings +Example: ne(1, 2) (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "not", + Detail = "(lhs)" + }, + InsertText = new SnippedString { + Value = "not($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if parameter is False +Min parameters: 1. Max parameters: 1 +Converts value to Boolean for evaluation +Example: not(eq(1, 2)) (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "notIn", + Detail = "(lhs...)" + }, + InsertText = new SnippedString { + Value = "notIn($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter isn't equal to any right parameter +Min parameters: 1. Max parameters: N +Converts right parameters to match type of left parameter. Equality comparison evaluates False if conversion fails. +Ordinal ignore-case comparison for Strings +Short-circuits after first match +Example: notIn('D', 'A', 'B', 'C') (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "or", + Detail = "(lhs, rhs...)" + }, + InsertText = new SnippedString { + Value = "or($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if any parameter is True +Min parameters: 2. Max parameters: N +Casts parameters to Boolean for evaluation +Short-circuits after first True +Example: or(eq(1, 1), eq(2, 3)) (returns True, short-circuits) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "replace", + Detail = "(str, src, replacement)" + }, + InsertText = new SnippedString { + Value = "replace($1, $2, $3)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Returns a new string in which all instances of a string in the current instance are replaced with another string +Min parameters: 3. Max parameters: 3 +replace(a, b, c): returns a, with all instances of b replaced by c +Example: replace('https://www.tinfoilsecurity.com/saml/consume','https://www.tinfoilsecurity.com','http://server') (returns http://server/saml/consume) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "split", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "split($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Splits a string into substrings based on the specified delimiting characters +Min parameters: 2. Max parameters: 2 +The first parameter is the string to split +The second parameter is the delimiting characters +Returns an array of substrings. The array includes empty strings when the delimiting characters appear consecutively or at the end of the string +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "startsWith", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "startsWith($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter string starts with right parameter +Min parameters: 2. Max parameters: 2 +Casts parameters to String for evaluation +Performs ordinal ignore-case comparison +Example: startsWith('ABCDE', 'AB') (returns True) +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "upper", + Detail = "(str)" + }, + InsertText = new SnippedString { + Value = "upper($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Converts a string or variable value to all uppercase characters +Min parameters: 1. Max parameters 1 +Returns the uppercase equivalent of a string +Example: upper('bah') returns BAH +" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "xor", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "xor($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if exactly one parameter is True +Min parameters: 2. Max parameters: 2 +Casts parameters to Boolean for evaluation +Example: xor(True, False) (returns True) +" + } + }; + } + + if(bestMatch.AllowedContext.Length > 0 && !adoFunctions) { + var kind = bestMatch.Tokens?.LastOrDefault(t => t.Index < bestMatch.Index)?.Kind; + switch(kind) { + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndGroup: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndIndex: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndParameters: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Null: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Number: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.NamedValue: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.PropertyName: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.String: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Boolean: + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "==", + }, + Kind = 24, + Documentation = new MarkdownString { + Value = "Equals Operator" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "!=", + }, + Kind = 24, + Documentation = new MarkdownString { + Value = "Not Equals Operator" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "||", + }, + Kind = 24, + Documentation = new MarkdownString { + Value = "logical or / coalescence from left to right" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "&&", + }, + Kind = 24, + Documentation = new MarkdownString { + Value = "logical and, returns and evaluates right parameter if left is truthy" + } + }; + if(kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.NamedValue || kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.PropertyName || kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndIndex || kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndGroup || kind == GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndParameters ) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = ".", + }, + Kind = 24, + Documentation = new MarkdownString { + Value = "dereference returns null if property doesn't exist" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "[...]", + }, + InsertText = new SnippedString { + Value = "[$1]" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @"index access" + } + }; + } + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "join", + Detail = "(array [, sep])" + }, + InsertText = new SnippedString { + Value = "join($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +`join( array, optionalSeparator )` + +The value for `array` can be an array or a string. All values in `array` are concatenated into a string. If you provide `optionalSeparator`, it is inserted between the concatenated values. Otherwise, the default separator `,` is used. Casts values to a string. +" + } + }; + break; + } + switch(kind) { + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndGroup: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndIndex: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.EndParameters: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Null: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Number: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.NamedValue: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.PropertyName: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.String: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Boolean: + case GitHub.DistributedTask.Expressions2.Tokens.TokenKind.Dereference: + break; + default: + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "true", + }, + Kind = 21, + Documentation = new MarkdownString { + Value = "Boolean Literal" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "false", + }, + Kind = 21, + Documentation = new MarkdownString { + Value = "Boolean Literal" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "!", + }, + Kind = 24, + Documentation = new MarkdownString { + Value = "logical not" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "(...)", + }, + InsertText = new SnippedString { + Value = "($1)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @"logical group" + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "startsWith", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "startsWith($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter string starts with right parameter +Min parameters: 2. Max parameters: 2 +Casts parameters to String for evaluation +Performs ordinal ignore-case comparison +Example: startsWith('ABCDE', 'AB') (returns True) + " + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "endsWith", + Detail = "(lhs, rhs)" + }, + InsertText = new SnippedString { + Value = "endsWith($1, $2)" + }, + Kind = 2, + Documentation = new MarkdownString { + Value = @" +Evaluates True if left parameter String ends with right parameter +Min parameters: 2. Max parameters: 2 +Casts parameters to String for evaluation +Performs ordinal ignore-case comparison +Example: `endsWith('ABCDE', 'DE')` (returns True) + " + } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "format", + Detail = "(fmt...)" + }, + Kind = 2, + InsertText = new SnippedString { + Value = "format($1, $2)" + }, + Documentation = new MarkdownString { + Value = "Formats the string according to the fmt string placeholder `{0}` are get replaced by the additional parameters." + } + }; + break; + } + } + } + + yield break; + } + if(def is MappingDefinition mapping && (bestMatch.Token is StringToken stkn && stkn.Value == "" || bestMatch.Token is NullToken || bestMatch.Token is MappingToken)) + { + if((flowStyle || row == bestMatch.Token.Line && context.AutoCompleteMatches.LastOrDefault(m => m != bestMatch)?.Token is MappingToken) && !(bestMatch.Token is MappingToken)) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "{}", + }, + InsertText = new SnippedString { Value = "{$1}" } + }; + yield break; + } + var candidates = mapping.Properties.Where(p => (bestMatch.Token as MappingToken)?.FirstOrDefault(e => e.Key?.ToString() == p.Key).Key == null); + var hasFirstProperties = candidates.Any(c => c.Value.FirstProperty); + foreach(var (k, desc) in candidates.Where(c => !hasFirstProperties || c.Value.FirstProperty).Select(p => { + var nested = schema.GetDefinition(p.Value.Type); + return (p.Key, p.Value.Description ?? nested?.Description); + })) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = k, + }, + InsertText = new SnippedString { Value = k + ":$0" }, + Documentation = desc == null ? null : new MarkdownString { + Value = desc + } + }; + } + if(bestMatch.AllowedContext?.Length > 0) { + var adoDirectives = (context.Flags & GitHub.DistributedTask.Expressions2.ExpressionFlags.ExtendedDirectives) != GitHub.DistributedTask.Expressions2.ExpressionFlags.None; + if(!flowStyle) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ insert }}", + }, + InsertText = new SnippedString { Value = "${{ insert }}:$0" } + }; + if(adoDirectives) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ if _ }}", + }, + InsertText = new SnippedString { Value = "${{ if $1 }}:$0" } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ elseif _ }}", + }, + InsertText = new SnippedString { Value = "${{ elseif $1 }}:$0" } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ else }}", + }, + InsertText = new SnippedString { Value = "${{ else }}:$0" } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ each _ in _ }}", + }, + InsertText = new SnippedString { Value = "${{ each $1 in $2 }}:$0" } + }; + } + } else { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ insert }}", + }, + InsertText = new SnippedString { Value = "\"${{ insert }}\":$0" } + }; + if(adoDirectives) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ if _ }}", + }, + InsertText = new SnippedString { Value = "\"${{ if $1 }}\":$0" } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ elseif _ }}", + }, + InsertText = new SnippedString { Value = "\"${{ elseif $1 }}\":$0" } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ else }}", + }, + InsertText = new SnippedString { Value = "\"${{ else }}\":$0" } + }; + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "${{ each _ in _ }}", + }, + InsertText = new SnippedString { Value = "\"${{ each $1 in $2 }}\":$0" } + }; + } + } + } + } + if(def is SequenceDefinition sequence && (bestMatch.Token is StringToken stkn2 && stkn2.Value == "" || bestMatch.Token is SequenceToken)) + { + if(flowStyle || row == bestMatch.Token.Line && !(bestMatch.Token is SequenceToken)) { + if(bestMatch.Token is SequenceToken) { + var item = schema.GetDefinition(sequence.ItemType); + if(schema.Get(item).Any()) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "{}", + }, + InsertText = new SnippedString { Value = "{$1}" } + }; + } + if(schema.Get(item).Any()) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "[]", + }, + InsertText = new SnippedString { Value = "[$1]" } + }; + } + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = ",", + } + }; + } else { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "[]", + }, + InsertText = new SnippedString { Value = "[$1]" } + }; + } + } else { + + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = "- ", + }, + InsertText = new SnippedString { Value = "- " } + }; + } + } + if(def is StringDefinition str && bestMatch.Token is ScalarToken) + { + if(str.Constant != null) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = str.Constant, + }, + Kind = 11 + }; + } + if(str.Pattern != null && Regex.IsMatch(str.Pattern, "^\\^[^\\\\\\+\\[\\]\\*\\{\\}\\.]+\\$$")) { + yield return new CompletionItem { + Label = new CompletionItemLabel { + Label = str.Pattern.Substring(1, str.Pattern.Length - 2), + }, + Kind = 11 + }; + } + } + if(def is OneOfDefinition oneOf) { + foreach(var k in oneOf.OneOf) { + var d = schema.GetDefinition(k); + foreach(var u in AddSuggestion(context, column, row, schema, bestMatch, d, allowed, flowStyle)) { + yield return u; + } + } + } + } + + public static List CollectCompletions(int column, int row, Context context, TemplateSchema schema) + { + var src = context.AutoCompleteMatches.Any(a => a.Token.Column == column) ? context.AutoCompleteMatches.Where(a => a.Token.Column == column) : context.AutoCompleteMatches.Where(a => a.Token.Column == context.AutoCompleteMatches.Last().Token.Column); + List list = src + .SelectMany(bestMatch => bestMatch.Definitions.SelectMany(def => AddSuggestion(context, column, row, schema, bestMatch, def, bestMatch.Token.Line <= row && bestMatch.Token.Column <= column && !(bestMatch.Token is ScalarToken) ? null : bestMatch.Token.Line < row ? new[] { DefinitionType.OneOf, DefinitionType.Mapping, DefinitionType.Sequence } : new[] { DefinitionType.OneOf, DefinitionType.Null, DefinitionType.Boolean, DefinitionType.Number, DefinitionType.String }, context.AutoCompleteMatches.TakeWhile(m => m != bestMatch).Append(bestMatch).Any(m => (m.Token.Type == TokenType.Sequence || m.Token.Type == TokenType.Mapping) && m.Token.PreWhiteSpace == null)))).DistinctBy(k => k.Label.Label).ToList(); + return list; + } + } + +} \ No newline at end of file diff --git a/src/Sdk/AzurePipelines/AzureDevops.cs b/src/Sdk/AzurePipelines/AzureDevops.cs index 692dc3cbd1a..3da2e883ab7 100644 --- a/src/Sdk/AzurePipelines/AzureDevops.cs +++ b/src/Sdk/AzurePipelines/AzureDevops.cs @@ -8,6 +8,7 @@ using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2.Sdk; using GitHub.DistributedTask.Expressions2.Sdk.Functions; +using GitHub.DistributedTask.Expressions2.Tokens; using GitHub.DistributedTask.ObjectTemplating; using GitHub.DistributedTask.ObjectTemplating.Schema; using GitHub.DistributedTask.ObjectTemplating.Tokens; @@ -21,7 +22,7 @@ namespace Runner.Server.Azure.Devops { public class AzureDevops { - private static TemplateSchema LoadSchema() { + public static TemplateSchema LoadSchema() { var assembly = Assembly.GetExecutingAssembly(); var json = default(String); using (var stream = assembly.GetManifestResourceStream("azurepiplines.json")) @@ -628,6 +629,154 @@ public static string RelativeTo(string cwd, string filename) { return string.Join('/', path.ToArray()); } + private static ((int, int)[], Dictionary<(int, int), int>) CreateIdxMapping(TemplateToken token) { + if(token is LiteralToken lit && lit.RawData != null) { + var rand = new Random(); + string C = "CX"; + while(lit.RawData.Contains(C)) { + C = rand.Next(255).ToString("X2"); + } + var praw = lit.ToString(); + (int, int)[] mapping = new (int, int)[praw.Length + 1]; + var rmapping = new Dictionary<(int, int), int>(); + Array.Fill(mapping, (-1, -1)); + + int column = lit.Column.Value; + int line = lit.Line.Value; + int ridx = -1; + for(int idx = 0; idx < lit.RawData.Length; idx++) { + if(lit.RawData[idx] == '\n') { + line++; + column = 1; + continue; + } + var xraw = lit.RawData.Insert(idx, C); + + var scanner = new YamlDotNet.Core.Scanner(new StringReader(xraw), true); + try { + while(scanner.MoveNext()) { + if(scanner.Current is YamlDotNet.Core.Tokens.Scalar s) { + var x = s.Value; + var m = x.IndexOf(C); + if(m >= 0 && m < mapping.Length && ridx <= m) { + if(mapping[m] != (-1,-1)) { + rmapping.Remove(mapping[m]); + } + mapping[m] = (line, column); + rmapping[(line, column)] = m; + ridx = m; + } + } + } + } catch { + + } + column++; + } + return (mapping, rmapping); + } + return (null, null); + } + + private static void SyntaxHighlightExpression(TemplateContext m_context, TemplateToken token, int offset, string value, int poffset = 0) { + var (mapping, rmapping) = CreateIdxMapping(token); + LexicalAnalyzer lexicalAnalyzer = new LexicalAnalyzer(value.Substring(offset), m_context.Flags); + Token tkn = null; + var startIndex = offset; + var lit = token as LiteralToken; + if(lit.RawData != null) { + while(lexicalAnalyzer.TryGetNextToken(ref tkn)) { + var (r, c) = mapping[startIndex + tkn.Index]; + if(tkn.Kind == TokenKind.Function) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 2, 2); + } else if(tkn.Kind == TokenKind.NamedValue) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 0, 1); + } else if(tkn.Kind == TokenKind.PropertyName) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 3, 0); + } else if(tkn.Kind == TokenKind.Boolean || tkn.Kind == TokenKind.Null) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 4, 2); + } else if(tkn.Kind == TokenKind.Number || tkn.Kind == TokenKind.String && tkn.ParsedValue is VersionWrapper) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 4, 4); + } else if(tkn.Kind == TokenKind.StartGroup || tkn.Kind == TokenKind.StartIndex || tkn.Kind == TokenKind.StartParameters || tkn.Kind == TokenKind.EndGroup || tkn.Kind == TokenKind.EndParameters + || tkn.Kind == TokenKind.EndIndex || tkn.Kind == TokenKind.Wildcard || tkn.Kind == TokenKind.Separator || tkn.Kind == TokenKind.LogicalOperator || tkn.Kind == TokenKind.Dereference) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 5, 0); + } else if(tkn.Kind == TokenKind.String) { + var (er, ec) = mapping[startIndex + tkn.Index + tkn.RawValue.Length]; + // Only add single line string + if(er == r && c < ec) { + m_context.AddSemToken(r, c, ec - c /* May contain escape codes */, 6, 0); + } + // TODO multi line string by splitting them + } + } + } + } + + private static bool AutoCompleteExpression(TemplateContext m_context, AutoCompleteEntry completion, int offset, string value, int poffset = 0) { + if(completion != null && m_context.AutoCompleteMatches != null) { + var idx = GetIdxOfExpression(completion.Token as LiteralToken, m_context.Row.Value, m_context.Column.Value); + var startIndex = -1 - completion.Index + offset; + if(idx == -1 && completion.Token.PreWhiteSpace != null && (m_context.Row.Value > completion.Token.PreWhiteSpace.Line || m_context.Row.Value == completion.Token.PreWhiteSpace.Line && m_context.Column.Value >= completion.Token.PostWhiteSpace.Character)) { + idx = startIndex; + } + if(idx == -1 && completion.Token.PostWhiteSpace != null && (m_context.Row.Value < completion.Token.PostWhiteSpace.Line || m_context.Row.Value == completion.Token.PostWhiteSpace.Line && m_context.Column.Value <= completion.Token.PostWhiteSpace.Character)) { + idx = startIndex + value.Length + poffset; + } + if(idx != -1 && idx >= startIndex && (idx <= startIndex + value.Length + poffset)) { + LexicalAnalyzer lexicalAnalyzer = new LexicalAnalyzer(value, m_context.Flags); + Token tkn = null; + List tkns = new List(); + while(lexicalAnalyzer.TryGetNextToken(ref tkn)) { + tkns.Add(tkn); + if(tkn.Index + startIndex > idx) { + break; + } + } + completion.Tokens = tkns; + completion.Index = idx - startIndex; + } + } + return true; + } + + private static int GetIdxOfExpression(LiteralToken lit, int row, int column) + { + var lc = column - lit.Column; + var lr = row - lit.Line; + var rand = new Random(); + string C = "CX"; + while(lit.RawData.Contains(C)) { + C = rand.Next(255).ToString("X2"); + } + var xraw = lit.RawData; + var idx = 0; + for(int i = 0; i < lr; i++) { + var n = xraw.IndexOf('\n', idx); + if(n == -1) { + return -1; + } + idx = n + 1; + } + idx += idx == 0 ? lc ?? 0 : column - 1; + if(idx < 0 || idx > xraw.Length) { + return -1; + } + xraw = xraw.Insert(idx, C); + + var scanner = new YamlDotNet.Core.Scanner(new StringReader(xraw), true); + try { + while(scanner.MoveNext()) { + if(scanner.Current is YamlDotNet.Core.Tokens.Scalar s) { + var x = s.Value; + return x.IndexOf(C); + } + } + } catch { + + } + return -1; + } + public static async Task<(string, TemplateToken)> ParseTemplate(Context context, string filenameAndRef, string schemaName = null, bool checks = true) { @@ -653,16 +802,18 @@ public static string RelativeTo(string cwd, string filename) { context.FileTable ??= new List(); context.FileTable.Add(errorTemplateFileName); var templateContext = AzureDevops.CreateTemplateContext(context.TraceWriter ?? new EmptyTraceWriter(), context.FileTable, context.Flags); + if(context.Column != 0 && context.Row != 0) { + templateContext.AutoCompleteMatches = context.AutoCompleteMatches ??= new List(); + templateContext.Column = context.Column; + templateContext.Row = context.Row; + } var fileId = templateContext.GetFileId(errorTemplateFileName); - TemplateToken token; - using (var stringReader = new StringReader(fileContent)) - { - // preserveString is needed for azure pipelines compatability of the templateContext property all boolean and number token are casted to string without loosing it's exact string value - var yamlObjectReader = new YamlObjectReader(fileId, stringReader, preserveString: true, forceAzurePipelines: true); - token = TemplateReader.Read(templateContext, schemaName ?? "pipeline-root", yamlObjectReader, fileId, out _); - } + // preserveString is needed for azure pipelines compatibility of the templateContext property all boolean and number token are casted to string without loosing it's exact string value + var yamlObjectReader = new YamlObjectReader(fileId, fileContent, preserveString: true, forceAzurePipelines: true); + TemplateToken token = TemplateReader.Read(templateContext, schemaName ?? "pipeline-root", yamlObjectReader, fileId, out _); + context.SemTokens = templateContext.SemTokens; if(checks) { foreach (var stepCond in token.TraverseByPattern(new[] { "steps", "*", "condition" }) @@ -674,18 +825,18 @@ public static string RelativeTo(string cwd, string filename) { .Concat(token.TraverseByPattern(new[] { "stages", "*", "jobs", "*", "strategy", "on", "", "steps", "*", "condition" })) ) { - CheckConditionalExpressions(templateContext.Errors, stepCond, Level.Step); + CheckConditionalExpressions(templateContext, stepCond, Level.Step); } foreach (var jobCond in token.TraverseByPattern(new[] { "jobs", "*", "condition" }) .Concat(token.TraverseByPattern(new[] { "stages", "*", "jobs", "*", "condition" })) ) { - CheckConditionalExpressions(templateContext.Errors, jobCond, Level.Job); + CheckConditionalExpressions(templateContext, jobCond, Level.Job); } foreach (var stageCond in token.TraverseByPattern(new[] { "stages", "*", "condition" }) ) { - CheckConditionalExpressions(templateContext.Errors, stageCond, Level.Stage); + CheckConditionalExpressions(templateContext, stageCond, Level.Stage); } foreach (var runtimeExpr in token.TraverseByPattern(new[] { "variables", "*", "value" }) .Concat(token.TraverseByPattern(new[] { "variables", "" })) @@ -722,7 +873,7 @@ public static string RelativeTo(string cwd, string filename) { .Concat(token.TraverseByPattern(new[] { "jobs", "*", "strategy", "parallel" })) ) { - CheckSingleRuntimeExpression(templateContext.Errors, runtimeExpr); + CheckSingleRuntimeExpression(templateContext, runtimeExpr); } try @@ -824,6 +975,8 @@ static async Task processTemplates(TemplateContext templateContext, int fileId, } if (fdef != null && val != null) { + // var autoCompleteState = templateContext.AutoCompleteMatches; + // templateContext.AutoCompleteMatches = new List(); TemplateEvaluator.Evaluate(templateContext, fdef, val, 0, fileId); if(start != null) { foreach(var (tkn, sh) in GetPatterns(start.Value) @@ -831,16 +984,16 @@ static async Task processTemplates(TemplateContext templateContext, int fileId, { switch(sh) { case 0: - CheckConditionalExpressions(templateContext.Errors, tkn, Level.Step); + CheckConditionalExpressions(templateContext, tkn, Level.Step); break; case 1: - CheckConditionalExpressions(templateContext.Errors, tkn, Level.Job); + CheckConditionalExpressions(templateContext, tkn, Level.Job); break; case 2: - CheckConditionalExpressions(templateContext.Errors, tkn, Level.Stage); + CheckConditionalExpressions(templateContext, tkn, Level.Stage); break; case 3: - CheckSingleRuntimeExpression(templateContext.Errors, tkn); + CheckSingleRuntimeExpression(templateContext, tkn); break; } } @@ -969,16 +1122,16 @@ static async Task processParams(TemplateContext templateContext, int fileId, Con { switch(sh) { case 0: - CheckConditionalExpressions(templateContext.Errors, tkn, Level.Step); + CheckConditionalExpressions(templateContext, tkn, Level.Step); break; case 1: - CheckConditionalExpressions(templateContext.Errors, tkn, Level.Job); + CheckConditionalExpressions(templateContext, tkn, Level.Job); break; case 2: - CheckConditionalExpressions(templateContext.Errors, tkn, Level.Stage); + CheckConditionalExpressions(templateContext, tkn, Level.Stage); break; case 3: - CheckSingleRuntimeExpression(templateContext.Errors, tkn); + CheckSingleRuntimeExpression(templateContext, tkn); break; } } @@ -1092,9 +1245,10 @@ static async Task processParams(TemplateContext templateContext, int fileId, Con } - private static void CheckSingleRuntimeExpression(TemplateValidationErrors errors, TemplateToken rawVal) + private static void CheckSingleRuntimeExpression(TemplateContext m_context, TemplateToken rawVal) { - if(rawVal == null || rawVal.Type == TokenType.Null || !(rawVal is LiteralToken) || rawVal.Type == TokenType.BasicExpression) { + var autoComplete = m_context.AutoCompleteMatches?.Count > 0 && m_context.AutoCompleteMatches.Last().Token == rawVal; + if(rawVal == null || !autoComplete && rawVal.Type == TokenType.Null || !(rawVal is LiteralToken) || rawVal.Type == TokenType.BasicExpression) { return; } var val = rawVal.AssertLiteralString("runtime expression"); @@ -1103,12 +1257,22 @@ private static void CheckSingleRuntimeExpression(TemplateValidationErrors errors return; } try { + var pval = val.Substring(2, val.Length - 3); + var names = new[] { "variables", "resources", "pipeline", "dependencies", "stageDependencies" }; + if(autoComplete) { + var match = m_context.AutoCompleteMatches.Last(); + match.AllowedContext = names.Append("counter(0,2)").ToArray(); + AutoCompleteExpression(m_context, match, 2, pval); + } + // m_context.AddSemToken(rawVal.Line.Value, rawVal.Column.Value, 2, 5, 0); + SyntaxHighlightExpression(m_context, rawVal, 2, val); + // m_context.AddSemToken(rawVal.Line.Value, rawVal.Column.Value, 2, 5, 0); var parser = new ExpressionParser() { Flags = ExpressionFlags.DTExpressionsV1 | ExpressionFlags.ExtendedDirectives | ExpressionFlags.AllowAnyForInsert }; - var node = parser.CreateTree(val.Substring(2, val.Length - 3), new EmptyTraceWriter().ToExpressionTraceWriter(), - new[] { "variables", "resources", "pipeline", "dependencies", "stageDependencies" }.Select(n => new NamedValueInfo(n)), + var node = parser.CreateTree(pval, new EmptyTraceWriter().ToExpressionTraceWriter(), + names.Select(n => new NamedValueInfo(n)), ExpressionConstants.AzureWellKnownFunctions.Where(kv => kv.Key != "split").Select(kv => kv.Value).Append(new FunctionInfo("counter", 0, 2))); } catch (Exception ex) { - errors.Add($"{GitHub.DistributedTask.ObjectTemplating.Tokens.TemplateTokenExtensions.GetAssertPrefix(rawVal)}{ex.Message}"); + m_context.Errors.Add($"{GitHub.DistributedTask.ObjectTemplating.Tokens.TemplateTokenExtensions.GetAssertPrefix(rawVal)}{ex.Message}"); } } @@ -1118,10 +1282,10 @@ private enum Level { Step } - private static void CheckConditionalExpressions(TemplateValidationErrors errors, TemplateToken rawCondition, Level level) + private static void CheckConditionalExpressions(TemplateContext m_context, TemplateToken rawCondition, Level level) { - - if(rawCondition == null || rawCondition.Type == TokenType.Null || !(rawCondition is LiteralToken) || rawCondition.Type == TokenType.BasicExpression) { + var autoComplete = m_context.AutoCompleteMatches?.Count > 0 && m_context.AutoCompleteMatches.Last().Token == rawCondition; + if(rawCondition == null || !autoComplete && rawCondition.Type == TokenType.Null || !(rawCondition is LiteralToken) || rawCondition.Type == TokenType.BasicExpression) { return; } var val = rawCondition.AssertLiteralString("condition"); @@ -1143,12 +1307,22 @@ private static void CheckConditionalExpressions(TemplateValidationErrors errors, funcs.Add(new FunctionInfo("Succeeded", 0, Int32.MaxValue)); funcs.Add(new FunctionInfo("SucceededOrFailed", 0, Int32.MaxValue)); try { + if(autoComplete) { + var match = m_context.AutoCompleteMatches.Last(); + match.AllowedContext = names.Concat(funcs.Select(f => $"{f.Name}({f.MinParameters},{f.MaxParameters})")).ToArray(); + if(rawCondition.Type == TokenType.Null) { + match.Token = new NullToken(match.Token.FileId, match.Token.Line, match.Token.Column + 1, ""); + } + AutoCompleteExpression(m_context, match, rawCondition.Type == TokenType.Null ? 1 : 0, val); + } + SyntaxHighlightExpression(m_context, rawCondition, 0, val); + var parser = new ExpressionParser() { Flags = ExpressionFlags.DTExpressionsV1 | ExpressionFlags.ExtendedDirectives | ExpressionFlags.AllowAnyForInsert }; var node = parser.CreateTree(val, new EmptyTraceWriter().ToExpressionTraceWriter(), names.Select(n => new NamedValueInfo(n)), ExpressionConstants.AzureWellKnownFunctions.Where(kv => kv.Key != "split").Select(kv => kv.Value).Concat(funcs)); } catch (Exception ex) { - errors.Add($"{GitHub.DistributedTask.ObjectTemplating.Tokens.TemplateTokenExtensions.GetAssertPrefix(rawCondition)}{ex.Message}"); + m_context.Errors.Add($"{GitHub.DistributedTask.ObjectTemplating.Tokens.TemplateTokenExtensions.GetAssertPrefix(rawCondition)}{ex.Message}"); } } diff --git a/src/Sdk/AzurePipelines/CompletionItem.cs b/src/Sdk/AzurePipelines/CompletionItem.cs new file mode 100644 index 00000000000..815b30880ba --- /dev/null +++ b/src/Sdk/AzurePipelines/CompletionItem.cs @@ -0,0 +1,62 @@ +using System.Reflection.Emit; + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Runner.Server.Azure.Devops { + + [JsonObject("", NamingStrategyType = typeof(CamelCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)] + public class CompletionItemLabel { + public string Label { get; set; } + public string Description { get; set; } + public string Detail { get; set; } + } + [JsonObject("", NamingStrategyType = typeof(CamelCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)] + public class MarkdownString { + public string Value { get; set; } + public bool? SupportThemeIcons { get; set; } + public bool? SupportHtml { get; set; } + } + + [JsonObject("", NamingStrategyType = typeof(CamelCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)] + public class SnippedString { + public string Value { get; set; } + } + + [JsonObject("", NamingStrategyType = typeof(CamelCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)] + public class Position { + public long Character { get; set; } + public long Line { get; set; } + } + + [JsonObject("", NamingStrategyType = typeof(CamelCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)] + public class Range { + public Position Start { get; set; } + public Position End { get; set; } + public bool? IsEmpty { get; set; } + public bool? IsSingleLine { get; set; } + } + [JsonObject("", NamingStrategyType = typeof(CamelCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)] + public class InsertReplaceRange { + public Range Inserting { get; set; } + public Range Replacing { get; set; } + } + + [JsonObject("", NamingStrategyType = typeof(CamelCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)] + public class CompletionItem { + public CompletionItemLabel Label { get; set; } + public string FilterText { get; set; } + public SnippedString InsertText { get; set; } + public string SortText { get; set; } + public bool? Preselect { get; set; } + public string Detail { get; set; } + public string[] CommitCharacters { get; set; } + public bool? KeepWhitespace { get; set; } + public int? Kind { get; set; } + public InsertReplaceRange Range { get; set; } + public MarkdownString Documentation { get; set; } + + [JsonIgnore] + public int Priority { get; set; } = 0; + } +} \ No newline at end of file diff --git a/src/Sdk/AzurePipelines/Context.cs b/src/Sdk/AzurePipelines/Context.cs index 16e39882b64..a769e1d3ef4 100644 --- a/src/Sdk/AzurePipelines/Context.cs +++ b/src/Sdk/AzurePipelines/Context.cs @@ -1,4 +1,5 @@ using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.ObjectTemplating; using GitHub.DistributedTask.ObjectTemplating.Tokens; using System; using System.Collections.Generic; @@ -17,6 +18,10 @@ public class Context { public ITaskByNameAndVersionProvider TaskByNameAndVersion { get; set; } public IRequiredParametersProvider RequiredParametersProvider { get; set; } public List FileTable { get; set; } = new List(); + public int Column { get; internal set; } + public int Row { get; internal set; } + internal List AutoCompleteMatches { get; set; } + public List SemTokens { get; internal set; } public Context Clone() { return MemberwiseClone() as Context; @@ -28,6 +33,9 @@ public Context ChildContext(MappingToken template, string path = null) { } var childContext = Clone(); childContext.RequiredParametersProvider = null; + childContext.AutoCompleteMatches = null; + childContext.Column = 0; + childContext.Row = 0; foreach(var kv in template) { switch(kv.Key.AssertString("key").Value) { case "resources": diff --git a/src/Sdk/AzurePipelines/azurepiplines.json b/src/Sdk/AzurePipelines/azurepiplines.json index e5ec7ae4f5a..ed6158dbf43 100644 --- a/src/Sdk/AzurePipelines/azurepiplines.json +++ b/src/Sdk/AzurePipelines/azurepiplines.json @@ -138,37 +138,47 @@ "properties": { "stages": { "type": "stages", + "description": "Stages are groups of jobs that can run without human intervention", "required": true }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where jobs in this pipeline will run unless otherwise specified" }, "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -178,37 +188,47 @@ "properties": { "extends": { "type": "extends", + "description": "Extends a template", "required": true }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where jobs in this pipeline will run unless otherwise specified" }, "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -218,37 +238,47 @@ "properties": { "jobs": { "type": "jobs", + "description": "Jobs represent units of work which can be assigned to a single agent or server", "required": true }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where jobs in this pipeline will run unless otherwise specified" }, "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -258,34 +288,43 @@ "properties": { "phases": { "type": "phases", + "description": "Phases which make up the pipeline", "required": true }, "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -294,16 +333,20 @@ "mapping": { "properties": { "strategy": { - "type": "jobStrategy" + "type": "jobStrategy", + "description": "Execution strategy for this job" }, "continueOnError": { - "type": "jobContinueOnError" + "type": "jobContinueOnError", + "description": "Continue running even on failure?" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where this job will run" }, "container": { - "type": "jobContainer" + "type": "jobContainer", + "description": "Container resource name" }, "services": { "type": "jobServices" @@ -313,34 +356,43 @@ }, "steps": { "type": "steps", + "description": "A list of steps to run in this job", "required": true }, "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -349,41 +401,52 @@ "mapping": { "properties": { "continueOnError": { - "type": "jobContinueOnError" + "type": "jobContinueOnError", + "description": "Continue running even on failure?" }, "queue": { - "type": "phaseQueueTarget" + "type": "phaseQueueTarget", + "description": "Queue where this phase will run" }, "steps": { "type": "steps", + "description": "A list of steps to run in this phase", "required": true }, "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -392,41 +455,52 @@ "mapping": { "properties": { "continueOnError": { - "type": "jobContinueOnError" + "type": "jobContinueOnError", + "description": "Continue running even on failure?" }, "server": { - "type": "phaseServerTarget" + "type": "phaseServerTarget", + "description": "True if this is an agent-less phase (runs on server)" }, "steps": { "type": "steps", + "description": "A list of steps to run in this phase", "required": true }, "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -443,31 +517,39 @@ "mapping": { "properties": { "name": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "schedules": { "type": "schedules" }, "resources": { - "type": "resources" + "type": "resources", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Variables for this pipeline" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -476,52 +558,66 @@ "mapping": { "properties": { "trigger": { - "type": "trigger" + "type": "trigger", + "description": "Continuous integration triggers" }, "name": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Append the commit message to the build number" }, "parameters": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline template parameters" }, "pr": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pull request triggers" }, "schedules": { "type": "any_allowExpressions" }, "resources": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Variables for the entire pipeline" }, "stages": { "type": "any_allowExpressions" }, "jobs": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Jobs which make up the pipeline" }, "extends": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Extends a template" }, "phases": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Phases which make up the pipeline" }, "strategy": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Execution strategy for the job" }, "continueOnError": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continue running even on failure?" }, "pool": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pool where this job will run" }, "container": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Container resource name" }, "services": { "type": "any_allowExpressions" @@ -530,16 +626,20 @@ "type": "any_allowExpressions" }, "steps": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "A list of steps to run" }, "queue": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Queue where this phase will run" }, "server": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "True if this is an agent-less phase (runs on server)" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -548,52 +648,66 @@ "mapping": { "properties": { "parameters": { - "type": "pipelineTemplateParameters" + "type": "pipelineTemplateParameters", + "description": "Pipeline template parameters" }, "name": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continuous integration triggers" }, "pr": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pull request triggers" }, "schedules": { "type": "any_allowExpressions" }, "resources": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Variables for the entire pipeline" }, "stages": { "type": "any_allowExpressions" }, "jobs": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Jobs which make up the pipeline" }, "extends": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Extends a template" }, "phases": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Phases which make up the pipeline" }, "strategy": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Execution strategy for the job" }, "continueOnError": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continue running even on failure?" }, "pool": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pool where this job will run" }, "container": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Container resource name" }, "services": { "type": "any_allowExpressions" @@ -602,16 +716,20 @@ "type": "any_allowExpressions" }, "steps": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "A list of steps to run" }, "queue": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Queue where this phase will run" }, "server": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "True if this is an agent-less phase (runs on server)" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -620,52 +738,66 @@ "mapping": { "properties": { "pr": { - "type": "pr" + "type": "pr", + "description": "Pull request triggers" }, "name": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continuous integration triggers" }, "parameters": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline template parameters" }, "schedules": { "type": "any_allowExpressions" }, "resources": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Variables for the entire pipeline" }, "stages": { "type": "any_allowExpressions" }, "jobs": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Jobs which make up the pipeline" }, "extends": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Extends a template" }, "phases": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Phases which make up the pipeline" }, "strategy": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Execution strategy for the job" }, "continueOnError": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continue running even on failure?" }, "pool": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pool where this job will run" }, "container": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Container resource name" }, "services": { "type": "any_allowExpressions" @@ -674,16 +806,20 @@ "type": "any_allowExpressions" }, "steps": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "A list of steps to run" }, "queue": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Queue where this phase will run" }, "server": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "True if this is an agent-less phase (runs on server)" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -695,49 +831,63 @@ "type": "schedules" }, "name": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continuous integration triggers" }, "parameters": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline template parameters" }, "pr": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pull request triggers" }, "resources": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Variables for the entire pipeline" }, "stages": { "type": "any_allowExpressions" }, "jobs": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Jobs which make up the pipeline" }, "extends": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Extends a template" }, "phases": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Phases which make up the pipeline" }, "strategy": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Execution strategy for the job" }, "continueOnError": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continue running even on failure?" }, "pool": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pool where this job will run" }, "container": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Container resource name" }, "services": { "type": "any_allowExpressions" @@ -746,16 +896,20 @@ "type": "any_allowExpressions" }, "steps": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "A list of steps to run" }, "queue": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Queue where this phase will run" }, "server": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "True if this is an agent-less phase (runs on server)" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -764,52 +918,66 @@ "mapping": { "properties": { "name": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline name" }, "appendCommitMessageToRunName": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Append the commit message to the build number" }, "trigger": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continuous integration triggers" }, "parameters": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pipeline template parameters" }, "pr": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pull request triggers" }, "schedules": { "type": "any_allowExpressions" }, "resources": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Containers and repositories used in the build" }, "variables": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Variables for the entire pipeline" }, "stages": { "type": "any_allowExpressions" }, "jobs": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Jobs which make up the pipeline" }, "extends": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Extends a template" }, "phases": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Phases which make up the pipeline" }, "strategy": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Execution strategy for the job" }, "continueOnError": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Continue running even on failure?" }, "pool": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Pool where this job will run" }, "container": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Container resource name" }, "services": { "type": "any_allowExpressions" @@ -818,16 +986,20 @@ "type": "any_allowExpressions" }, "steps": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "A list of steps to run" }, "queue": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "Queue where this phase will run" }, "server": { - "type": "any_allowExpressions" + "type": "any_allowExpressions", + "description": "True if this is an agent-less phase (runs on server)" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" } } } @@ -846,7 +1018,8 @@ "mapping": { "properties": { "autoCancel": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Whether to cancel running PR builds when a new commit lands in the branch" }, "branches": { "type": "includeExcludeFilters" @@ -855,7 +1028,8 @@ "type": "includeExcludeFilters" }, "drafts": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Whether to start a run when a draft PR is created" } } } @@ -881,7 +1055,8 @@ "mapping": { "properties": { "batch": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Whether to batch changes per branch" }, "branches": { "type": "includeExcludeFilters" @@ -972,7 +1147,8 @@ "required": true }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the parameter" }, "type": { "type": "templateParameterType" @@ -1094,7 +1270,8 @@ "required": true }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the parameter" }, "type": { "type": "pipelineTemplateParameterType" @@ -1258,22 +1435,27 @@ "mapping": { "properties": { "builds": { - "type": "buildResources" + "type": "buildResources", + "description": "List of external build resources" }, "containers": { - "type": "containerResources" + "type": "containerResources", + "description": "List of container images" }, "pipelines": { "type": "pipelineResources" }, "repositories": { - "type": "repositoryResources" + "type": "repositoryResources", + "description": "List of external repositories" }, "webhooks": { - "type": "webhookResources" + "type": "webhookResources", + "description": "List of webhooks" }, "packages": { - "type": "packageResources" + "type": "packageResources", + "description": "List of external packages" } } } @@ -1299,19 +1481,23 @@ "properties": { "build": { "type": "referenceName", + "description": "Alias or name of build artifact", "first-property": true, "required": true }, "type": { "type": "nonEmptyString", + "description": "Name of the artifact type", "required": true }, "connection": { "type": "nonEmptyString", + "description": "Name of the connection. This connection will be used for all the communication related to this artifact.", "required": true }, "source": { "type": "nonEmptyString", + "description": "Name of the source definition/build/job", "required": true }, "version": { @@ -1321,7 +1507,8 @@ "type": "string_allowExpressions" }, "trigger": { - "type": "buildResourceTrigger" + "type": "buildResourceTrigger", + "description": "When the artifact mentioned in this build resource completes a build, its allowed to trigger this pipeline." } } } @@ -1352,19 +1539,23 @@ "properties": { "package": { "type": "referenceName", + "description": "Alias of package artifact", "first-property": true, "required": true }, "type": { "type": "nonEmptyString", + "description": "Type of the package. Ex - NuGet, NPM etc.", "required": true }, "connection": { "type": "nonEmptyString", + "description": "Name of the connection. This connection will be used for all the communication related to this artifact.", "required": true }, "name": { "type": "nonEmptyString", + "description": "Name of the package", "required": true }, "version": { @@ -1374,7 +1565,8 @@ "type": "string_allowExpressions" }, "trigger": { - "type": "packageResourceTrigger" + "type": "packageResourceTrigger", + "description": "Trigger a new pipeline run when a new version of this package is available." } } } @@ -1409,6 +1601,7 @@ "properties": { "container": { "type": "referenceName", + "description": "ID for the container", "first-property": true, "required": true }, @@ -1419,20 +1612,25 @@ "type": "containerResourceTrigger" }, "endpoint": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "ID of the service endpoint connecting to a private container registry" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the container's environment" }, "image": { "type": "azp-string", + "description": "Container image tag", "required": true }, "mapDockerSocket": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Set this flag to false to force the agent not to setup the /var/run/docker.sock volume on container jobs" }, "options": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Options to pass into container host" }, "ports": { "type": "sequenceOfString_allowExpressions" @@ -1501,6 +1699,7 @@ "properties": { "pipeline": { "type": "referenceName", + "description": "ID of the pipeline resource", "first-property": true, "required": true }, @@ -1593,11 +1792,13 @@ "properties": { "repository": { "type": "referenceName", + "description": "ID of the external repository", "first-property": true, "required": true }, "endpoint": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "ID of the service endpoint connecting to this repository" }, "trigger": { "type": "trigger" @@ -1613,30 +1814,50 @@ "loose-value-type": "any" } }, + "repositoryCheckoutOptions-clean": { + "description": "Scorch the repo before fetching?", + "allowed-values": [ + "true", + "false" + ] + }, "repositoryCheckoutOptions": { "mapping": { "properties": { "clean": { - "type": "azp-string" + "type": "repositoryCheckoutOptions-clean", + "description": "Scorch the repo before fetching?" }, "fetchDepth": { - "type": "azp-string" + "type": "azp-string", + "description": "Depth of Git graph to fetch" }, "fetchTags": { - "type": "azp-string" + "type": "azp-string", + "description": "Fetch tags?" }, "lfs": { - "type": "azp-string" + "type": "azp-string", + "description": "Fetch and checkout Git LFS objects?" }, "submodules": { - "type": "azp-string" + "type": "azp-string", + "description": "Fetch and checkout submodules?" }, "persistCredentials": { - "type": "azp-string" + "type": "azp-string", + "description": "Keep credentials available for later use?" } } } }, + "legacyResource-clean": { + "description": "Scorch the repo before fetching?", + "allowed-values": [ + "true", + "false" + ] + }, "legacyResource": { "mapping": { "properties": { @@ -1646,13 +1867,16 @@ "required": true }, "clean": { - "type": "azp-string" + "type": "legacyResource-clean", + "description": "Scorch the repo before fetching?" }, "fetchDepth": { - "type": "azp-string" + "type": "azp-string", + "description": "Depth of Git graph to fetch" }, "lfs": { - "type": "azp-string" + "type": "azp-string", + "description": "Fetch and checkout Git LFS objects?" } } } @@ -1672,18 +1896,22 @@ "properties": { "webhook": { "type": "referenceName", + "description": "Name of the webhook", "first-property": true, "required": true }, "connection": { "type": "nonEmptyString", + "description": "Name of the connection. In case of offline webhook this will be the type of Incoming Webhook otherwise it will be the type of the webhook extension.", "required": true }, "type": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Name of the webhook extension. leave this empty if its offline webhook." }, "filters": { - "type": "webhookFilters" + "type": "webhookFilters", + "description": "List of trigger filters." } } } @@ -1698,11 +1926,13 @@ "properties": { "path": { "type": "nonEmptyString", + "description": "json path to select data from event payload", "first-property": true, "required": true }, "value": { "type": "nonEmptyString", + "description": "Expected value for the filter to match", "required": true } } @@ -1817,35 +2047,45 @@ "properties": { "stage": { "type": "azp-string", + "description": "ID of the stage", "first-property": true, "required": true }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the stage" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where jobs in this stage will run unless otherwise specified" }, "dependsOn": { - "type": "jobDependsOn" + "type": "jobDependsOn", + "description": "Any stages which must complete before this one" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this stage" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Stage-specific variables" }, "jobs": { - "type": "jobs" + "type": "jobs", + "description": "Jobs which make up the stage" }, "lockBehavior": { - "type": "lockBehavior" + "type": "lockBehavior", + "description": "Behavior lock requests from this stage should exhibit in relation to other exclusive lock requests" }, "trigger": { - "type": "stageTrigger" + "type": "stageTrigger", + "description": "Stage trigger manual or automatic (default)" }, "isSkippable": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Setting false prevents the stage from being skipped. By default it's always true" }, "templateContext": { "type": "templateContext" @@ -1858,11 +2098,13 @@ "properties": { "template": { "type": "nonEmptyString", + "description": "Reference to a template for this stage", "first-property": true, "required": true }, "parameters": { - "type": "azp-mapping" + "type": "azp-mapping", + "description": "Parameters used in a stage template" } } } @@ -2025,10 +2267,12 @@ "mapping": { "properties": { "parameters": { - "type": "templateParameters" + "type": "templateParameters", + "description": "Step-specific parameters" }, "steps": { "type": "steps", + "description": "A list of steps to run", "required": true } } @@ -2038,10 +2282,12 @@ "mapping": { "properties": { "parameters": { - "type": "templateParameters" + "type": "templateParameters", + "description": "Parameters used in a job template" }, "jobs": { - "type": "jobs" + "type": "jobs", + "description": "Jobs which make up the pipeline" } } } @@ -2069,6 +2315,7 @@ }, "extends": { "type": "extends", + "description": "Extends a template", "required": true } } @@ -2089,7 +2336,8 @@ "type": "nonEmptyString" }, "parameters": { - "type": "azp-mapping" + "type": "azp-mapping", + "description": "Parameters used in the extend" } } }, @@ -2102,10 +2350,12 @@ "mapping": { "properties": { "parameters": { - "type": "templateParameters" + "type": "templateParameters", + "description": "Parameters used in a job template" }, "jobs": { - "type": "jobs" + "type": "jobs", + "description": "Jobs which make up the pipeline" } } } @@ -2114,10 +2364,12 @@ "mapping": { "properties": { "parameters": { - "type": "templateParameters" + "type": "templateParameters", + "description": "Parameters used in a phase template" }, "phases": { - "type": "phases" + "type": "phases", + "description": "Phases which make up the pipeline" } } } @@ -2141,38 +2393,49 @@ "properties": { "job": { "type": "referenceName", + "description": "ID of the job", "first-property": true, "required": true }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the job" }, "dependsOn": { - "type": "jobDependsOn" + "type": "jobDependsOn", + "description": "Any jobs which must complete before this one" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this job" }, "continueOnError": { - "type": "jobContinueOnError" + "type": "jobContinueOnError", + "description": "Continue running even on failure?" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this job to complete before the server kills it" }, "cancelTimeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for the job to cancel before forcibly terminating it" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Job-specific variables" }, "strategy": { - "type": "jobStrategy" + "type": "jobStrategy", + "description": "Execution strategy for this job" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where this job will run" }, "container": { - "type": "jobContainer" + "type": "jobContainer", + "description": "Container resource name" }, "services": { "type": "jobServices" @@ -2181,10 +2444,12 @@ "type": "jobWorkspace" }, "uses": { - "type": "explicitResources" + "type": "explicitResources", + "description": "Any resources required by this job that are not already referenced" }, "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" }, "templateContext": { "type": "templateContext" @@ -2201,46 +2466,59 @@ "required": true }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the deployment" }, "dependsOn": { - "type": "jobDependsOn" + "type": "jobDependsOn", + "description": "Any jobs which must complete before this one" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this deployment" }, "continueOnError": { - "type": "jobContinueOnError" + "type": "jobContinueOnError", + "description": "Continue running even on failure?" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this job to complete before the server kills it" }, "cancelTimeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for the job to cancel before forcibly terminating it" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Deployment-specific variables" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where this job will run" }, "environment": { "type": "deploymentEnvironment" }, "strategy": { - "type": "deploymentStrategy" + "type": "deploymentStrategy", + "description": "Execution strategy for this deployment" }, "workspace": { - "type": "jobWorkspace" + "type": "jobWorkspace", + "description": "What to clean up before the job runs" }, "uses": { - "type": "explicitResources" + "type": "explicitResources", + "description": "Any resources required by this job that are not already referenced" }, "container": { - "type": "jobContainer" + "type": "jobContainer", + "description": "Container resource name" }, "services": { - "type": "jobServices" + "type": "jobServices", + "description": "Container resources to run as a service container" }, "templateContext": { "type": "templateContext" @@ -2253,11 +2531,13 @@ "properties": { "template": { "type": "nonEmptyString", + "description": "Reference to a template for this deployment", "first-property": true, "required": true }, "parameters": { - "type": "azp-mapping" + "type": "azp-mapping", + "description": "Parameters used in a deployment template" } } } @@ -2273,10 +2553,12 @@ "mapping": { "properties": { "repositories": { - "type": "sequenceOfNonEmptyString" + "type": "sequenceOfNonEmptyString", + "description": "Repository references" }, "pools": { - "type": "sequenceOfNonEmptyString" + "type": "sequenceOfNonEmptyString", + "description": "Pool references" } } } @@ -2288,10 +2570,12 @@ "mapping": { "properties": { "name": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Name of a pool" }, "demands": { - "type": "poolDemands" + "type": "poolDemands", + "description": "List of demands (for a private pool)" } }, "loose-key-type": "string", @@ -2299,6 +2583,7 @@ } }, "pool": { + "description": "Pool details", "one-of": [ "pool-0", "pool-1" @@ -2325,7 +2610,8 @@ "mapping": { "properties": { "alias": { - "type": "azp-string" + "type": "azp-string", + "description": "The alias of the container resource" } } } @@ -2334,20 +2620,25 @@ "mapping": { "properties": { "endpoint": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "ID of the service endpoint connecting to a private container registry" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the container's environment" }, "image": { "type": "azp-string", + "description": "Container image tag", "required": true }, "mapDockerSocket": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Set this flag to false to force the agent not to setup the /var/run/docker.sock volume on container jobs" }, "options": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Options to pass into container host" }, "ports": { "type": "sequenceOfString_allowExpressions" @@ -2372,20 +2663,25 @@ "mapping": { "properties": { "endpoint": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "ID of the service endpoint connecting to a private container registry" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the container's environment" }, "image": { "type": "azp-string", + "description": "Container image tag", "required": true }, "mapDockerSocket": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Set this flag to false to force the agent not to setup the /var/run/docker.sock volume on container jobs" }, "options": { - "type": "string_allowExpressions" + "type": "string_allowExpressions", + "description": "Options to pass into container host" }, "ports": { "type": "sequenceOfString_allowExpressions" @@ -2403,16 +2699,20 @@ "mapping": { "properties": { "work": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Mount the work directory as readonly" }, "externals": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Mount the externals directory as readonly" }, "tools": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Mount the tools directory as readonly" }, "tasks": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Mount the tasks directory as readonly" } } } @@ -2424,11 +2724,20 @@ "loose-value-type": "any" } }, + "jobWorkspace-clean": { + "description": "Which parts of the workspace should be scorched before fetching", + "allowed-values": [ + "outputs", + "resources", + "all" + ] + }, "jobWorkspace": { "mapping": { "properties": { "clean": { - "type": "azp-string" + "type": "jobWorkspace-clean", + "description": "Which parts of the workspace should be scorched before fetching" } } } @@ -2440,7 +2749,8 @@ "type": "jobMatrix" }, "maxParallel": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Maximum number of jobs running in parallel" } } } @@ -2449,7 +2759,8 @@ "mapping": { "properties": { "parallel": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Run the job this many times" } } } @@ -2477,6 +2788,7 @@ ] }, "matrixProperties": { + "description": "Variable-value pair to pass in this matrix instance", "mapping": { "properties": {}, "loose-key-type": "string", @@ -2490,24 +2802,30 @@ "mapping": { "properties": { "name": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Name of environment" }, "resourceName": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Name of resource" }, "resourceId": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Id of resource" }, "resourceType": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Type of environment resource" }, "tags": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "List of tag filters" } } } }, "deploymentEnvironment": { + "description": "Environment details", "one-of": [ "deploymentEnvironment-0", "deploymentEnvironment-1" @@ -2517,7 +2835,8 @@ "mapping": { "properties": { "runOnce": { - "type": "runOnceDeploymentStrategy" + "type": "runOnceDeploymentStrategy", + "description": "RunOnce Deployment strategy" } } } @@ -2526,7 +2845,8 @@ "mapping": { "properties": { "rolling": { - "type": "rollingDeploymentStrategy" + "type": "rollingDeploymentStrategy", + "description": "Rolling Deployment strategy" } } } @@ -2535,7 +2855,8 @@ "mapping": { "properties": { "canary": { - "type": "canaryDeploymentStrategy" + "type": "canaryDeploymentStrategy", + "description": "Canary Deployment strategy" } } } @@ -2551,10 +2872,12 @@ "mapping": { "properties": { "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where pre deploy steps will run" } } } @@ -2563,10 +2886,12 @@ "mapping": { "properties": { "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where deploy steps will run" } } } @@ -2575,10 +2900,12 @@ "mapping": { "properties": { "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where route traffic steps will run" } } } @@ -2587,10 +2914,12 @@ "mapping": { "properties": { "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where post route traffic steps will run" } } } @@ -2599,10 +2928,12 @@ "mapping": { "properties": { "failure": { - "type": "onFailureHook" + "type": "onFailureHook", + "description": "Runs on failure of any step" }, "success": { - "type": "onSuccessHook" + "type": "onSuccessHook", + "description": "Runs on success of all of the steps" } } } @@ -2611,10 +2942,12 @@ "mapping": { "properties": { "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where post on failure steps will run" } } } @@ -2623,10 +2956,12 @@ "mapping": { "properties": { "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" }, "pool": { - "type": "pool" + "type": "pool", + "description": "Pool where on success steps will run" } } } @@ -2635,19 +2970,24 @@ "mapping": { "properties": { "preDeploy": { - "type": "preDeployHook" + "type": "preDeployHook", + "description": "Pre deploy hook for runOnce deployment strategy" }, "deploy": { - "type": "deployHook" + "type": "deployHook", + "description": "Deploy hook for runOnce deployment strategy" }, "routeTraffic": { - "type": "routeTrafficHook" + "type": "routeTrafficHook", + "description": "Route traffic hook for runOnce deployment strategy" }, "postRouteTraffic": { - "type": "postRouteTrafficHook" + "type": "postRouteTrafficHook", + "description": "Post route traffic hook for runOnce deployment strategy" }, "on": { - "type": "onSuccessOrFailureHook" + "type": "onSuccessOrFailureHook", + "description": "On success or failure hook for runOnce deployment strategy" } } } @@ -2656,22 +2996,28 @@ "mapping": { "properties": { "maxParallel": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Maximum number of jobs running in parallel" }, "preDeploy": { - "type": "preDeployHook" + "type": "preDeployHook", + "description": "Pre deploy hook for rolling deployment strategy" }, "deploy": { - "type": "deployHook" + "type": "deployHook", + "description": "Deploy hook for rolling deployment strategy" }, "routeTraffic": { - "type": "routeTrafficHook" + "type": "routeTrafficHook", + "description": "Route traffic hook for rolling deployment strategy" }, "postRouteTraffic": { - "type": "postRouteTrafficHook" + "type": "postRouteTrafficHook", + "description": "Post route traffic hook for rolling deployment strategy" }, "on": { - "type": "onSuccessOrFailureHook" + "type": "onSuccessOrFailureHook", + "description": "On success or failure hook for rolling deployment strategy" } } } @@ -2680,22 +3026,28 @@ "mapping": { "properties": { "increments": { - "type": "canaryDeploymentIncrements" + "type": "canaryDeploymentIncrements", + "description": "Maximum batch size for deployment" }, "preDeploy": { - "type": "preDeployHook" + "type": "preDeployHook", + "description": "Pre deploy hook for canary deployment strategy" }, "deploy": { - "type": "deployHook" + "type": "deployHook", + "description": "Deploy hook for canary deployment strategy" }, "routeTraffic": { - "type": "routeTrafficHook" + "type": "routeTrafficHook", + "description": "Route traffic hook for canary deployment strategy" }, "postRouteTraffic": { - "type": "postRouteTrafficHook" + "type": "postRouteTrafficHook", + "description": "Post route traffic hook for canary deployment strategy" }, "on": { - "type": "onSuccessOrFailureHook" + "type": "onSuccessOrFailureHook", + "description": "On success or failure hook for canary deployment strategy" } } } @@ -2715,29 +3067,37 @@ "properties": { "phase": { "type": "referenceName", + "description": "ID of the phase", "first-property": true, "required": true }, "dependsOn": { - "type": "jobDependsOn" + "type": "jobDependsOn", + "description": "Any phases which must complete before this one" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name of the phase" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this phase" }, "continueOnError": { - "type": "jobContinueOnError" + "type": "jobContinueOnError", + "description": "Continue running even on failure?" }, "queue": { - "type": "phaseQueueTarget" + "type": "phaseQueueTarget", + "description": "Queue where this phase will run" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Phase-specific variables" }, "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run in this phase" } } } @@ -2747,29 +3107,37 @@ "properties": { "phase": { "type": "referenceName", + "description": "ID of the phase", "first-property": true, "required": true }, "dependsOn": { - "type": "jobDependsOn" + "type": "jobDependsOn", + "description": "Any phases which must complete before this one" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name of the phase" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this phase" }, "continueOnError": { - "type": "jobContinueOnError" + "type": "jobContinueOnError", + "description": "Continue running even on failure?" }, "server": { - "type": "phaseServerTarget" + "type": "phaseServerTarget", + "description": "True if this is an agent-less phase (runs on server)" }, "variables": { - "type": "variables" + "type": "variables", + "description": "Phase-specific variables" }, "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run in this phase" } } } @@ -2779,11 +3147,13 @@ "properties": { "template": { "type": "nonEmptyString", + "description": "Reference to a template for this phase", "first-property": true, "required": true }, "parameters": { - "type": "azp-mapping" + "type": "azp-mapping", + "description": "Parameters used in a phase template" } } } @@ -2800,25 +3170,31 @@ "mapping": { "properties": { "cancelTimeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for the phase to cancel before forcibly terminating it" }, "container": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Container resource name" }, "demands": { - "type": "phaseTargetDemands" + "type": "phaseTargetDemands", + "description": "List of demands (for a private queue)" }, "matrix": { "type": "phaseTargetMatrix" }, "name": { - "type": "azp-string" + "type": "azp-string", + "description": "Name of a queue" }, "parallel": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Maximum number of parallel agent executions" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait before cancelling the phase" }, "workspace": { "type": "phaseTargetWorkspace" @@ -2827,6 +3203,7 @@ } }, "phaseQueueTarget": { + "description": "Queue details", "one-of": [ "phaseQueueTarget-0", "phaseQueueTarget-1" @@ -2839,16 +3216,19 @@ "mapping": { "properties": { "cancelTimeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for the job to cancel before forcibly terminating it" }, "matrix": { "type": "phaseTargetMatrix" }, "parallel": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Maximum number of parallel agent executions" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait before cancelling the job" } } } @@ -2873,11 +3253,20 @@ "phaseTargetDemands-1" ] }, + "phaseTargetWorkspace-clean": { + "description": "Scorch the repo before fetching?", + "allowed-values": [ + "outputs", + "resources", + "all" + ] + }, "phaseTargetWorkspace": { "mapping": { "properties": { "clean": { - "type": "azp-string" + "type": "phaseTargetWorkspace-clean", + "description": "Scorch the repo before fetching?" } } } @@ -2893,6 +3282,7 @@ "string": {} }, "phaseTargetMatrix": { + "description": "List of permutations of variable values to run", "one-of": [ "phaseTargetMatrix-0", "phaseTargetMatrix-1" @@ -2902,10 +3292,12 @@ "mapping": { "properties": { "parameters": { - "type": "templateParameters" + "type": "templateParameters", + "description": "Step-specific parameters" }, "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" } } } @@ -2924,41 +3316,53 @@ "properties": { "script": { "type": "azp-string", + "description": "An inline script", "first-property": true, "required": true }, "failOnStderr": { - "type": "azp-string" + "type": "azp-string", + "description": "Fail the task if output is sent to Stderr?" }, "workingDirectory": { - "type": "azp-string" + "type": "azp-string", + "description": "Start the script with this working directory" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -2968,6 +3372,7 @@ "properties": { "powershell": { "type": "azp-string", + "description": "Inline PowerShell or reference to a PowerShell file", "first-property": true, "required": true }, @@ -2975,40 +3380,52 @@ "type": "azp-string" }, "failOnStderr": { - "type": "azp-string" + "type": "azp-string", + "description": "Fail the task if output is sent to Stderr?" }, "ignoreLASTEXITCODE": { - "type": "azp-string" + "type": "azp-string", + "description": "Check the final exit code of the script to determine whether the step succeeded?" }, "workingDirectory": { - "type": "azp-string" + "type": "azp-string", + "description": "Start the script with this working directory" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3018,6 +3435,7 @@ "properties": { "pwsh": { "type": "azp-string", + "description": "Inline PowerShell or reference to a PowerShell file", "first-property": true, "required": true }, @@ -3025,40 +3443,52 @@ "type": "azp-string" }, "failOnStderr": { - "type": "azp-string" + "type": "azp-string", + "description": "Fail the task if output is sent to Stderr?" }, "ignoreLASTEXITCODE": { - "type": "azp-string" + "type": "azp-string", + "description": "Check the final exit code of the script to determine whether the step succeeded?" }, "workingDirectory": { - "type": "azp-string" + "type": "azp-string", + "description": "Start the script with this working directory" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3068,106 +3498,151 @@ "properties": { "bash": { "type": "azp-string", + "description": "An inline script", "first-property": true, "required": true }, "failOnStderr": { - "type": "azp-string" + "type": "azp-string", + "description": "Fail the task if output is sent to Stderr?" }, "workingDirectory": { - "type": "azp-string" + "type": "azp-string", + "description": "Start the script with this working directory" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } }, + "step-5-clean": { + "description": "Scorch the repo before fetching?", + "allowed-values": [ + "true", + "false" + ] + }, + "step-5-workspaceRepo": { + "description": "Make the repository root directory the default working directory?", + "allowed-values": [ + "true", + "false" + ] + }, "step-5": { "mapping": { "properties": { "checkout": { "type": "azp-string", + "description": "Alias of the repository resource to check out or 'none'", "first-property": true, "required": true }, "clean": { - "type": "azp-string" + "type": "step-5-clean", + "description": "Scorch the repo before fetching?" }, "fetchDepth": { - "type": "azp-string" + "type": "azp-string", + "description": "Depth of Git graph to fetch" }, "fetchFilter": { - "type": "azp-string" + "type": "azp-string", + "description": "Filter Git history" }, "fetchTags": { - "type": "azp-string" + "type": "azp-string", + "description": "Fetch tags?" }, "lfs": { - "type": "azp-string" + "type": "azp-string", + "description": "Fetch Git-LFS objects?" }, "persistCredentials": { - "type": "azp-string" + "type": "azp-string", + "description": "Keep credentials available for later use?" }, "submodules": { - "type": "azp-string" + "type": "azp-string", + "description": "Check out Git submodules?" }, "path": { - "type": "azp-string" + "type": "azp-string", + "description": "Path of the repository to check out" }, "workspaceRepo": { - "type": "azp-string" + "type": "step-5-workspaceRepo", + "description": "Make the repository root directory the default working directory?" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3177,41 +3652,53 @@ "properties": { "download": { "type": "nonEmptyString", + "description": "Reference to the pipeline", "first-property": true, "required": true }, "artifact": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Name of the artifact to download" }, "patterns": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Pattern to download files from artifact" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3221,47 +3708,61 @@ "properties": { "downloadBuild": { "type": "nonEmptyString", + "description": "ID for the build resource", "first-property": true, "required": true }, "artifact": { - "type": "azp-string" + "type": "azp-string", + "description": "Name of the artifact to download" }, "path": { - "type": "azp-string" + "type": "azp-string", + "description": "Path to download the artifact into" }, "patterns": { - "type": "azp-string" + "type": "azp-string", + "description": "Downloads the files which matches the patterns" }, "inputs": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Inputs for the task" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3271,38 +3772,49 @@ "properties": { "getPackage": { "type": "nonEmptyString", + "description": "ID for the package resource", "first-property": true, "required": true }, "path": { - "type": "azp-string" + "type": "azp-string", + "description": "Path to download the package into" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3319,31 +3831,40 @@ "type": "azp-string" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3360,31 +3881,40 @@ "type": "azp-string" }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3394,11 +3924,13 @@ "properties": { "template": { "type": "nonEmptyString", + "description": "Reference to a template for this step", "first-property": true, "required": true }, "parameters": { - "type": "azp-mapping" + "type": "azp-mapping", + "description": "Parameters used in a step template" } } } @@ -3412,31 +3944,40 @@ "required": true }, "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3460,22 +4001,33 @@ "stepTarget-0": { "string": {} }, + "stepTarget-1-commands": { + "description": "Set of allowed logging commands ('any' or 'restricted')", + "allowed-values": [ + "any", + "restricted" + ] + }, "stepTarget-1": { "mapping": { "properties": { "container": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Container to target (or 'host' for host machine)" }, "commands": { - "type": "azp-string" + "type": "stepTarget-1-commands", + "description": "Set of allowed logging commands ('any' or 'restricted')" }, "settableVariables": { - "type": "variableRestrictions" + "type": "variableRestrictions", + "description": "Restrictions on which variables that can be set" } } } }, "stepTarget": { + "description": "Step target", "one-of": [ "stepTarget-0", "stepTarget-1" @@ -3502,7 +4054,8 @@ "mapping": { "properties": { "steps": { - "type": "tasks" + "type": "tasks", + "description": "A list of steps to run in this job" } } } @@ -3516,31 +4069,40 @@ "mapping": { "properties": { "condition": { - "type": "azp-string" + "type": "azp-string", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Continue running even on failure?" }, "displayName": { - "type": "azp-string" + "type": "azp-string", + "description": "Human-readable name for the task" }, "target": { - "type": "stepTarget" + "type": "stepTarget", + "description": "Environment in which to run this task" }, "enabled": { - "type": "azp-boolean" + "type": "azp-boolean", + "description": "Run this task when the job runs?" }, "env": { - "type": "mappingOfStringString" + "type": "mappingOfStringString", + "description": "Variables to map into the process's environment" }, "name": { - "type": "referenceName" + "type": "referenceName", + "description": "ID of the step" }, "timeoutInMinutes": { - "type": "nonEmptyString" + "type": "nonEmptyString", + "description": "Time to wait for this task to complete before the server kills it" }, "retryCountOnTaskFailure": { - "type": "azp-string" + "type": "azp-string", + "description": "Number of retries if the task fails" } } } @@ -3697,1536 +4259,1792 @@ ] }, "task-task-0": { + "description": "Run a PowerShell script on Linux, macOS, or Windows", "string": { "constant": "PowerShell@2", "ignore-case": true } }, "task-task-1": { + "description": "Run a PowerShell script", "string": { "constant": "PowerShell@1", "ignore-case": true } }, "task-task-2": { + "description": "Run a PowerShell script within an Azure environment", "string": { "constant": "AzurePowerShell@4", "ignore-case": true } }, "task-task-3": { + "description": "Run a PowerShell script within an Azure environment", "string": { "constant": "AzurePowerShell@5", "ignore-case": true } }, "task-task-4": { + "description": "Run a PowerShell script within an Azure environment", "string": { "constant": "AzurePowerShell@2", "ignore-case": true } }, "task-task-5": { + "description": "Run a PowerShell script within an Azure environment", "string": { "constant": "AzurePowerShell@3", "ignore-case": true } }, "task-task-6": { + "description": "Run a PowerShell script within an Azure environment", "string": { "constant": "AzurePowerShell@1", "ignore-case": true } }, "task-task-7": { + "description": "Run scripts and make changes to a MySQL Database", "string": { "constant": "MysqlDeploymentOnMachineGroup@1", "ignore-case": true } }, "task-task-8": { + "description": "Authentication task for the pip client used for installing Python distributions", "string": { "constant": "PipAuthenticate@0", "ignore-case": true } }, "task-task-9": { + "description": "Authentication task for the pip client used for installing Python distributions", "string": { "constant": "PipAuthenticate@1", "ignore-case": true } }, "task-task-10": { + "description": "Build, test, and deploy with Apache Maven", "string": { "constant": "Maven@4", "ignore-case": true } }, "task-task-11": { + "description": "Build, test, and deploy with Apache Maven", "string": { "constant": "Maven@2", "ignore-case": true } }, "task-task-12": { + "description": "Build, test, and deploy with Apache Maven", "string": { "constant": "Maven@3", "ignore-case": true } }, "task-task-13": { + "description": "Build with Apache Maven", "string": { "constant": "Maven@1", "ignore-case": true } }, "task-task-14": { + "description": "Build, test and publish using dotnet core command-line.", "string": { "constant": "DotNetCoreCLI@0", "ignore-case": true } }, "task-task-15": { + "description": "Build, test, package, or publish a dotnet application, or run a custom dotnet command", "string": { "constant": "DotNetCoreCLI@2", "ignore-case": true } }, "task-task-16": { + "description": "Build, test and publish using dotnet core command-line.", "string": { "constant": "DotNetCoreCLI@1", "ignore-case": true } }, "task-task-17": { + "description": "This task is deprecated. Use 'NuGet' instead.", "string": { "constant": "XamarinComponentRestore@0", "ignore-case": true } }, "task-task-18": { + "description": "Deploy to Azure App Service a web, mobile, or API app using Docker, Java, .NET, .NET Core, Node.js, PHP, Python, or Ruby", "string": { "constant": "AzureRmWebAppDeployment@4", "ignore-case": true } }, "task-task-19": { + "description": "Update Azure App Service using Web Deploy / Kudu REST APIs", "string": { "constant": "AzureRmWebAppDeployment@2", "ignore-case": true } }, "task-task-20": { + "description": "Deploy the web project to the AzureRM Web App using Web Deploy", "string": { "constant": "AzureRMWebAppDeployment@1", "ignore-case": true } }, "task-task-21": { + "description": "Deploy to Azure App Service a web, mobile, or API app using Docker, Java, .NET, .NET Core, Node.js, PHP, Python, or Ruby", "string": { "constant": "AzureRmWebAppDeployment@3", "ignore-case": true } }, "task-task-22": { + "description": "Execute PowerShell scripts on remote machine(s)", "string": { "constant": "PowerShellOnTargetMachines@1", "ignore-case": true } }, "task-task-23": { + "description": "Execute PowerShell scripts on remote machines using PSSession and Invoke-Command for remoting", "string": { "constant": "PowerShellOnTargetMachines@3", "ignore-case": true } }, "task-task-24": { + "description": "Execute PowerShell scripts on remote machine(s)", "string": { "constant": "PowerShellOnTargetMachines@2", "ignore-case": true } }, "task-task-25": { + "description": "Publish any of the code coverage results from a build", "string": { "constant": "PublishCodeCoverageResults@2", "ignore-case": true } }, "task-task-26": { + "description": "[DEPRECATION WARNING! Users are recommended to switch to version 2*.] Publish Cobertura or JaCoCo code coverage results from a build", "string": { "constant": "PublishCodeCoverageResults@1", "ignore-case": true } }, "task-task-27": { + "description": "Deprecated: This task and it’s companion task (Visual Studio Test Agent Deployment) are deprecated. Use the 'Visual Studio Test' task instead. The VSTest task can run unit as well as functional tests. Run tests on one or more agents using the multi-agent job setting. Use the 'Visual Studio Test Platform' task to run tests without needing Visual Studio on the agent. VSTest task also brings new capabilities such as automatically rerunning failed tests.", "string": { "constant": "RunVisualStudioTestsusingTestAgent@1", "ignore-case": true } }, "task-task-28": { + "description": "Pause deployment and wait for intervention", "string": { "constant": "ManualIntervention@6", "ignore-case": true } }, "task-task-29": { + "description": "Pause deployment and wait for intervention", "string": { "constant": "ManualIntervention@7", "ignore-case": true } }, "task-task-30": { + "description": "Pause deployment and wait for manual intervention", "string": { "constant": "ManualIntervention@8", "ignore-case": true } }, "task-task-31": { + "description": "Install an Apple provisioning profile required to build on a macOS agent machine", "string": { "constant": "InstallAppleProvisioningProfile@1", "ignore-case": true } }, "task-task-32": { + "description": "Install an Apple provisioning profile required to build on a macOS agent", "string": { "constant": "InstallAppleProvisioningProfile@0", "ignore-case": true } }, "task-task-33": { + "description": "[DEPRECATED] Finish the analysis and upload the results to SonarQube", "string": { "constant": "SonarQubePostTest@1", "ignore-case": true } }, "task-task-34": { + "description": "Create and upload an sdist or wheel to a PyPI-compatible index using Twine", "string": { "constant": "PyPIPublisher@0", "ignore-case": true } }, "task-task-35": { + "description": "Run scripts with Knife commands on your Chef workstation", "string": { "constant": "ChefKnife@1", "ignore-case": true } }, "task-task-36": { + "description": "Find in cache or download a specific version of Go and add it to the PATH", "string": { "constant": "GoTool@0", "ignore-case": true } }, "task-task-37": { + "description": "Generate an .ipa file from Xcode build output using xcrun (Xcode 7 or below)", "string": { "constant": "XcodePackageiOS@0", "ignore-case": true } }, "task-task-38": { + "description": "Get, build, or test a Go application, or run a custom Go command", "string": { "constant": "Go@0", "ignore-case": true } }, "task-task-39": { + "description": "Publish Pipeline Metadata to Evidence store", "string": { "constant": "PublishPipelineMetadata@0", "ignore-case": true } }, "task-task-40": { + "description": "Build, tag, push, or run Docker images, or run a Docker command", "string": { "constant": "Docker@1", "ignore-case": true } }, "task-task-41": { + "description": "Build, tag, push, or run Docker images, or run a Docker command", "string": { "constant": "Docker@0", "ignore-case": true } }, "task-task-42": { + "description": "Build or push Docker images, login or logout, start or stop containers, or run a Docker command", "string": { "constant": "Docker@2", "ignore-case": true } }, "task-task-43": { + "description": "Queue a job on a Jenkins server", "string": { "constant": "JenkinsQueueJob@2", "ignore-case": true } }, "task-task-44": { + "description": "Queue a job on a Jenkins server", "string": { "constant": "JenkinsQueueJob@1", "ignore-case": true } }, "task-task-45": { + "description": "Upload files using FTP", "string": { "constant": "FtpUpload@1", "ignore-case": true } }, "task-task-46": { + "description": "Upload files using FTP", "string": { "constant": "FtpUpload@2", "ignore-case": true } }, "task-task-47": { + "description": "Copy files to remote Windows machines", "string": { "constant": "WindowsMachineFileCopy@2", "ignore-case": true } }, "task-task-48": { + "description": "Copy files to remote Windows machines", "string": { "constant": "WindowsMachineFileCopy@1", "ignore-case": true } }, "task-task-49": { + "description": "[Deprecated] Use Gradle", "string": { "constant": "AndroidBuild@1", "ignore-case": true } }, "task-task-50": { + "description": "Authenticate for uploading Python distributions using twine. Add '-r FeedName/EndpointName --config-file $(PYPIRC_PATH)' to your twine upload command. For feeds present in this organization, use the feed name as the repository (-r). Otherwise, use the endpoint name defined in the service connection.", "string": { "constant": "TwineAuthenticate@0", "ignore-case": true } }, "task-task-51": { + "description": "Authenticate for uploading Python distributions using twine. Add '-r FeedName/EndpointName --config-file $(PYPIRC_PATH)' to your twine upload command. For feeds present in this organization, use the feed name as the repository (-r). Otherwise, use the endpoint name defined in the service connection.", "string": { "constant": "TwineAuthenticate@1", "ignore-case": true } }, "task-task-52": { + "description": "Deploy a website or web application using Web Deploy", "string": { "constant": "IISWebAppDeploymentOnMachineGroup@0", "ignore-case": true } }, "task-task-53": { + "description": "Run a Python file or inline script", "string": { "constant": "PythonScript@0", "ignore-case": true } }, "task-task-54": { + "description": "Install Helm and Kubernetes on an agent machine", "string": { "constant": "HelmInstaller@0", "ignore-case": true } }, "task-task-55": { + "description": "Install Helm on an agent machine", "string": { "constant": "HelmInstaller@1", "ignore-case": true } }, "task-task-56": { + "description": "Install specific Node.js version to run node tasks", "string": { "constant": "NodeTaskRunnerInstaller@0", "ignore-case": true } }, "task-task-57": { + "description": "[Deprecated] Upgrade to free version of Xamarin: https://store.xamarin.com", "string": { "constant": "XamarinLicense@1", "ignore-case": true } }, "task-task-58": { + "description": "This version of the task is deprecated, use NuGetAuthenticateV1 instead. Configure NuGet tools to authenticate with Azure Artifacts and other NuGet repositories. Requires NuGet >= 4.8.5385, dotnet >= 2.1.400, or MSBuild >= 15.8.166.59604.", "string": { "constant": "NuGetAuthenticate@0", "ignore-case": true } }, "task-task-59": { + "description": "Configure NuGet tools to authenticate with Azure Artifacts and other NuGet repositories. Requires NuGet >= 4.8.5385, dotnet >= 6, or MSBuild >= 15.8.166.59604", "string": { "constant": "NuGetAuthenticate@1", "ignore-case": true } }, "task-task-60": { + "description": "Restore your nuget packages using dotnet CLI", "string": { "constant": "DownloadGitHubNugetPackage@1", "ignore-case": true } }, "task-task-61": { + "description": "Provides credentials for Azure Artifacts feeds and external maven repositories", "string": { "constant": "MavenAuthenticate@0", "ignore-case": true } }, "task-task-62": { + "description": "Use this task under deploy phase provider to create a resource dynamically", "string": { "constant": "ReviewApp@0", "ignore-case": true } }, "task-task-63": { + "description": "Acquire a specific version of Java from a user-supplied Azure blob or the tool cache and sets JAVA_HOME", "string": { "constant": "JavaToolInstaller@0", "ignore-case": true } }, "task-task-64": { + "description": "Deploy to Chef environments by editing environment attributes", "string": { "constant": "Chef@1", "ignore-case": true } }, "task-task-65": { + "description": "Update a function app with .NET, Python, JavaScript, PowerShell, Java based web applications", "string": { "constant": "AzureFunctionApp@2", "ignore-case": true } }, "task-task-66": { + "description": "Update a function app with .NET, Python, JavaScript, PowerShell, Java based web applications", "string": { "constant": "AzureFunctionApp@1", "ignore-case": true } }, "task-task-67": { + "description": "Don't use this task if you're also using the npm task. Provides npm credentials to an .npmrc file in your repository for the scope of the build. This enables npm task runners like gulp and Grunt to authenticate with private registries.", "string": { "constant": "npmAuthenticate@0", "ignore-case": true } }, "task-task-68": { + "description": "Build with MSBuild", "string": { "constant": "MSBuild@1", "ignore-case": true } }, "task-task-69": { + "description": "Build a machine image using Packer, which may be used for Azure Virtual machine scale set deployment", "string": { "constant": "PackerBuild@0", "ignore-case": true } }, "task-task-70": { + "description": "Build a machine image using Packer, which may be used for Azure Virtual machine scale set deployment", "string": { "constant": "PackerBuild@1", "ignore-case": true } }, "task-task-71": { + "description": "Deprecated: use the “NuGet” task instead. It works with the new Tool Installer framework so you can easily use new versions of NuGet without waiting for a task update, provides better support for authenticated feeds outside this organization/collection, and uses NuGet 4 by default.", "string": { "constant": "NuGetPackager@0", "ignore-case": true } }, "task-task-72": { + "description": "Install a specified version of Duffle for installing and managing CNAB bundles", "string": { "constant": "DuffleInstaller@0", "ignore-case": true } }, "task-task-73": { + "description": "Automatically updates the versions of a packaged Service Fabric application.", "string": { "constant": "ServiceFabricUpdateAppVersions@1", "ignore-case": true } }, "task-task-74": { + "description": "Automatically update portions of application and service manifests in a packaged Azure Service Fabric application", "string": { "constant": "ServiceFabricUpdateManifests@2", "ignore-case": true } }, "task-task-75": { + "description": "Observe the configured Azure Monitor rules for active alerts", "string": { "constant": "AzureMonitor@1", "ignore-case": true } }, "task-task-76": { + "description": "Observe the configured classic Azure Monitor rules for active alerts", "string": { "constant": "AzureMonitor@0", "ignore-case": true } }, "task-task-77": { + "description": "Azure Pipepine Task for setting up Notation CLI, sign and verify with Notation", "string": { "constant": "Notation@0", "ignore-case": true } }, "task-task-78": { + "description": "Connect or disconnect an Azure virtual machine's network interface to a Load Balancer's back end address pool", "string": { "constant": "AzureNLBManagement@1", "ignore-case": true } }, "task-task-79": { + "description": "Run an Apache JMeter load test in the cloud", "string": { "constant": "ApacheJMeterLoadTest@1", "ignore-case": true } }, "task-task-80": { + "description": "Build, push or run multi-container Docker applications. Task can be used with Docker or Azure Container registry.", "string": { "constant": "DockerCompose@0", "ignore-case": true } }, "task-task-81": { + "description": "Build, push or run multi-container Docker applications. Task can be used with Docker or Azure Container registry.", "string": { "constant": "DockerCompose@1", "ignore-case": true } }, "task-task-82": { + "description": "Configure alerts on available metrics for an Azure resource (Deprecated)", "string": { "constant": "AzureMonitorAlerts@0", "ignore-case": true } }, "task-task-83": { + "description": "[Deprecated] Test mobile apps with Xamarin Test Cloud using Xamarin.UITest. Instead, use the 'App Center test' task.", "string": { "constant": "XamarinTestCloud@1", "ignore-case": true } }, "task-task-84": { + "description": "Deploy an Azure Service Fabric application to a cluster", "string": { "constant": "ServiceFabricDeploy@1", "ignore-case": true } }, "task-task-85": { + "description": "Build an Xcode workspace on Mac OS", "string": { "constant": "Xcode@2", "ignore-case": true } }, "task-task-86": { + "description": "Build an Xcode workspace on Mac OS", "string": { "constant": "Xcode@1", "ignore-case": true } }, "task-task-87": { + "description": "Build, test, or archive an Xcode workspace on macOS. Optionally package an app.", "string": { "constant": "Xcode@5", "ignore-case": true } }, "task-task-88": { + "description": "Build an Xcode workspace on macOS", "string": { "constant": "Xcode@3", "ignore-case": true } }, "task-task-89": { + "description": "Build, test, or archive an Xcode workspace on macOS. Optionally package an app.", "string": { "constant": "Xcode@4", "ignore-case": true } }, "task-task-90": { + "description": "Deprecated: use the “NuGet” task instead. It works with the new Tool Installer framework so you can easily use new versions of NuGet without waiting for a task update, provides better support for authenticated feeds outside this organization/collection, and uses NuGet 4 by default.", "string": { "constant": "NuGetPublisher@0", "ignore-case": true } }, "task-task-91": { + "description": "Execute a work item query and check the number of items returned", "string": { "constant": "queryWorkItems@0", "ignore-case": true } }, "task-task-92": { + "description": "Deploy containers to Azure App Service", "string": { "constant": "AzureWebAppContainer@1", "ignore-case": true } }, "task-task-93": { + "description": "Deploy a SQL Server database using DACPAC or SQL scripts", "string": { "constant": "SqlDacpacDeploymentOnMachineGroup@0", "ignore-case": true } }, "task-task-94": { + "description": "Cache files between runs", "string": { "constant": "CacheBeta@1", "ignore-case": true } }, "task-task-95": { + "description": "Cache files between runs", "string": { "constant": "Cache@2", "ignore-case": true } }, "task-task-96": { + "description": "Cache files between runs", "string": { "constant": "CacheBeta@0", "ignore-case": true } }, "task-task-97": { + "description": "Build with the CMake cross-platform build system", "string": { "constant": "CMake@1", "ignore-case": true } }, "task-task-98": { + "description": "Test mobile app packages with Visual Studio Mobile Center.", "string": { "constant": "VSMobileCenterTest@0", "ignore-case": true } }, "task-task-99": { + "description": "Test app packages with Visual Studio App Center", "string": { "constant": "AppCenterTest@1", "ignore-case": true } }, "task-task-100": { + "description": "Download a secure file to the agent machine", "string": { "constant": "DownloadSecureFile@1", "ignore-case": true } }, "task-task-101": { + "description": "An Azure DevOps Task to build and deploy Azure Container Apps.", "string": { "constant": "AzureContainerApps@0", "ignore-case": true } }, "task-task-102": { + "description": "An Azure DevOps Task to build and deploy Azure Container Apps.", "string": { "constant": "AzureContainerApps@1", "ignore-case": true } }, "task-task-103": { + "description": "Use the specified version of Ruby from the tool cache, optionally adding it to the PATH", "string": { "constant": "UseRubyVersion@0", "ignore-case": true } }, "task-task-104": { + "description": "Run the Grunt JavaScript task runner", "string": { "constant": "Grunt@0", "ignore-case": true } }, "task-task-105": { + "description": "Deploy an Azure SQL Database using DACPAC or run scripts using SQLCMD", "string": { "constant": "SqlAzureDacpacDeployment@1", "ignore-case": true } }, "task-task-106": { + "description": "Uses container-structure-test (https://github.com/GoogleContainerTools/container-structure-test) to validate the structure of an image based on four categories of tests - command tests, file existence tests, file content tests and metadata tests", "string": { "constant": "ContainerStructureTest@0", "ignore-case": true } }, "task-task-107": { + "description": "Deploy using MSDeploy, then create/update websites and app pools", "string": { "constant": "IISWebAppDeployment@1", "ignore-case": true } }, "task-task-108": { + "description": "Run a load test in the cloud with Azure Pipelines", "string": { "constant": "CloudLoadTest@1", "ignore-case": true } }, "task-task-109": { + "description": "Install Kubectl on agent machine", "string": { "constant": "KubectlInstaller@0", "ignore-case": true } }, "task-task-110": { + "description": "Run a command line script using Bash on Linux and macOS and cmd.exe on Windows", "string": { "constant": "CmdLine@2", "ignore-case": true } }, "task-task-111": { + "description": "Run a command line with arguments", "string": { "constant": "CmdLine@1", "ignore-case": true } }, "task-task-112": { + "description": "Deprecated: use the “NuGet” task instead. It works with the new Tool Installer framework so you can easily use new versions of NuGet without waiting for a task update, provides better support for authenticated feeds outside this organization/collection, and uses NuGet 4 by default.", "string": { "constant": "NuGet@0", "ignore-case": true } }, "task-task-113": { + "description": "Container Build Task", "string": { "constant": "ContainerBuild@0", "ignore-case": true } }, "task-task-114": { + "description": "Restore, pack, or push NuGet packages, or run a NuGet command. Supports NuGet.org and authenticated feeds like Azure Artifacts and MyGet. Uses NuGet.exe and works with .NET Framework apps. For .NET Core and .NET Standard apps, use the .NET Core task.", "string": { "constant": "NuGetCommand@2", "ignore-case": true } }, "task-task-115": { + "description": "Installs or restores missing NuGet packages. Use NuGetAuthenticate@0 task for latest capabilities.", "string": { "constant": "NuGetInstaller@0", "ignore-case": true } }, "task-task-116": { + "description": "Restores NuGet packages in preparation for a Visual Studio Build step.", "string": { "constant": "NuGetRestore@1", "ignore-case": true } }, "task-task-117": { + "description": "Delay further execution of a workflow by a fixed time", "string": { "constant": "Delay@1", "ignore-case": true } }, "task-task-118": { + "description": "Build an iOS app with Xamarin on macOS", "string": { "constant": "XamariniOS@1", "ignore-case": true } }, "task-task-119": { + "description": "Build an iOS app with Xamarin on macOS", "string": { "constant": "XamariniOS@2", "ignore-case": true } }, "task-task-120": { + "description": "Publish test results to Azure Pipelines", "string": { "constant": "PublishTestResults@1", "ignore-case": true } }, "task-task-121": { + "description": "Publish test results to Azure Pipelines", "string": { "constant": "PublishTestResults@2", "ignore-case": true } }, "task-task-122": { + "description": "Copy files to Azure Blob Storage or virtual machines", "string": { "constant": "AzureFileCopy@4", "ignore-case": true } }, "task-task-123": { + "description": "Copy files to Azure Blob Storage or virtual machines", "string": { "constant": "AzureFileCopy@1", "ignore-case": true } }, "task-task-124": { + "description": "Copy files to Azure Blob Storage or virtual machines", "string": { "constant": "AzureFileCopy@5", "ignore-case": true } }, "task-task-125": { + "description": "Copy files to Azure Blob Storage or virtual machines", "string": { "constant": "AzureFileCopy@2", "ignore-case": true } }, "task-task-126": { + "description": "Copy files to Azure Blob Storage or virtual machines", "string": { "constant": "AzureFileCopy@6", "ignore-case": true } }, "task-task-127": { + "description": "Copy files to Azure Blob Storage or virtual machines", "string": { "constant": "AzureFileCopy@3", "ignore-case": true } }, "task-task-128": { + "description": "Index your source code and publish symbols to a file share", "string": { "constant": "PublishSymbols@1", "ignore-case": true } }, "task-task-129": { + "description": "Index your source code and publish symbols to a file share or Azure Artifacts symbol server", "string": { "constant": "PublishSymbols@2", "ignore-case": true } }, "task-task-130": { + "description": "Copy files or build artifacts to a remote machine over SSH", "string": { "constant": "CopyFilesOverSSH@0", "ignore-case": true } }, "task-task-131": { + "description": "Build using a Gradle wrapper script", "string": { "constant": "Gradle@3", "ignore-case": true } }, "task-task-132": { + "description": "Build using a Gradle wrapper script", "string": { "constant": "Gradle@2", "ignore-case": true } }, "task-task-133": { + "description": "Build using a Gradle wrapper script", "string": { "constant": "Gradle@1", "ignore-case": true } }, "task-task-134": { + "description": "Run a build using Gradle wrapper", "string": { "constant": "Gradle@0", "ignore-case": true } }, "task-task-135": { + "description": "Distribute app builds to testers and users via Visual Studio App Center", "string": { "constant": "AppCenterDistribute@2", "ignore-case": true } }, "task-task-136": { + "description": "Distribute app builds to testers and users via Visual Studio App Center", "string": { "constant": "AppCenterDistribute@1", "ignore-case": true } }, "task-task-137": { + "description": "Distribute app builds to testers and users via Visual Studio App Center", "string": { "constant": "AppCenterDistribute@3", "ignore-case": true } }, "task-task-138": { + "description": "Distribute app builds to testers and users via App Center", "string": { "constant": "AppCenterDistribute@0", "ignore-case": true } }, "task-task-139": { + "description": "Acquires a specific version of NuGet from the internet or the tools cache and adds it to the PATH. Use this task to change the version of NuGet used in the NuGet tasks.", "string": { "constant": "NuGetToolInstaller@0", "ignore-case": true } }, "task-task-140": { + "description": "Acquires a specific version of NuGet from the internet or the tools cache and adds it to the PATH. Use this task to change the version of NuGet used in the NuGet tasks.", "string": { "constant": "NuGetToolInstaller@1", "ignore-case": true } }, "task-task-141": { + "description": "Download artifacts produced by a Jenkins job", "string": { "constant": "JenkinsDownloadArtifacts@1", "ignore-case": true } }, "task-task-142": { + "description": "Update a function app with a Docker container", "string": { "constant": "AzureFunctionAppContainer@1", "ignore-case": true } }, "task-task-143": { + "description": "Decrypt a file using OpenSSL", "string": { "constant": "DecryptFile@1", "ignore-case": true } }, "task-task-144": { + "description": "Deploy, configure, update a Kubernetes cluster in Azure Container Service by running helm commands", "string": { "constant": "HelmDeploy@0", "ignore-case": true } }, "task-task-145": { + "description": "Deploy, configure, update a Kubernetes cluster in Azure Container Service by running helm commands", "string": { "constant": "HelmDeploy@1", "ignore-case": true } }, "task-task-146": { + "description": "Install an Apple certificate required to build on a macOS agent machine", "string": { "constant": "InstallAppleCertificate@2", "ignore-case": true } }, "task-task-147": { + "description": "Install an Apple certificate required to build on a macOS agent", "string": { "constant": "InstallAppleCertificate@1", "ignore-case": true } }, "task-task-148": { + "description": "Install an Apple certificate required to build on a macOS agent", "string": { "constant": "InstallAppleCertificate@0", "ignore-case": true } }, "task-task-149": { + "description": "Invoke an Azure Function", "string": { "constant": "AzureFunction@1", "ignore-case": true } }, "task-task-150": { + "description": "Invoke Azure function as a part of your process.", "string": { "constant": "AzureFunction@0", "ignore-case": true } }, "task-task-151": { + "description": "Install Open Policy Agent on agent machine", "string": { "constant": "OpenPolicyAgentInstaller@0", "ignore-case": true } }, "task-task-152": { + "description": "Downloads a GitHub Release from a repository", "string": { "constant": "DownloadGitHubRelease@0", "ignore-case": true } }, "task-task-153": { + "description": "Run shell commands or a script on a remote machine using SSH", "string": { "constant": "SSH@0", "ignore-case": true } }, "task-task-154": { + "description": "Publish (upload) a file or directory as a named artifact for the current run", "string": { "constant": "PublishPipelineArtifact@1", "ignore-case": true } }, "task-task-155": { + "description": "Publish a local directory or file as a named artifact for the current pipeline", "string": { "constant": "PublishPipelineArtifact@0", "ignore-case": true } }, "task-task-156": { + "description": "[DEPRECATED] Fetch the Quality Profile from SonarQube to configure the analysis", "string": { "constant": "SonarQubePreBuild@1", "ignore-case": true } }, "task-task-157": { + "description": "Download artifacts from a file share, like \\\\share\\drop", "string": { "constant": "DownloadFileshareArtifacts@1", "ignore-case": true } }, "task-task-158": { + "description": "Deploy, configure, update a Kubernetes cluster in Azure Container Service by running kubectl commands", "string": { "constant": "Kubernetes@0", "ignore-case": true } }, "task-task-159": { + "description": "Deploy, configure, update a Kubernetes cluster in Azure Container Service by running kubectl commands", "string": { "constant": "Kubernetes@1", "ignore-case": true } }, "task-task-160": { + "description": "Build and deploy an Azure IoT Edge image", "string": { "constant": "AzureIoTEdge@2", "ignore-case": true } }, "task-task-161": { + "description": "Deploy a Docker Compose application to an Azure Service Fabric cluster", "string": { "constant": "ServiceFabricComposeDeploy@0", "ignore-case": true } }, "task-task-162": { + "description": "Sign and align Android APK files", "string": { "constant": "AndroidSigning@1", "ignore-case": true } }, "task-task-163": { + "description": "Sign and align Android APK files", "string": { "constant": "AndroidSigning@2", "ignore-case": true } }, "task-task-164": { + "description": "Sign and align Android APK files", "string": { "constant": "AndroidSigning@3", "ignore-case": true } }, "task-task-165": { + "description": "Download build and pipeline artifacts", "string": { "constant": "DownloadPipelineArtifact@2", "ignore-case": true } }, "task-task-166": { + "description": "Downloads an artifact associated with a pipeline", "string": { "constant": "DownloadPipelineArtifact@0", "ignore-case": true } }, "task-task-167": { + "description": "Download a named artifact from a pipeline to a local path", "string": { "constant": "DownloadPipelineArtifact@1", "ignore-case": true } }, "task-task-168": { + "description": "Use the specified version of Python from the tool cache, optionally adding it to the PATH", "string": { "constant": "UsePythonVersion@0", "ignore-case": true } }, "task-task-169": { + "description": "Run a PowerShell script in the context of an Azure Service Fabric cluster connection", "string": { "constant": "ServiceFabricPowerShell@1", "ignore-case": true } }, "task-task-170": { + "description": "Run tests with Visual Studio test runner", "string": { "constant": "VSTest@1", "ignore-case": true } }, "task-task-171": { + "description": "Run unit and functional tests (Selenium, Appium, Coded UI test, etc.) using the Visual Studio Test (VsTest) runner. Test frameworks that have a Visual Studio test adapter such as MsTest, xUnit, NUnit, Chutzpah (for JavaScript tests using QUnit, Mocha and Jasmine), etc. can be run. Tests can be distributed on multiple agents using this task (version 2).", "string": { "constant": "VSTest@2", "ignore-case": true } }, "task-task-172": { + "description": "Run unit and functional tests (Selenium, Appium, Coded UI test, etc.) using the Visual Studio Test (VsTest) runner. Test frameworks that have a Visual Studio test adapter such as MsTest, xUnit, NUnit, Chutzpah (for JavaScript tests using QUnit, Mocha and Jasmine), etc. can be run. Tests can be distributed on multiple agents using this task (version 2).", "string": { "constant": "VSTest@3", "ignore-case": true } }, "task-task-173": { + "description": "[PREVIEW] Pause a pipeline run to wait for manual interaction. Works only with YAML pipelines.", "string": { "constant": "ManualValidation@1", "ignore-case": true } }, "task-task-174": { + "description": "[PREVIEW] Pause a pipeline run to wait for manual interaction. Works only with YAML pipelines.", "string": { "constant": "ManualValidation@0", "ignore-case": true } }, "task-task-175": { + "description": "Build with Apache Ant", "string": { "constant": "Ant@1", "ignore-case": true } }, "task-task-176": { + "description": "Deprecated: Instead, use the 'Visual Studio Test' task to run unit and functional tests", "string": { "constant": "DeployVisualStudioTestAgent@2", "ignore-case": true } }, "task-task-177": { + "description": "Deploy and configure Test Agent to run tests on a set of machines", "string": { "constant": "DeployVisualStudioTestAgent@1", "ignore-case": true } }, "task-task-178": { + "description": "Create and activate a Conda environment", "string": { "constant": "CondaEnvironment@0", "ignore-case": true } }, "task-task-179": { + "description": "This task is deprecated. Use `conda` directly in script to work with Anaconda environments.", "string": { "constant": "CondaEnvironment@1", "ignore-case": true } }, "task-task-180": { + "description": "Run a Windows command or batch script and optionally allow it to change the environment", "string": { "constant": "BatchScript@1", "ignore-case": true } }, "task-task-181": { + "description": "Install npm packages from GitHub.", "string": { "constant": "DownloadGithubNpmPackage@1", "ignore-case": true } }, "task-task-182": { + "description": "Build with MSBuild and set the Visual Studio version property", "string": { "constant": "VSBuild@1", "ignore-case": true } }, "task-task-183": { + "description": "Download Azure Key Vault secrets", "string": { "constant": "AzureKeyVault@1", "ignore-case": true } }, "task-task-184": { + "description": "Download Azure Key Vault secrets", "string": { "constant": "AzureKeyVault@2", "ignore-case": true } }, "task-task-185": { + "description": "Acquire a specific version of the .NET Core SDK from the internet or local cache and add it to the PATH", "string": { "constant": "DotNetCoreInstaller@0", "ignore-case": true } }, "task-task-186": { + "description": "Acquires a specific version of the .NET Core SDK from the internet or the local cache and adds it to the PATH. Use this task to change the version of .NET Core used in subsequent tasks. Additionally provides proxy support.", "string": { "constant": "UseDotNet@2", "ignore-case": true } }, "task-task-187": { + "description": "Acquire a specific version of the .NET Core SDK from the internet or local cache and add it to the PATH", "string": { "constant": "DotNetCoreInstaller@1", "ignore-case": true } }, "task-task-188": { + "description": "Start, stop, restart, slot swap, slot delete, install site extensions or enable continuous monitoring for an Azure App Service", "string": { "constant": "AzureAppServiceManage@0", "ignore-case": true } }, "task-task-189": { + "description": "Helps to install kubelogin", "string": { "constant": "KubeloginInstaller@0", "ignore-case": true } }, "task-task-190": { + "description": "Install Azure Func Core Tools", "string": { "constant": "FuncToolsInstaller@0", "ignore-case": true } }, "task-task-191": { + "description": "Replace tokens with variable values in XML or JSON configuration files", "string": { "constant": "FileTransform@2", "ignore-case": true } }, "task-task-192": { + "description": "Replace tokens with variable values in XML or JSON configuration files", "string": { "constant": "FileTransform@1", "ignore-case": true } }, "task-task-193": { + "description": "Extract a variety of archive and compression files such as .7z, .rar, .tar.gz, and .zip", "string": { "constant": "ExtractFiles@1", "ignore-case": true } }, "task-task-194": { + "description": "Build an Android app with Xamarin", "string": { "constant": "XamarinAndroid@1", "ignore-case": true } }, "task-task-195": { + "description": "Publish Build artifacts to the server or a file share", "string": { "constant": "PublishBuildArtifacts@0", "ignore-case": true } }, "task-task-196": { + "description": "[DEPRECATED] Use the Copy Files task and the Publish Build Artifacts task instead", "string": { "constant": "CopyPublishBuildArtifacts@1", "ignore-case": true } }, "task-task-197": { + "description": "Download a package from a package management feed in Azure Artifacts", "string": { "constant": "DownloadPackage@1", "ignore-case": true } }, "task-task-198": { + "description": "Download a package from a package management feed in Azure Artifacts", "string": { "constant": "DownloadPackage@0", "ignore-case": true } }, "task-task-199": { + "description": "Deploy, start, stop, delete Azure Resource Groups", "string": { "constant": "AzureResourceGroupDeployment@1", "ignore-case": true } }, "task-task-200": { + "description": "Deploy an Azure Resource Manager (ARM) template to a resource group and manage virtual machines", "string": { "constant": "AzureResourceGroupDeployment@2", "ignore-case": true } }, "task-task-201": { + "description": "Deploy an Azure Resource Manager (ARM) template to all the deployment scopes", "string": { "constant": "AzureResourceManagerTemplateDeployment@3", "ignore-case": true } }, "task-task-202": { + "description": "Invoke REST API as a part of your process.", "string": { "constant": "InvokeRESTAPI@0", "ignore-case": true } }, "task-task-203": { + "description": "Invoke a REST API as a part of your pipeline.", "string": { "constant": "InvokeRESTAPI@1", "ignore-case": true } }, "task-task-204": { + "description": "Archive files using compression formats such as .7z, .rar, .tar.gz, and .zip.", "string": { "constant": "ArchiveFiles@1", "ignore-case": true } }, "task-task-205": { + "description": "Compress files into .7z, .tar.gz, or .zip", "string": { "constant": "ArchiveFiles@2", "ignore-case": true } }, "task-task-206": { + "description": "Write a comment to your Github entity i.e. issue or a Pull Request (PR)", "string": { "constant": "GitHubComment@0", "ignore-case": true } }, "task-task-207": { + "description": "Copy files from a source folder to a target folder using patterns matching file paths (not folder paths)", "string": { "constant": "CopyFiles@2", "ignore-case": true } }, "task-task-208": { + "description": "Copy files from source folder to target folder using minimatch patterns (The minimatch patterns will only match file paths, not folder paths)", "string": { "constant": "CopyFiles@1", "ignore-case": true } }, "task-task-209": { + "description": "Run your scripts and make changes to your Azure Database for MySQL", "string": { "constant": "AzureMysqlDeployment@1", "ignore-case": true } }, "task-task-210": { + "description": "Run an npm command. Use NpmAuthenticate@0 task for latest capabilities.", "string": { "constant": "Npm@0", "ignore-case": true } }, "task-task-211": { + "description": "Install and publish npm packages, or run an npm command. Supports npmjs.com and authenticated registries like Azure Artifacts.", "string": { "constant": "Npm@1", "ignore-case": true } }, "task-task-212": { + "description": "[PREVIEW] Build and deploy an Azure Static Web App", "string": { "constant": "AzureStaticWebApp@0", "ignore-case": true } }, "task-task-213": { + "description": "Finds or downloads and caches the specified version spec of Node.js and adds it to the PATH", "string": { "constant": "NodeTool@0", "ignore-case": true } }, "task-task-214": { + "description": "Set up a Node.js environment and add it to the PATH, additionally providing proxy support", "string": { "constant": "UseNode@1", "ignore-case": true } }, "task-task-215": { + "description": "Deploy a SQL Server database using DACPAC", "string": { "constant": "SqlServerDacpacDeployment@1", "ignore-case": true } }, "task-task-216": { + "description": "Acquire the test platform from nuget.org or the tool cache. Satisfies the ‘vstest’ demand and can be used for running tests and collecting diagnostic data using the Visual Studio Test task.", "string": { "constant": "VisualStudioTestPlatformInstaller@1", "ignore-case": true } }, "task-task-217": { + "description": "Sends a message to Azure Service Bus using a service connection (no agent is required)", "string": { "constant": "PublishToAzureServiceBus@1", "ignore-case": true } }, "task-task-218": { + "description": "Sends a message to Azure Service Bus using an Azure Resource Manager service connection (no agent is required)", "string": { "constant": "PublishToAzureServiceBus@2", "ignore-case": true } }, "task-task-219": { + "description": "Sends a message to azure service bus using a service connection (no agent required).", "string": { "constant": "PublishToAzureServiceBus@0", "ignore-case": true } }, "task-task-220": { + "description": "Use Kubernetes manifest files to deploy to clusters or even bake the manifest files to be used for deployments using Helm charts", "string": { "constant": "KubernetesManifest@0", "ignore-case": true } }, "task-task-221": { + "description": "Use Kubernetes manifest files to deploy to clusters or even bake the manifest files to be used for deployments using Helm charts", "string": { "constant": "KubernetesManifest@1", "ignore-case": true } }, "task-task-222": { + "description": "Download files that were saved as artifacts of a completed build", "string": { "constant": "DownloadBuildArtifacts@1", "ignore-case": true } }, "task-task-223": { + "description": "Download files that were saved as artifacts of a completed build", "string": { "constant": "DownloadBuildArtifacts@0", "ignore-case": true } }, "task-task-224": { + "description": "Install CocoaPods dependencies for Swift and Objective-C Cocoa projects", "string": { "constant": "CocoaPods@0", "ignore-case": true } }, "task-task-225": { + "description": "Deploy applications to Azure Spring Apps and manage deployments.", "string": { "constant": "AzureSpringCloud@0", "ignore-case": true } }, "task-task-226": { + "description": "Deploy an Azure Web App for Linux or Windows", "string": { "constant": "AzureWebApp@1", "ignore-case": true } }, "task-task-227": { + "description": "Run Azure CLI commands against an Azure subscription in a Shell script when running on Linux agent or Batch script when running on Windows agent.", "string": { "constant": "AzureCLI@1", "ignore-case": true } }, "task-task-228": { + "description": "Run Azure CLI commands against an Azure subscription in a PowerShell Core/Shell script when running on Linux agent or PowerShell/PowerShell Core/Batch script when running on Windows agent.", "string": { "constant": "AzureCLI@2", "ignore-case": true } }, "task-task-229": { + "description": "Run a Shell or Batch script with Azure CLI commands against an azure subscription", "string": { "constant": "AzureCLI@0", "ignore-case": true } }, "task-task-230": { + "description": "Create, edit, or delete a GitHub release", "string": { "constant": "GitHubRelease@0", "ignore-case": true } }, "task-task-231": { + "description": "Create, edit, or delete a GitHub release", "string": { "constant": "GitHubRelease@1", "ignore-case": true } }, "task-task-232": { + "description": "Use cURL to upload files with FTP, FTPS, SFTP, HTTP, and more.", "string": { "constant": "cURLUploader@1", "ignore-case": true } }, "task-task-233": { + "description": "Use cURL's supported protocols to upload files", "string": { "constant": "cURLUploader@2", "ignore-case": true } }, "task-task-234": { + "description": "Update/Add App settings an Azure Web App for Linux or Windows", "string": { "constant": "AzureAppServiceSettings@1", "ignore-case": true } }, "task-task-235": { + "description": "Download or publish Universal Packages", "string": { "constant": "UniversalPackages@0", "ignore-case": true } }, "task-task-236": { + "description": "Security and compliance assessment for Azure Policy", "string": { "constant": "AzurePolicyCheckGate@0", "ignore-case": true } }, "task-task-237": { + "description": "Deploy Azure function to Kubernetes cluster.", "string": { "constant": "AzureFunctionOnKubernetes@0", "ignore-case": true } }, "task-task-238": { + "description": "Deploy Azure function to Kubernetes cluster.", "string": { "constant": "AzureFunctionOnKubernetes@1", "ignore-case": true } }, "task-task-239": { + "description": "Run a shell script using Bash", "string": { "constant": "ShellScript@2", "ignore-case": true } }, "task-task-240": { + "description": "Run a Bash script on macOS, Linux, or Windows", "string": { "constant": "Bash@3", "ignore-case": true } }, "task-task-241": { + "description": "Run a shell script using bash", "string": { "constant": "ShellScript@1", "ignore-case": true } }, "task-task-242": { + "description": "Publish build artifacts to Azure Pipelines or a Windows file share", "string": { "constant": "PublishBuildArtifacts@1", "ignore-case": true } }, "task-task-243": { + "description": "Install an SSH key prior to a build or deployment", "string": { "constant": "InstallSSHKey@0", "ignore-case": true } }, "task-task-244": { + "description": "Deploy a virtual machine scale set image", "string": { "constant": "AzureVmssDeployment@0", "ignore-case": true } }, "task-task-245": { + "description": "Create or update Azure App Service using Azure PowerShell", "string": { "constant": "AzureWebPowerShellDeployment@1", "ignore-case": true } }, "task-task-246": { + "description": "Authentication task for the conda client", "string": { "constant": "CondaAuthenticate@0", "ignore-case": true } }, "task-task-247": { + "description": "Deploy an Azure Cloud Service", "string": { "constant": "AzureCloudPowerShellDeployment@1", "ignore-case": true } }, "task-task-248": { + "description": "Deploy an Azure Cloud Service", "string": { "constant": "AzureCloudPowerShellDeployment@2", "ignore-case": true } }, "task-task-249": { + "description": "Authentication task for the cargo client used for installing Cargo crates distribution", "string": { "constant": "CargoAuthenticate@0", "ignore-case": true } }, "task-task-250": { + "description": "Delete folders, or files matching a pattern", "string": { "constant": "DeleteFiles@1", "ignore-case": true } }, "task-task-251": { + "description": "Run the gulp Node.js streaming task-based build system", "string": { "constant": "gulp@0", "ignore-case": true } }, "task-task-252": { + "description": "Run the gulp Node.js streaming task-based build system", "string": { "constant": "gulp@1", "ignore-case": true } }, "task-task-253": { + "description": "Run a quick web performance test in the cloud with Azure Pipelines", "string": { "constant": "QuickPerfTest@1", "ignore-case": true } }, "task-task-254": { + "description": "Create or update websites, web apps, virtual directories, or application pools", "string": { "constant": "IISWebAppManagementOnMachineGroup@0", "ignore-case": true } }, "task-task-255": { + "description": "Install Docker CLI on agent machine.", "string": { "constant": "DockerInstaller@0", "ignore-case": true @@ -5238,38 +6056,46 @@ } }, "task-displayName": { + "description": "Human-readable name for the task", "string": {} }, "task-name": { + "description": "ID of the task instance", "string": { "pattern": "^[_A-Za-z0-9]*$" } }, "task-condition": { + "description": "Evaluate this condition expression to determine whether to run this task", "string": {} }, "task-continueOnError": { + "description": "Continue running the parent job even on failure?", "one-of": [ "azp-boolean", "boolean" ] }, "task-enabled": { + "description": "Run this task when the job runs?", "string": {} }, "task-retryCountOnTaskFailure": { + "description": "Number of retries if the task fails", "one-of": [ "azp-number", "number" ] }, "task-timeoutInMinutes": { + "description": "Time to wait for this task to complete before the server kills it", "one-of": [ "azp-number", "number" ] }, "task-inputs": { + "description": "Task-specific inputs", "mapping": { "properties": {}, "loose-key-type": "string", @@ -5277,6 +6103,7 @@ } }, "task-env": { + "description": "Variables to map into the process's environment", "mapping": { "properties": {}, "loose-key-type": "string", @@ -5292,32 +6119,42 @@ "required": true }, "displayName": { - "type": "task-displayName" + "type": "task-displayName", + "description": "Human-readable name for the task" }, "name": { - "type": "task-name" + "type": "task-name", + "description": "ID of the task instance" }, "condition": { - "type": "task-condition" + "type": "task-condition", + "description": "Evaluate this condition expression to determine whether to run this task" }, "continueOnError": { - "type": "task-continueOnError" + "type": "task-continueOnError", + "description": "Continue running the parent job even on failure?" }, "enabled": { - "type": "task-enabled" + "type": "task-enabled", + "description": "Run this task when the job runs?" }, "retryCountOnTaskFailure": { - "type": "task-retryCountOnTaskFailure" + "type": "task-retryCountOnTaskFailure", + "description": "Number of retries if the task fails" }, "timeoutInMinutes": { - "type": "task-timeoutInMinutes" + "type": "task-timeoutInMinutes", + "description": "Time to wait for this task to complete before the server kills it" }, "inputs": { - "type": "task-inputs" + "type": "task-inputs", + "description": "Task-specific inputs" }, "env": { - "type": "task-env" - } + "type": "task-env", + "description": "Variables to map into the process's environment" + }, + "target": "stepTarget" } } }, @@ -5345,10 +6182,12 @@ "mapping": { "properties": { "parameters": { - "type": "templateParameters" + "type": "templateParameters", + "description": "Step-specific parameters" }, "steps": { - "type": "steps" + "type": "steps", + "description": "A list of steps to run" } } } diff --git a/src/Sdk/AzurePipelines/json-schema-transform.js b/src/Sdk/AzurePipelines/json-schema-transform.js index 896138772cc..9df39c10a83 100644 --- a/src/Sdk/AzurePipelines/json-schema-transform.js +++ b/src/Sdk/AzurePipelines/json-schema-transform.js @@ -112,6 +112,9 @@ var addDefinition = function(id) { var d = schema.definitions[id] var target = {} var deprecated = false + if(d.description) { + target.description = d.description; + } if(d.type === "object" || d.properties) { target.mapping = {} target.mapping.properties = {} @@ -122,7 +125,7 @@ var addDefinition = function(id) { } target.mapping.properties[pname] = {} var ref = pval["$ref"] - if(ref && ref.indexOf(defprefix) === 0) { + if(ref && ref.indexOf(defprefix) === 0 && !pval.enum) { target.mapping.properties[pname].type = getId(ref.substring(defprefix.length)) } else { schema.definitions[id + "-" + pname] = pval @@ -131,6 +134,10 @@ var addDefinition = function(id) { deprecated = true } } + var description = pval.description; + if(description) { + target.mapping.properties[pname].description = description; + } if(d.firstProperty && d.firstProperty.includes(pname)) { target.mapping.properties[pname]["first-property"] = true target.mapping.properties[pname]["required"] = true @@ -144,7 +151,20 @@ var addDefinition = function(id) { target.mapping["loose-value-type"] = "any" } targetSchema.definitions[getId(id)] = target - } else if(d.type === "string") { + } else if(d.enum && d.enum.length === 1) { + target.string = {} + target.string.constant = d.enum[0] + if(d.ignoreCase === "value" || d.ignoreCase === "all") { + target.string["ignore-case"] = true + } + targetSchema.definitions[getId(id)] = target + } else if(d.enum) { + target["allowed-values"] = [] + for(var val of d.enum) { + target["allowed-values"].push(val) + } + targetSchema.definitions[getId(id)] = target + } else if(d.type === "string") { // "referenceName": { // "type": "string", // "pattern": "^[-_A-Za-z0-9]*$" @@ -157,19 +177,6 @@ var addDefinition = function(id) { target.string["ignore-case"] = true } - targetSchema.definitions[getId(id)] = target - } else if(d.enum && d.enum.length === 1) { - target.string = {} - target.string.constant = d.enum[0] - if(d.ignoreCase === "value" || d.ignoreCase === "all") { - target.string["ignore-case"] = true - } - targetSchema.definitions[getId(id)] = target - } else if(d.enum) { - target["allowed-values"] = [] - for(var val of d.enum) { - target["allowed-values"].push(val) - } targetSchema.definitions[getId(id)] = target } else if(d.anyOf) { target["one-of"] = [] @@ -321,6 +328,7 @@ targetSchema.definitions["task-task"] = targetSchema.definitions["nonEmptyString targetSchema.definitions["azp-any"]["one-of"].push("boolean", "null", "number") targetSchema.definitions["any_allowExpressions"]["one-of"].push("boolean", "null", "number") +targetSchema.definitions["task"].mapping.properties["target"] = "stepTarget" console.log(JSON.stringify(targetSchema, null, 4)) \ No newline at end of file diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectReader.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectReader.cs index 4f09806a1ca..684ff55b98b 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectReader.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/IObjectReader.cs @@ -13,11 +13,11 @@ internal interface IObjectReader Boolean AllowSequenceStart(out SequenceToken token); - Boolean AllowSequenceEnd(); + Boolean AllowSequenceEnd(SequenceToken token = null); Boolean AllowMappingStart(out MappingToken token); - Boolean AllowMappingEnd(); + Boolean AllowMappingEnd(MappingToken token = null); void ValidateStart(); diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs index 596d44c5e6e..e5e71267813 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs @@ -47,7 +47,9 @@ protected Definition(MappingToken definition) } else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal)) { + var description = definition[i].Value.AssertString($"description"); definition.RemoveAt(i); + Description = description.Value; } else if (String.Equals(definitionKey.Value, "actionsIfExpression", StringComparison.Ordinal)) { @@ -64,6 +66,8 @@ protected Definition(MappingToken definition) public bool ActionsIfExpression { get; private set; } + public string Description { get; } + internal abstract DefinitionType DefinitionType { get; } /// diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs index 538f46417b7..f419cd536c7 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs @@ -39,7 +39,7 @@ internal OneOfDefinition(MappingToken definition) } } - internal override DefinitionType DefinitionType => DefinitionType.Mapping; + internal override DefinitionType DefinitionType => DefinitionType.OneOf; internal List OneOf { get; } = new List(); diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateContext.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateContext.cs index 35ed2ea36b2..988315393b8 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateContext.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateContext.cs @@ -4,11 +4,26 @@ using System.Linq; using System.Threading; using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.Expressions2.Sdk.Functions.v1; +using GitHub.DistributedTask.Expressions2.Tokens; using GitHub.DistributedTask.ObjectTemplating.Schema; using GitHub.DistributedTask.ObjectTemplating.Tokens; namespace GitHub.DistributedTask.ObjectTemplating { + internal class AutoCompleteEntry { + public int Depth { get; set; } + public TemplateToken Token { get; set; } + public Definition[] Definitions { get; set; } + public string[] AllowedContext { get; set; } + public List Tokens { get; set; } + public int Index { get; set; } = -1; + public bool SemTokensOnly { get; set; } + public (int, int)[] Mapping { get; set; } + public Dictionary<(int, int), int> RMapping { get; internal set; } + + } + /// /// Context object that is flowed through while loading and evaluating object templates /// @@ -18,6 +33,40 @@ public sealed class TemplateContext public ExpressionFlags Flags { get; set; } internal CancellationToken CancellationToken { get; set; } + public int? Column { get; set; } + public int? Row { get; set; } + internal List AutoCompleteMatches { get; set; } + public List SemTokens { get; set; } = new List(); + public int LastRow { get; set; } = 1; + public int LastColumn { get; set; } = 1; + + public void AddSemToken(int row, int column, int len, int type, int mod) { + if(row - LastRow < 0 || ((row - LastRow) != 0 ? column - 1: column - LastColumn) < 0) { + // Insert + int i = 0; + int r = 1; + while(r + SemTokens[i * 5] < row) { + r += SemTokens[i++ * 5]; + } + int c = 1; + while(c + SemTokens[i * 5 + 1] <= column && r + SemTokens[i * 5] == row) { + c += SemTokens[i * 5 + 1]; + r += SemTokens[i * 5]; + i++; + } + if(SemTokens[i * 5] == 0) { + SemTokens[i * 5 + 1] -= (row - r) != 0 ? column - 1: column - c; + } else { + SemTokens[i * 5] -= row - r; + } + SemTokens.InsertRange(i * 5, new int[] { row - r, (row - r) != 0 ? column - 1: column - c, len, type, mod }); + } else { + SemTokens.AddRange(new int[] { row - LastRow, (row - LastRow) != 0 ? column - 1: column - LastColumn, len, type, mod}); + LastRow = row; + LastColumn = column; + } + } + internal TemplateValidationErrors Errors { get diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs index 6ee697fbf44..754a8ff394a 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs @@ -93,6 +93,10 @@ private TemplateToken Evaluate(DefinitionInfo definition) { if (scalar is LiteralToken literal) { + var entry = m_context.AutoCompleteMatches?.FirstOrDefault(m => m.Token == scalar); + if(entry != null) { + entry.Definitions = entry.Definitions.Append(definition.Definition).ToArray(); + } Validate(ref literal, definition); return literal; } @@ -106,6 +110,11 @@ private TemplateToken Evaluate(DefinitionInfo definition) if (m_unraveler.AllowSequenceStart(definition.Expand, out SequenceToken sequence)) { var sequenceDefinition = definition.Get().FirstOrDefault(); + var entry = m_context.AutoCompleteMatches?.FirstOrDefault(m => m.Token.FileId == sequence.FileId && m.Token.Line == sequence.Line && m.Token.Column == sequence.Column && m.Token.Type == sequence.Type); + if(entry != null) { + entry.Token = sequence; + entry.Definitions = entry.Definitions.Append(definition.Definition).ToArray(); + } // Legal if (sequenceDefinition != null) @@ -139,6 +148,11 @@ private TemplateToken Evaluate(DefinitionInfo definition) if (m_unraveler.AllowMappingStart(definition.Expand, out MappingToken mapping)) { var mappingDefinitions = definition.Get().ToList(); + var entry = m_context.AutoCompleteMatches?.FirstOrDefault(m => m.Token.FileId == mapping.FileId && m.Token.Line == mapping.Line && m.Token.Column == mapping.Column && m.Token.Type == mapping.Type); + if(entry != null) { + entry.Token = mapping; + entry.Definitions = entry.Definitions.Append(definition.Definition).ToArray(); + } // Legal if (mappingDefinitions.Count > 0) @@ -249,6 +263,13 @@ private void HandleMappingWithWellKnownProperties( m_unraveler.SkipMappingValue(); } + if(m_context.AutoCompleteMatches != null) { + var aentry = m_context.AutoCompleteMatches.Where(a => a.Token == mapping).FirstOrDefault(); + if(aentry != null) { + aentry.Definitions = mappingDefinitions.Cast().ToArray(); + } + } + // Only one if (mappingDefinitions.Count > 1 && !hasExpressionKey) { diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateMemory.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateMemory.cs index f2d30c08628..aeba2ebed36 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateMemory.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateMemory.cs @@ -23,6 +23,8 @@ internal TemplateMemory( public Int32 MaxBytes => m_maxBytes; + public Int32 Depth => m_depth; + internal void AddBytes(Int32 bytes) { checked diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs index 40244b6ba35..3ecfc4ca002 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs @@ -6,6 +6,9 @@ using GitHub.DistributedTask.Expressions2.Sdk; using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.ObjectTemplating.Schema; +using GitHub.DistributedTask.Expressions2.Tokens; +using System.IO; +using Runner.Server.Azure.Devops; namespace GitHub.DistributedTask.ObjectTemplating { @@ -68,6 +71,17 @@ internal static TemplateToken Read( return result; } + private bool Match(TemplateToken token) { + if(token.PreWhiteSpace != null) { + return m_context.Row > token.PreWhiteSpace.Line || m_context.Row == token.PreWhiteSpace.Line && m_context.Column >= token.PreWhiteSpace.Character; + } + return m_context.Row > token.Line || m_context.Row == token.Line && m_context.Column >= token.Column; + } + + private bool MatchPost(TemplateToken token) { + return m_context.Row < token.PostWhiteSpace.Line || m_context.Row == token.PostWhiteSpace.Line && m_context.Column <= token.PostWhiteSpace.Character; + } + private TemplateToken ReadValue(DefinitionInfo definition) { m_memory.IncrementEvents(); @@ -84,6 +98,15 @@ private TemplateToken ReadValue(DefinitionInfo definition) // Sequence if (m_objectReader.AllowSequenceStart(out SequenceToken sequence)) { + if(m_context.AutoCompleteMatches != null && Match(sequence)) { + m_context.AutoCompleteMatches.RemoveAll(m => m.Depth >= m_memory.Depth); + m_context.AutoCompleteMatches.Add(new AutoCompleteEntry { + Depth = m_memory.Depth, + Token = sequence, + AllowedContext = definition.AllowedContext, + Definitions = new [] { definition.Definition } + }); + } m_memory.IncrementDepth(); m_memory.AddBytes(sequence); @@ -95,7 +118,7 @@ private TemplateToken ReadValue(DefinitionInfo definition) var itemDefinition = new DefinitionInfo(definition, sequenceDefinition.ItemType); // Add each item - while (!m_objectReader.AllowSequenceEnd()) + while (!m_objectReader.AllowSequenceEnd(sequence)) { var item = ReadValue(itemDefinition); sequence.Add(item); @@ -108,12 +131,19 @@ private TemplateToken ReadValue(DefinitionInfo definition) m_context.Error(sequence, TemplateStrings.UnexpectedSequenceStart()); // Skip each item - while (!m_objectReader.AllowSequenceEnd()) + while (!m_objectReader.AllowSequenceEnd(sequence)) { SkipValue(); } } + if(m_context.AutoCompleteMatches != null && sequence.PostWhiteSpace != null && !MatchPost(sequence)) { + var completion = m_context.AutoCompleteMatches.FirstOrDefault(m => m.Token == sequence); + if(completion != null) { + m_context.AutoCompleteMatches.RemoveAll(m => m.Depth >= completion.Depth); + } + } + m_memory.DecrementDepth(); return sequence; } @@ -121,6 +151,15 @@ private TemplateToken ReadValue(DefinitionInfo definition) // Mapping if (m_objectReader.AllowMappingStart(out MappingToken mapping)) { + if(m_context.AutoCompleteMatches != null && Match(mapping)) { + m_context.AutoCompleteMatches.RemoveAll(m => m.Depth >= m_memory.Depth); + m_context.AutoCompleteMatches.Add(new AutoCompleteEntry { + Depth = m_memory.Depth, + Token = mapping, + AllowedContext = definition.AllowedContext, + Definitions = new [] { definition.Definition } + }); + } m_memory.IncrementDepth(); m_memory.AddBytes(mapping); @@ -147,13 +186,20 @@ private TemplateToken ReadValue(DefinitionInfo definition) { m_context.Error(mapping, TemplateStrings.UnexpectedMappingStart()); - while (!m_objectReader.AllowMappingEnd()) + while (!m_objectReader.AllowMappingEnd(mapping)) { SkipValue(); SkipValue(); } } + if(m_context.AutoCompleteMatches != null && mapping.PostWhiteSpace != null && !MatchPost(mapping)) { + var completion = m_context.AutoCompleteMatches.FirstOrDefault(m => m.Token == mapping); + if(completion != null) { + m_context.AutoCompleteMatches.RemoveAll(m => m.Depth >= completion.Depth); + } + } + m_memory.DecrementDepth(); return mapping; } @@ -195,14 +241,26 @@ private void HandleMappingWithWellKnownProperties( TemplateToken nextValue; var anyDefinition = new DefinitionInfo(definition, TemplateConstants.Any); if(nextKeyScalar is EachExpressionToken eachexp) { - var def = new DefinitionInfo(definition, "any"); + var def = m_context.AutoCompleteMatches != null ? new DefinitionInfo(definition) : new DefinitionInfo(definition, "any"); def.AllowedContext = definition.AllowedContext.Append(eachexp.Variable).ToArray(); + if(m_context.AutoCompleteMatches != null && definition.Parent is SequenceDefinition) { + var oneOf = new OneOfDefinition(); + oneOf.OneOf.Add(definition.ParentName); + oneOf.OneOf.Add(definition.Name); + def.Definition = oneOf; + } nextValue = ReadValue(def); } else if(nextKeyScalar is ConditionalExpressionToken || nextKeyScalar is InsertExpressionToken && (m_context.Flags & Expressions2.ExpressionFlags.AllowAnyForInsert) != Expressions2.ExpressionFlags.None) { - var def = new DefinitionInfo(definition, "any"); + var def = m_context.AutoCompleteMatches != null ? new DefinitionInfo(definition) : new DefinitionInfo(definition, "any"); + if(m_context.AutoCompleteMatches != null && definition.Parent is SequenceDefinition) { + var oneOf = new OneOfDefinition(); + oneOf.OneOf.Add(definition.ParentName); + oneOf.OneOf.Add(definition.Name); + def.Definition = oneOf; + } nextValue = ReadValue(def); } else { - nextValue = ReadValue(anyDefinition); + nextValue = ReadValue(m_context.AutoCompleteMatches != null ? new DefinitionInfo(definition) : anyDefinition); } mapping.Add(nextKeyScalar, nextValue); } @@ -261,6 +319,13 @@ private void HandleMappingWithWellKnownProperties( SkipValue(); } + if(m_context.AutoCompleteMatches != null) { + var aentry = m_context.AutoCompleteMatches.Where(a => a.Token == mapping).FirstOrDefault(); + if(aentry != null) { + aentry.Definitions = mappingDefinitions.Cast().ToArray(); + } + } + // Only one if (mappingDefinitions.Count > 1 && !hasExpressionKey) { @@ -307,7 +372,7 @@ private void HandleMappingWithWellKnownProperties( } } } - ExpectMappingEnd(); + ExpectMappingEnd(mapping); } private void HandleMappingWithAllLooseProperties( @@ -331,14 +396,26 @@ private void HandleMappingWithAllLooseProperties( { m_memory.AddBytes(nextKeyScalar); if(nextKeyScalar is EachExpressionToken eachexp) { - var def = new DefinitionInfo(mappingDefinition, "any"); + var def = m_context.AutoCompleteMatches != null ? new DefinitionInfo(mappingDefinition) : new DefinitionInfo(mappingDefinition, "any"); def.AllowedContext = valueDefinition.AllowedContext.Append(eachexp.Variable).ToArray(); + if(m_context.AutoCompleteMatches != null && mappingDefinition.Parent is SequenceDefinition) { + var oneOf = new OneOfDefinition(); + oneOf.OneOf.Add(mappingDefinition.ParentName); + oneOf.OneOf.Add(mappingDefinition.Name); + def.Definition = oneOf; + } nextValue = ReadValue(def); } else if(nextKeyScalar is ConditionalExpressionToken || nextKeyScalar is InsertExpressionToken && (m_context.Flags & Expressions2.ExpressionFlags.AllowAnyForInsert) != Expressions2.ExpressionFlags.None) { - var def = new DefinitionInfo(mappingDefinition, "any"); + var def = m_context.AutoCompleteMatches != null ? new DefinitionInfo(mappingDefinition) : new DefinitionInfo(mappingDefinition, "any"); + if(m_context.AutoCompleteMatches != null && mappingDefinition.Parent is SequenceDefinition) { + var oneOf = new OneOfDefinition(); + oneOf.OneOf.Add(mappingDefinition.ParentName); + oneOf.OneOf.Add(mappingDefinition.Name); + def.Definition = oneOf; + } nextValue = ReadValue(def); } else { - nextValue = ReadValue(valueDefinition); + nextValue = ReadValue(m_context.AutoCompleteMatches != null ? new DefinitionInfo(mappingDefinition) : valueDefinition); } mapping.Add(nextKeyScalar, nextValue); } @@ -375,12 +452,12 @@ private void HandleMappingWithAllLooseProperties( mapping.Add(nextKey, nextValue); } - ExpectMappingEnd(); + ExpectMappingEnd(mapping); } - private void ExpectMappingEnd() + private void ExpectMappingEnd(MappingToken token) { - if (!m_objectReader.AllowMappingEnd()) + if (!m_objectReader.AllowMappingEnd(token)) { throw new Exception("Expected mapping end"); // Should never happen } @@ -507,6 +584,65 @@ private ScalarToken ParseScalar( LiteralToken token, DefinitionInfo definitionInfo) { + AutoCompleteEntry completion = null;//m_context.Row >= token.Line + completion = new AutoCompleteEntry { + Depth = m_memory.Depth, + Token = token, + AllowedContext = definitionInfo.AllowedContext, + Definitions = new [] { definitionInfo.Definition } + }; + if(m_context.AutoCompleteMatches != null && Match(token) && (token.PostWhiteSpace == null || MatchPost(token) /*(m_context.Row < token.PostWhiteSpace.Line && !(token.PostWhiteSpace.Line == m_context.Row && token.PostWhiteSpace.Character > m_context.Column))*/)) { + m_context.AutoCompleteMatches.RemoveAll(m => m.Depth >= m_memory.Depth); + m_context.AutoCompleteMatches.Add(completion); + } else { + completion.SemTokensOnly = true; + } + if(token is LiteralToken lit && lit.RawData != null) { + var rand = new Random(); + string C = "CX"; + while(lit.RawData.Contains(C)) { + C = rand.Next(255).ToString("X2"); + } + var praw = lit.ToString(); + (int, int)[] mapping = new (int, int)[praw.Length + 1]; + var rmapping = new Dictionary<(int, int), int>(); + Array.Fill(mapping, (-1, -1)); + + int column = lit.Column.Value; + int line = lit.Line.Value; + int ridx = -1; + for(int idx = 0; idx < lit.RawData.Length; idx++) { + if(lit.RawData[idx] == '\n') { + line++; + column = 1; + continue; + } + var xraw = lit.RawData.Insert(idx, C); + + var scanner = new YamlDotNet.Core.Scanner(new StringReader(xraw), true); + try { + while(scanner.MoveNext()) { + if(scanner.Current is YamlDotNet.Core.Tokens.Scalar s) { + var x = s.Value; + var m = x.IndexOf(C); + if(m >= 0 && m < mapping.Length && ridx <= m) { + if(mapping[m] != (-1,-1)) { + rmapping.Remove(mapping[m]); + } + mapping[m] = (line, column); + rmapping[(line, column)] = m; + ridx = m; + } + } + } + } catch { + + } + column++; + } + completion.Mapping = mapping; + completion.RMapping = rmapping; + } var allowedContext = definitionInfo.AllowedContext; var isExpression = definitionInfo.Definition is StringDefinition sdef && sdef.IsExpression; var actionsIfExpression = definitionInfo.Definition.ActionsIfExpression || isExpression; @@ -523,8 +659,11 @@ private ScalarToken ParseScalar( (startExpression = raw.IndexOf(TemplateConstants.OpenExpression)) < 0) // Doesn't contain ${{ { if(!String.IsNullOrEmpty(raw) && isExpression) { + if(completion != null && completion.Index < 0) { + completion.Index = -1; + } // Check if value should still be evaluated as an expression - var expression = ParseExpression(token.Line, token.Column, raw, allowedContext, out Exception ex); + var expression = ParseExpression(completion, token.Line, token.Column, raw, allowedContext, out Exception ex); // Check for error if (ex != null) { m_context.Error(token, ex); @@ -543,6 +682,11 @@ private ScalarToken ParseScalar( // An expression starts here: if (i == startExpression) { + if(completion.Mapping?.Length > startExpression && token.Line != null && token.Column != null) { + var (r, c) = completion.Mapping[startExpression]; + m_context.AddSemToken(r, c, 3, 5, 0); + } + // Find the end of the expression - i.e. }} startExpression = i; var endExpression = -1; @@ -561,18 +705,34 @@ private ScalarToken ParseScalar( } } + bool hasEnd = false; + // Check if not closed if (endExpression < startExpression) { m_context.Error(token, TemplateStrings.ExpressionNotClosed()); - return token; + if(completion == null) { + return token; + } + endExpression = raw.Length + TemplateConstants.CloseExpression.Length - 1; + } else { + hasEnd = true; + } + + if(completion != null && completion.Index < 0) { + completion.Index = - (startExpression + TemplateConstants.OpenExpression.Length + 1); } // Parse the expression var rawExpression = raw.Substring( startExpression + TemplateConstants.OpenExpression.Length, endExpression - startExpression + 1 - TemplateConstants.OpenExpression.Length - TemplateConstants.CloseExpression.Length); - var expression = ParseExpression(token.Line, token.Column, rawExpression, allowedContext, out Exception ex); + var expression = ParseExpression(completion, token.Line, token.Column, rawExpression, allowedContext, out Exception ex); + + if(completion.Mapping?.Length >= endExpression - 1 && hasEnd && token.Line != null && token.Column != null) { + var (r, c) = completion.Mapping[endExpression - 1]; + m_context.AddSemToken(r, c, 2, 5, 0); + } // Check for error if (ex != null) @@ -675,25 +835,125 @@ segments[0] is BasicExpressionToken basicExpression && return new BasicExpressionToken(m_fileId, token.Line, token.Column, $"format('{format}'{args})"); } + private bool AutoCompleteExpression(AutoCompleteEntry completion, int offset, string value, int poffset = 0) { + if(completion != null) + { + LexicalAnalyzer lexicalAnalyzer = new LexicalAnalyzer(value.Substring(offset), m_context.Flags); + Token tkn = null; + var startIndex = -1 - completion.Index + offset; + var lit = completion.Token as LiteralToken; + if(lit.RawData != null) { + var mapping = completion.Mapping; + while(lexicalAnalyzer.TryGetNextToken(ref tkn)) { + var (r, c) = mapping[startIndex + tkn.Index]; + if(tkn.Kind == TokenKind.Function) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 2, 2); + } else if(tkn.Kind == TokenKind.NamedValue) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 0, 1); + } else if(tkn.Kind == TokenKind.PropertyName) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 3, 0); + } else if(tkn.Kind == TokenKind.Boolean || tkn.Kind == TokenKind.Null) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 4, 2); + } else if(tkn.Kind == TokenKind.Number || tkn.Kind == TokenKind.String && tkn.ParsedValue is VersionWrapper) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 4, 4); + } else if(tkn.Kind == TokenKind.StartGroup || tkn.Kind == TokenKind.StartIndex || tkn.Kind == TokenKind.StartParameters || tkn.Kind == TokenKind.EndGroup || tkn.Kind == TokenKind.EndParameters + || tkn.Kind == TokenKind.EndIndex || tkn.Kind == TokenKind.Wildcard || tkn.Kind == TokenKind.Separator || tkn.Kind == TokenKind.LogicalOperator || tkn.Kind == TokenKind.Dereference) { + m_context.AddSemToken(r, c, tkn.RawValue.Length, 5, 0); + } else if(tkn.Kind == TokenKind.String) { + var (er, ec) = mapping[startIndex + tkn.Index + tkn.RawValue.Length]; + // Only add single line string + if(er == r && c < ec) { + m_context.AddSemToken(r, c, ec - c /* May contain escape codes */, 6, 0); + } + // TODO multi line string by splitting them + } + } + } + } + if(completion != null && !completion.SemTokensOnly && m_context.AutoCompleteMatches != null) { + // var idx = GetIdxOfExpression(completion.Token as LiteralToken, m_context.Row.Value, m_context.Column.Value); + var idx = completion.RMapping.TryGetValue((m_context.Row.Value, m_context.Column.Value), out var i) ? i : -1; + var startIndex = -1 - completion.Index + offset; + if(idx != -1 && idx >= startIndex && (idx <= startIndex + value.Length + poffset)) { + LexicalAnalyzer lexicalAnalyzer = new LexicalAnalyzer(value.Substring(offset), m_context.Flags); + Token tkn = null; + List tkns = new List(); + while(lexicalAnalyzer.TryGetNextToken(ref tkn)) { + if(tkn.Index + startIndex > idx) { + break; + } + tkns.Add(tkn); + } + completion.Tokens = tkns; + completion.Index = idx - startIndex; + } + } + return true; + } + + private static int GetIdxOfExpression(LiteralToken lit, int row, int column) + { + var lc = column - lit.Column; + var lr = row - lit.Line; + var rand = new Random(); + string C = "CX"; + while(lit.RawData.Contains(C)) { + C = rand.Next(255).ToString("X2"); + } + var xraw = lit.RawData; + var idx = 0; + for(int i = 0; i < lr; i++) { + var n = xraw.IndexOf('\n', idx); + if(n == -1) { + return -1; + } + idx = n + 1; + } + idx += idx == 0 ? lc ?? 0 : column - 1; + if(idx > xraw.Length) { + return -1; + } + xraw = xraw.Insert(idx, C); + + var scanner = new YamlDotNet.Core.Scanner(new StringReader(xraw), true); + try { + while(scanner.MoveNext()) { + if(scanner.Current is YamlDotNet.Core.Tokens.Scalar s) { + var x = s.Value; + return x.IndexOf(C); + } + } + } catch { + + } + return -1; + } + private ExpressionToken ParseExpression( + AutoCompleteEntry completion, Int32? line, Int32? column, String value, String[] allowedContext, out Exception ex) { + // TODO !!!!! If the expressions parameter is missing in directives provide auto completion + // It's buggy + // Empty expressions like ${{ }} are not auto completed? var trimmed = value.Trim(); // Check if the value is empty if (String.IsNullOrEmpty(trimmed)) { + AutoCompleteExpression(completion, 0, value); ex = new ArgumentException(TemplateStrings.ExpectedExpression()); return null; } + var trimmedNo = value.IndexOf(trimmed); bool extendedDirectives = (m_context.Flags & Expressions2.ExpressionFlags.ExtendedDirectives) != Expressions2.ExpressionFlags.None; // Try to find a matching directive - List parameters; + List<(int, String)> parameters; if (MatchesDirective(trimmed, TemplateConstants.InsertDirective, 0, out parameters, out ex)) { return new InsertExpressionToken(m_fileId, line, column); @@ -702,17 +962,17 @@ private ExpressionToken ParseExpression( { return null; } - else if (extendedDirectives && MatchesDirective(trimmed, "if", 1, out parameters, out ex) && ExpressionToken.IsValidExpression(parameters[0], allowedContext, out ex, m_context.Flags)) + else if (extendedDirectives && MatchesDirective(trimmed, "if", 1, out parameters, out ex) && AutoCompleteExpression(completion, trimmedNo + parameters[0].Item1, value) && ExpressionToken.IsValidExpression(parameters[0].Item2, allowedContext, out ex, m_context.Flags) || parameters?.Count == 1 && !AutoCompleteExpression(completion, trimmedNo + parameters[0].Item1, value)) { - return new IfExpressionToken(m_fileId, line, column, parameters[0]); + return new IfExpressionToken(m_fileId, line, column, parameters[0].Item2); } else if (ex != null) { return null; } - else if (extendedDirectives && MatchesDirective(trimmed, "elseif", 1, out parameters, out ex) && ExpressionToken.IsValidExpression(parameters[0], allowedContext, out ex, m_context.Flags)) + else if (extendedDirectives && MatchesDirective(trimmed, "elseif", 1, out parameters, out ex) && AutoCompleteExpression(completion, trimmedNo + parameters[0].Item1, value) && ExpressionToken.IsValidExpression(parameters[0].Item2, allowedContext, out ex, m_context.Flags) || parameters?.Count == 1 && !AutoCompleteExpression(completion, trimmedNo + parameters[0].Item1, value)) { - return new ElseIfExpressionToken(m_fileId, line, column, parameters[0]); + return new ElseIfExpressionToken(m_fileId, line, column, parameters[0].Item2); } else if (ex != null) { @@ -726,15 +986,17 @@ private ExpressionToken ParseExpression( { return null; } - else if (extendedDirectives && MatchesDirective(trimmed, "each", 3, out parameters, out ex) && parameters[1] == "in" && ExpressionToken.IsValidExpression(parameters[2], allowedContext, out ex, m_context.Flags)) + else if (extendedDirectives && MatchesDirective(trimmed, "each", 3, out parameters, out ex) && parameters[1].Item2 == "in" && AutoCompleteExpression(completion, trimmedNo + parameters[2].Item1, value) && ExpressionToken.IsValidExpression(parameters[2].Item2, allowedContext, out ex, m_context.Flags) || parameters?.Count == 3 && !AutoCompleteExpression(completion, trimmedNo + parameters[2].Item1, value)) { - return new EachExpressionToken(m_fileId, line, column, parameters[0], parameters[2]); + return new EachExpressionToken(m_fileId, line, column, parameters[0].Item2, parameters[2].Item2); } else if (ex != null) { return null; } + AutoCompleteExpression(completion, 0, value); + // Check if the value is an expression if (!ExpressionToken.IsValidExpression(trimmed, allowedContext, out ex, m_context.Flags)) { @@ -767,13 +1029,13 @@ private static Boolean MatchesDirective( String trimmed, String directive, Int32 expectedParameters, - out List parameters, + out List<(int, String)> parameters, out Exception ex) { if (trimmed.StartsWith(directive, StringComparison.Ordinal) && (trimmed.Length == directive.Length || Char.IsWhiteSpace(trimmed[directive.Length]))) { - parameters = new List(); + parameters = new List<(int, String)>(); var startIndex = directive.Length; var inString = false; var parens = 0; @@ -784,7 +1046,7 @@ private static Boolean MatchesDirective( { if (startIndex < i) { - parameters.Add(trimmed.Substring(startIndex, i - startIndex)); + parameters.Add((startIndex, trimmed.Substring(startIndex, i - startIndex))); } startIndex = i + 1; @@ -805,13 +1067,17 @@ private static Boolean MatchesDirective( if (startIndex < trimmed.Length) { - parameters.Add(trimmed.Substring(startIndex)); + parameters.Add((startIndex, trimmed.Substring(startIndex))); } if (expectedParameters != parameters.Count) { ex = new ArgumentException(TemplateStrings.ExpectedNParametersFollowingDirective(expectedParameters, directive, parameters.Count)); - parameters = null; + if(expectedParameters == parameters.Count + 1) { + parameters.Add((parameters.LastOrDefault().Item1 + (parameters.LastOrDefault().Item2?.Length ?? 2) + 1, "")); + } else { + parameters = null; + } return false; } @@ -866,6 +1132,10 @@ public DefinitionInfo( { m_schema = schema; + Parent = null; + ParentName = null; + Name = name; + // Lookup the definition Definition = m_schema.GetDefinition(name); @@ -873,12 +1143,33 @@ public DefinitionInfo( AllowedContext = Definition.ReaderContext; } + public DefinitionInfo( + DefinitionInfo parent) + { + m_schema = parent.m_schema; + Parent = parent.Definition; + ParentName = parent.ParentName; + + Name = parent.Name; + + // Lookup the definition + Definition = parent.Definition; + + // Record allowed context + AllowedContext = parent.AllowedContext.ToArray(); + } + public DefinitionInfo( DefinitionInfo parent, String name) { m_schema = parent.m_schema; + Parent = parent.Definition; + ParentName = parent.Name; + + Name = name; + // Lookup the definition Definition = m_schema.GetDefinition(name); @@ -899,7 +1190,11 @@ public IEnumerable Get() return m_schema.Get(Definition); } + public Definition? Parent { get; } + public string? ParentName { get; } + private TemplateSchema m_schema; + public string? Name { get; } public Definition Definition; public String[] AllowedContext; } diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/LiteralToken.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/LiteralToken.cs index 17f6926e84e..0e9acaf36bd 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/LiteralToken.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/LiteralToken.cs @@ -18,5 +18,7 @@ public LiteralToken( : base(tokenType, fileId, line, column) { } + + public string RawData { get; internal set; } } } diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateToken.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateToken.cs index a30b784adda..55f374e3832 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateToken.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateToken.cs @@ -8,6 +8,8 @@ using GitHub.Services.WebApi.Internal; using Newtonsoft.Json; +using Runner.Server.Azure.Devops; + namespace GitHub.DistributedTask.ObjectTemplating.Tokens { /// @@ -287,5 +289,8 @@ private MappingToken CreateMappingToken(TemplateContext context) context.Memory.AddBytes(result); return result; } + + public Position PreWhiteSpace { get; set; } + public Position PostWhiteSpace { get; set; } } } diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs index 9ad26c0ccd5..1adc348f110 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs @@ -309,13 +309,13 @@ public static IEnumerable TraverseByPattern( if (!skip && level + 1 < pattern.Length && (token is SequenceToken && pattern[level + 1] == "*" || token is MappingToken)) { state = new TraversalState(state, token); - level++; + level+=state.Increment; } } else { + level -= state.Increment; state = state.Parent; - level--; } } } @@ -434,6 +434,7 @@ public bool MoveNext(bool omitKeys) private bool m_isKey; public TemplateToken Current; public TraversalState Parent; + public int Increment = 1; } } } diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/JsonObjectReader.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/JsonObjectReader.cs index 72ec60de039..558f127ef5f 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/JsonObjectReader.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/JsonObjectReader.cs @@ -62,7 +62,7 @@ public Boolean AllowSequenceStart(out SequenceToken sequence) return false; } - public Boolean AllowSequenceEnd() + public Boolean AllowSequenceEnd(SequenceToken _) { if (m_enumerator.Current.Type == ParseEventType.SequenceEnd) { @@ -87,7 +87,7 @@ public Boolean AllowMappingStart(out MappingToken mapping) return false; } - public Boolean AllowMappingEnd() + public Boolean AllowMappingEnd(MappingToken _) { if (m_enumerator.Current.Type == ParseEventType.MappingEnd) { diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/YamlObjectReader.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/YamlObjectReader.cs index 756cdeec8bb..2a02cfa2bd5 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/YamlObjectReader.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/YamlObjectReader.cs @@ -2,8 +2,14 @@ using System.Globalization; using System.IO; using System.Linq; + +using GitHub.DistributedTask.Expressions2.Tokens; using GitHub.DistributedTask.ObjectTemplating; using GitHub.DistributedTask.ObjectTemplating.Tokens; + +using Runner.Server.Azure.Devops; + + using YamlDotNet.Core; using YamlDotNet.Core.Events; @@ -30,6 +36,18 @@ internal YamlObjectReader( m_force_azure_pipelines = forceAzurePipelines; } + internal YamlObjectReader( + Int32? fileId, + string input, + bool yamlAnchors = false, + bool yamlFold = false, + bool yamlMerge = false, + bool preserveString = false, + bool forceAzurePipelines = false) : this(fileId, new StringReader(input), yamlAnchors, yamlFold, yamlMerge, preserveString, forceAzurePipelines) + { + m_rawInput = input; + } + private string GetScalarStringValue(Scalar scalar) { return m_yamlFold && scalar.Style == ScalarStyle.Folded ? scalar.Value.Replace("\n", " ") : scalar.Value; } @@ -44,7 +62,7 @@ public Boolean AllowLiteral(out LiteralToken value) // String tag if (String.Equals(scalar.Tag.Value, c_stringTag, StringComparison.Ordinal)) { - value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, GetScalarStringValue(scalar)); + value = CreateStringToken(scalar); MoveNext(); return true; } @@ -74,6 +92,11 @@ public Boolean AllowLiteral(out LiteralToken value) throw new NotSupportedException($"Unexpected tag '{scalar.Tag}'"); } + if(!string.IsNullOrEmpty(m_rawInput)) { + FillPreWhitespace(scalar, value); + FillPostWhitespace(scalar, value); + } + MoveNext(); return true; } @@ -96,7 +119,11 @@ public Boolean AllowLiteral(out LiteralToken value) } else { - value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, GetScalarStringValue(scalar)); + value = CreateStringToken(scalar); + } + if(!string.IsNullOrEmpty(m_rawInput) && value.Type != TokenType.String) { + FillPreWhitespace(scalar, value); + FillPostWhitespace(scalar, value); } MoveNext(); @@ -104,7 +131,7 @@ public Boolean AllowLiteral(out LiteralToken value) } // Otherwise assume string - value = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, GetScalarStringValue(scalar)); + value = CreateStringToken(scalar); MoveNext(); return true; } @@ -113,11 +140,99 @@ public Boolean AllowLiteral(out LiteralToken value) return false; } + private LiteralToken CreateStringToken(Scalar scalar) + { + var tkn = new StringToken(m_fileId, scalar.Start.Line, scalar.Start.Column, GetScalarStringValue(scalar)); + if(!string.IsNullOrEmpty(m_rawInput)) + { + tkn.RawData = m_rawInput.Substring(scalar.Start.Index, scalar.End.Index - scalar.Start.Index); + if(scalar.Style != ScalarStyle.SingleQuoted && scalar.Style != ScalarStyle.DoubleQuoted) { + FillPreWhitespace(scalar, tkn); + // TODO Yaml scalar keys break intellisense, this modifies preprocessing + if(tkn.PreWhiteSpace != null && tkn.PreWhiteSpace.Line < tkn.Line) { + tkn.PreWhiteSpace.Line = tkn.Line.Value; + tkn.PreWhiteSpace.Character = 1; + } + FillPostWhitespace(scalar, tkn); + } else { + tkn.PostWhiteSpace = new Position { Line = scalar.End.Line, Character = scalar.End.Column }; + } + } + return tkn; + } + + private void FillPreWhitespace(NodeEvent scalar, TemplateToken tkn) + { + var lines = 0; + var column = 0; + int i = scalar.Start.Index - 1; + for (; i >= 0 && (m_rawInput[i] == ' ' || m_rawInput[i] == '\n'); i--) + { + switch (m_rawInput[i]) + { + case ' ': + column++; + break; + case '\n': + lines++; + column = 0; + if (i - 1 >= 0 && m_rawInput[i - 1] == '\r') + { + i--; + } + break; + } + } + int cpos = 0; + if (lines > 0 && i >= 0) + { + int lstart = m_rawInput.LastIndexOf('\n', i); + cpos = i + 1 - lstart; + } + tkn.PreWhiteSpace = new Position() { Line = scalar.Start.Line - lines, Character = lines == 0 ? scalar.Start.Column - column : cpos }; + } + + private void FillPostWhitespace(ParsingEvent scalar, TemplateToken tkn) + { + var lines = 0; + var column = 0; + int i = scalar.End.Index; + for (; i < m_rawInput.Length && (m_rawInput[i] == ' ' || m_rawInput[i] == '\r' || m_rawInput[i] == '\n'); i++) + { + switch (m_rawInput[i]) + { + case ' ': + column++; + break; + case '\n': + lines++; + column = 1; + break; + case '\r': + lines++; + column = 1; + if (i + 1 < m_rawInput.Length && m_rawInput[i + 1] == '\n') + { + i++; + } + break; + } + } + tkn.PostWhiteSpace = new Position() { Line = scalar.End.Line + lines, Character = lines == 0 ? scalar.End.Column + column : column }; + } + + public Boolean AllowSequenceStart(out SequenceToken value) { if (EvaluateCurrent() is SequenceStart sequenceStart) { value = new SequenceToken(m_fileId, sequenceStart.Start.Line, sequenceStart.Start.Column); + if(!string.IsNullOrEmpty(m_rawInput)) + { + if(sequenceStart.Style == SequenceStyle.Block) { + FillPreWhitespace(sequenceStart, value); + } + } MoveNext(); return true; } @@ -126,10 +241,18 @@ public Boolean AllowSequenceStart(out SequenceToken value) return false; } - public Boolean AllowSequenceEnd() + public Boolean AllowSequenceEnd(SequenceToken value) { - if (EvaluateCurrent() is SequenceEnd) + if (EvaluateCurrent() is SequenceEnd sequenceEnd) { + if(!string.IsNullOrEmpty(m_rawInput) && value != null) + { + if(value.PreWhiteSpace != null) { + FillPostWhitespace(sequenceEnd, value); + } else { + value.PostWhiteSpace = new Position() { Line = sequenceEnd.End.Line, Character = sequenceEnd.End.Column }; + } + } MoveNext(); return true; } @@ -142,6 +265,12 @@ public Boolean AllowMappingStart(out MappingToken value) if (EvaluateCurrent() is MappingStart mappingStart) { value = new MappingToken(m_fileId, mappingStart.Start.Line, mappingStart.Start.Column); + if(!string.IsNullOrEmpty(m_rawInput)) + { + if(mappingStart.Style == MappingStyle.Block) { + FillPreWhitespace(mappingStart, value); + } + } MoveNext(); return true; } @@ -150,10 +279,18 @@ public Boolean AllowMappingStart(out MappingToken value) return false; } - public Boolean AllowMappingEnd() + public Boolean AllowMappingEnd(MappingToken value) { - if (EvaluateCurrent() is MappingEnd) + if (EvaluateCurrent() is MappingEnd mappingEnd) { + if(!string.IsNullOrEmpty(m_rawInput) && value != null) + { + if(value.PreWhiteSpace != null) { + FillPostWhitespace(mappingEnd, value); + } else { + value.PostWhiteSpace = new Position() { Line = mappingEnd.End.Line, Character = mappingEnd.End.Column }; + } + } MoveNext(); return true; } @@ -606,5 +743,7 @@ private void ThrowInvalidValue( private readonly bool m_preserve_string; private readonly bool m_force_azure_pipelines; private ParsingEvent m_current; + private string m_rawInput; + } } diff --git a/src/Sdk/Sdk.csproj b/src/Sdk/Sdk.csproj index 167ccfa5950..f43e78974d3 100644 --- a/src/Sdk/Sdk.csproj +++ b/src/Sdk/Sdk.csproj @@ -37,5 +37,9 @@ azurepiplines.json + + descriptions.json + + diff --git a/src/azure-pipelines-vscode-ext/CHANGELOG.md b/src/azure-pipelines-vscode-ext/CHANGELOG.md index e975f364242..3d1db6a36cb 100644 --- a/src/azure-pipelines-vscode-ext/CHANGELOG.md +++ b/src/azure-pipelines-vscode-ext/CHANGELOG.md @@ -1,4 +1,10 @@ -### v0.1.1 +### v0.2.0 +- experimental **slow** auto complete (opt int via azure-pipelines-vscode-ext.enable-auto-complete) + - the upcoming Runner.Server extension would be faster by using the native dotnet builds via a language server +- experimental semantic highlighting of template and runtime expressions (opt in via azure-pipelines-vscode-ext.enable-semantic-highlighting) + - currently could slow down syntax checking, this task and the syntax check task needs to be merged to provide both results via one round trip + +### v0.1.2 - syntax check template parameter types for variable templates as well ### v0.1.1 diff --git a/src/azure-pipelines-vscode-ext/README.md b/src/azure-pipelines-vscode-ext/README.md index a05b982052c..6d798b95975 100644 --- a/src/azure-pipelines-vscode-ext/README.md +++ b/src/azure-pipelines-vscode-ext/README.md @@ -8,6 +8,22 @@ The first VSCode Extension which can Validate and Expand Azure Pipeline YAML fil #### Live preview of the expanded / rendered pipeline ![Live preview of the expanded / rendered pipeline](https://raw.githubusercontent.com/wiki/ChristopherHX/runner.server/live-preview.gif) +#### Experimental autocompletion + +**slow ( disabled by default )** + +You can try this out by enabling setting `azure-pipelines-vscode-ext.enable-auto-complete` (no restart needed), feedback wanted. + +![experimental autocompletion](https://raw.githubusercontent.com/wiki/ChristopherHX/runner.server/auto-completion.gif) + +#### Experimental semantic highlighting of template and runtime expressions + +**potentially slow ( disabled by default )** + +You can try this out by enabling setting `azure-pipelines-vscode-ext.enable-semantic-highlighting` (no restart needed), feedback wanted. + +![experimental highlighting](https://raw.githubusercontent.com/wiki/ChristopherHX/runner.server/semantic-highlighting.gif) + ## Features ### Remote Template References diff --git a/src/azure-pipelines-vscode-ext/ext-core/Interop.cs b/src/azure-pipelines-vscode-ext/ext-core/Interop.cs index 5167fec4dba..b47f3be9ad6 100644 --- a/src/azure-pipelines-vscode-ext/ext-core/Interop.cs +++ b/src/azure-pipelines-vscode-ext/ext-core/Interop.cs @@ -13,4 +13,9 @@ public static partial class Interop { internal static partial Task RequestRequiredParameter(JSObject handle, string name); [JSImport("error", "extension.js")] internal static partial Task Error(JSObject handle, string message); + [JSImport("autocompletelist", "extension.js")] + internal static partial Task AutoCompleteList(JSObject handle, string json); + [JSImport("semTokens", "extension.js")] + internal static partial Task SemTokens(JSObject handle, int[] data); + } \ No newline at end of file diff --git a/src/azure-pipelines-vscode-ext/ext-core/Program.cs b/src/azure-pipelines-vscode-ext/ext-core/Program.cs index 7162d6e2246..b4b9328f04d 100644 --- a/src/azure-pipelines-vscode-ext/ext-core/Program.cs +++ b/src/azure-pipelines-vscode-ext/ext-core/Program.cs @@ -8,12 +8,15 @@ using System.Threading.Tasks; using System.Runtime.CompilerServices; using System.Runtime.InteropServices.JavaScript; +using GitHub.DistributedTask.ObjectTemplating.Schema; +using System.Linq; +using System.Text.RegularExpressions; -while(true) { +while (true) { await Interop.Sleep(10 * 60 * 1000); } -public class MyClass { +public partial class MyClass { public class MyFileProvider : IFileProvider { @@ -79,6 +82,7 @@ public class ErrorWrapper { } [MethodImpl(MethodImplOptions.NoInlining)] + [JSExport] public static async Task ExpandCurrentPipeline(JSObject handle, string currentFileName, string variables, string parameters, bool returnErrorContent, string schema) { var context = new Runner.Server.Azure.Devops.Context { FileProvider = new MyFileProvider(handle), @@ -125,18 +129,22 @@ public static async Task ExpandCurrentPipeline(JSObject handle, string c } [MethodImpl(MethodImplOptions.NoInlining)] - public static async Task ParseCurrentPipeline(JSObject handle, string currentFileName, string schemaName) { + [JSExport] + public static async Task ParseCurrentPipeline(JSObject handle, string currentFileName, string schemaName, int column, int row) { var context = new Context { FileProvider = new MyFileProvider(handle), TraceWriter = new TraceWriter(handle), Flags = GitHub.DistributedTask.Expressions2.ExpressionFlags.DTExpressionsV1 | GitHub.DistributedTask.Expressions2.ExpressionFlags.ExtendedDirectives, RequiredParametersProvider = new RequiredParametersProvider(handle), - VariablesProvider = new VariablesProvider { Variables = new Dictionary() } + VariablesProvider = new VariablesProvider { Variables = new Dictionary() }, + Column = column, + Row = row }; + var check = column == 0 && row == 0; try { - var (name, template) = await AzureDevops.ParseTemplate(context, currentFileName, schemaName); + var (name, template) = await AzureDevops.ParseTemplate(context, currentFileName, schemaName, true); Interop.Log(handle, 0, "Done: " + template.ToString()); - } catch(TemplateValidationException ex) { + } catch(TemplateValidationException ex) when(check) { var fileIdReplacer = new System.Text.RegularExpressions.Regex("FileId: (\\d+)"); var allErrors = new List(); foreach(var error in ex.Errors) { @@ -147,16 +155,30 @@ public static async Task ParseCurrentPipeline(JSObject handle, string currentFil } await Interop.Error(handle, JsonConvert.SerializeObject(new ErrorWrapper { Message = ex.Message, Errors = allErrors })); } catch(Exception ex) { - var fileIdReplacer = new System.Text.RegularExpressions.Regex("FileId: (\\d+)"); - var errorContent = fileIdReplacer.Replace(ex.Message, match => { - return $"{context.FileTable[int.Parse(match.Groups[1].Value) - 1]}"; - }); - await Interop.Error(handle, JsonConvert.SerializeObject(new ErrorWrapper { Message = ex.Message, Errors = new List { errorContent } })); + if(check) { + var fileIdReplacer = new System.Text.RegularExpressions.Regex("FileId: (\\d+)"); + var errorContent = fileIdReplacer.Replace(ex.Message, match => { + return $"{context.FileTable[int.Parse(match.Groups[1].Value) - 1]}"; + }); + await Interop.Error(handle, JsonConvert.SerializeObject(new ErrorWrapper { Message = ex.Message, Errors = new List { errorContent } })); + } + } + if(!check && context.AutoCompleteMatches.Count > 0) { + // Bug Only suggest scalar values if cursor is within the token + // Don't suggest mapping and array on the other location, or fix autocomplete structure + // transform string + multi enum values to oneofdefinition with constants so autocomplete works / use allowed values + var schema = AzureDevops.LoadSchema(); + List list = AutoCompletetionHelper.CollectCompletions(column, row, context, schema); + await Interop.AutoCompleteList(handle, JsonConvert.SerializeObject(list)); } + if(check && context.SemTokens?.Count > 0) { + await Interop.SemTokens(handle, [.. context.SemTokens]); + } + } - [MethodImpl(MethodImplOptions.NoInlining)] + [JSExport] public static string YAMLToJson(string content) { try { return AzurePipelinesUtils.YAMLToJson(content); diff --git a/src/azure-pipelines-vscode-ext/index.js b/src/azure-pipelines-vscode-ext/index.js index 581003e7aeb..6830de937cf 100644 --- a/src/azure-pipelines-vscode-ext/index.js +++ b/src/azure-pipelines-vscode-ext/index.js @@ -1,7 +1,7 @@ const vscode = require('vscode'); import { basePaths, customImports } from "./config.js" import { AzurePipelinesDebugSession } from "./azure-pipelines-debug"; -import { integer } from "vscode-languageclient"; +import { integer, Position } from "vscode-languageclient"; import jsYaml from "js-yaml"; /** @@ -151,6 +151,14 @@ function activate(context) { }, error: async (handle, message) => { await handle.error(message); + }, + autocompletelist: async (handle, completions) => { + handle.autocompletelist = JSON.parse(completions); + }, + semTokens: async (handle, completions) => { + if(handle.enableSemTokens){ + handle.semTokens = completions; + } } }); logchannel.appendLine("Starting extension main to keep dotnet alive"); @@ -164,7 +172,7 @@ function activate(context) { }); var defcollection = vscode.languages.createDiagnosticCollection(); - var expandAzurePipeline = async (validate, repos, vars, params, callback, fspathname, error, task, collection, state, skipAskForInput, syntaxOnly, schema) => { + var expandAzurePipeline = async (validate, repos, vars, params, callback, fspathname, error, task, collection, state, skipAskForInput, syntaxOnly, schema, pos, autocompletelist) => { collection ??= defcollection; var textEditor = vscode.window.activeTextEditor; if(!textEditor && !fspathname) { @@ -376,11 +384,19 @@ function activate(context) { vscode.window.showWarningMessage("Please run this command on your root pipeline and not on a nested template detected as: " + schema); } } + handle.enableSemTokens = autocompletelist && ('enableSemTokens' in autocompletelist); + var result = syntaxOnly - ? await runtime.BINDING.bind_static_method("[ext-core] MyClass:ParseCurrentPipeline")(handle, filename, schema) + ? await runtime.BINDING.bind_static_method("[ext-core] MyClass:ParseCurrentPipeline")(handle, filename, schema, pos ? pos.character + 1 : 0, pos ? pos.line + 1 : 0) : await runtime.BINDING.bind_static_method("[ext-core] MyClass:ExpandCurrentPipeline")(handle, filename, JSON.stringify(variables), JSON.stringify(parameters), (error && true) == true, schema) - if(state) { + if(pos) { + autocompletelist.autocompletelist = handle.autocompletelist + } + if(handle.enableSemTokens) { + autocompletelist.semTokens = handle.semTokens + } + if(state) { state.referencedFiles = handle.referencedFiles; if(!skipAskForInput) { state.repositories = handle.repositories; @@ -518,6 +534,71 @@ function activate(context) { return schema; } + var registerSemanticHighlighting = () => vscode.languages.registerDocumentSemanticTokensProvider({ + language: "yaml" + }, { + provideDocumentSemanticTokens: async (doc, token) => { + var data = {enableSemTokens: true}; + await expandAzurePipeline(false, null, null, null, () => { + }, null, () => { + }, null, null, null, true, true, null, null, data); + var semTokens = data.semTokens || new Uint32Array(); + return new vscode.SemanticTokens(semTokens); + } + }, new vscode.SemanticTokensLegend(["variable","parameter","function","property","constant","punctuation","string"], ["readonly","defaultLibrary","numeric"])); + var registerAutoCompletionYaml = (lang) => vscode.languages.registerCompletionItemProvider({ + language: lang ?? "yaml" + }, { + provideCompletionItems: async (doc, pos, token, context) => { + var data = {autocompletelist: []}; + await expandAzurePipeline(false, null, null, null, () => { + }, null, () => { + }, null, null, null, true, true, null, pos, data); + for(var item of data.autocompletelist) { + if(item.insertText && item.insertText.value) { + item.insertText = new vscode.SnippetString(item.insertText.value) + } + if(item.documentation) { + item.documentation = new vscode.MarkdownString(item.documentation.value, item.supportThemeIcons) + } + } + return data.autocompletelist + } + }); + + var semHightl = null; + var autoCompleteYaml = null; + var autoCompleteAdo = null; + var semHightlSettingChanged = () => { + if(vscode.workspace.getConfiguration("azure-pipelines-vscode-ext").get("enable-semantic-highlighting")) { + semHightl = registerSemanticHighlighting(); + context.subscriptions.push(semHightl); + } else { + semHightl?.dispose(); + } + }; + var autoCompleteSettingChanged = () => { + if(vscode.workspace.getConfiguration("azure-pipelines-vscode-ext").get("enable-auto-complete")) { + autoCompleteYaml = registerAutoCompletionYaml(); + autoCompleteAdo = registerAutoCompletionYaml("azure-pipelines"); + context.subscriptions.push(autoCompleteYaml); + context.subscriptions.push(autoCompleteAdo); + } else { + autoCompleteYaml?.dispose(); + autoCompleteAdo?.dispose(); + } + }; + vscode.workspace.onDidChangeConfiguration(conf => { + if(conf.affectsConfiguration("azure-pipelines-vscode-ext.enable-semantic-highlighting")) { + semHightlSettingChanged(); + } + if(conf.affectsConfiguration("azure-pipelines-vscode-ext.enable-auto-complete")) { + autoCompleteSettingChanged(); + } + }) + semHightlSettingChanged(); + autoCompleteSettingChanged(); + context.subscriptions.push(vscode.commands.registerCommand(statusbar.command.command, async (file, collection, obj) => { var getSchema = () => { try { diff --git a/src/azure-pipelines-vscode-ext/package-lock.json b/src/azure-pipelines-vscode-ext/package-lock.json index cf0af2eb889..c5755008da2 100644 --- a/src/azure-pipelines-vscode-ext/package-lock.json +++ b/src/azure-pipelines-vscode-ext/package-lock.json @@ -1,12 +1,12 @@ { "name": "azure-pipelines-vscode-ext", - "version": "0.1.1", + "version": "0.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "azure-pipelines-vscode-ext", - "version": "0.1.1", + "version": "0.2.0", "license": "MIT", "dependencies": { "@vscode/debugadapter": "^1.64.0", @@ -18,7 +18,7 @@ }, "devDependencies": { "@types/vscode": "^1.73.0", - "@vscode/vsce": "^2.31.1", + "@vscode/vsce": "^3.1.0", "webpack-cli": "^5.1.4" }, "engines": { @@ -317,16 +317,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@types/eslint": { "version": "8.44.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", @@ -388,9 +378,9 @@ "integrity": "sha512-Zhf3KvB+J04M4HPE2yCvEILGVtPixXUQMLBvx4QcAtjhc5lnwlZbbt80LCsZO2B+2BH8RMgVXk3QQ5DEzEne2Q==" }, "node_modules/@vscode/vsce": { - "version": "2.31.1", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.31.1.tgz", - "integrity": "sha512-LwEQFKXV21C4/brvGPH/9+7ZOUM5cbK7oJ4fVmy0YG75NIy1HV8eMSoBZrl+u23NxpAhor62Cu1aI+JFtCtjSg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.1.0.tgz", + "integrity": "sha512-fwdfp1Ol+bZtlSGkpcd/nztfo6+SVsTOMWjZ/+a88lVtUn7gXNbSu7dbniecl5mz4vINl+oaVDVtVdGbJDApmw==", "dev": true, "dependencies": { "@azure/identity": "^4.1.0", @@ -405,7 +395,7 @@ "hosted-git-info": "^4.0.2", "jsonc-parser": "^3.2.0", "leven": "^3.1.0", - "markdown-it": "^12.3.2", + "markdown-it": "^14.1.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "parse-semver": "^1.1.1", @@ -422,7 +412,7 @@ "vsce": "vsce" }, "engines": { - "node": ">= 16" + "node": ">= 20" }, "optionalDependencies": { "keytar": "^7.7.0" @@ -848,9 +838,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "engines": { "node": ">=12" @@ -1605,9 +1595,9 @@ } }, "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -1970,9 +1960,9 @@ } }, "node_modules/jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -1982,9 +1972,6 @@ }, "funding": { "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest-worker": { @@ -2137,12 +2124,12 @@ } }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/loader-runner": { @@ -2220,34 +2207,26 @@ } }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, "node_modules/merge-stream": { @@ -2491,9 +2470,9 @@ } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, "node_modules/parse-semver": { @@ -2571,9 +2550,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", "dev": true, "engines": { "node": "20 || >=22" @@ -2671,6 +2650,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -3395,9 +3383,9 @@ } }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, "node_modules/underscore": { @@ -4122,13 +4110,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true - }, "@types/eslint": { "version": "8.44.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", @@ -4187,9 +4168,9 @@ "integrity": "sha512-Zhf3KvB+J04M4HPE2yCvEILGVtPixXUQMLBvx4QcAtjhc5lnwlZbbt80LCsZO2B+2BH8RMgVXk3QQ5DEzEne2Q==" }, "@vscode/vsce": { - "version": "2.31.1", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.31.1.tgz", - "integrity": "sha512-LwEQFKXV21C4/brvGPH/9+7ZOUM5cbK7oJ4fVmy0YG75NIy1HV8eMSoBZrl+u23NxpAhor62Cu1aI+JFtCtjSg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.1.0.tgz", + "integrity": "sha512-fwdfp1Ol+bZtlSGkpcd/nztfo6+SVsTOMWjZ/+a88lVtUn7gXNbSu7dbniecl5mz4vINl+oaVDVtVdGbJDApmw==", "dev": true, "requires": { "@azure/identity": "^4.1.0", @@ -4205,7 +4186,7 @@ "jsonc-parser": "^3.2.0", "keytar": "^7.7.0", "leven": "^3.1.0", - "markdown-it": "^12.3.2", + "markdown-it": "^14.1.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "parse-semver": "^1.1.1", @@ -4537,9 +4518,9 @@ "requires": {} }, "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true }, "ansi-styles": { @@ -5084,9 +5065,9 @@ } }, "foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -5337,13 +5318,12 @@ "dev": true }, "jackspeak": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", - "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", "dev": true, "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" + "@isaacs/cliui": "^8.0.2" } }, "jest-worker": { @@ -5475,12 +5455,12 @@ "dev": true }, "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "requires": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "loader-runner": { @@ -5549,30 +5529,23 @@ } }, "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "requires": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "dependencies": { - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true - } + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" } }, "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, "merge-stream": { @@ -5758,9 +5731,9 @@ "dev": true }, "package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, "parse-semver": { @@ -5820,9 +5793,9 @@ }, "dependencies": { "lru-cache": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", - "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", "dev": true } } @@ -5900,6 +5873,12 @@ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "peer": true }, + "punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true + }, "qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -6392,9 +6371,9 @@ "peer": true }, "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, "underscore": { diff --git a/src/azure-pipelines-vscode-ext/package.json b/src/azure-pipelines-vscode-ext/package.json index b436e9b9336..613b2b926d5 100644 --- a/src/azure-pipelines-vscode-ext/package.json +++ b/src/azure-pipelines-vscode-ext/package.json @@ -8,7 +8,7 @@ "Programming Languages" ], "icon": "pipeline-rocket.png", - "version": "0.1.2", + "version": "0.2.0", "publisher": "christopherhx", "repository": "https://github.com/ChristopherHX/runner.server", "engines": { @@ -130,6 +130,18 @@ "default": false, "scope": "window", "description": "Hide all status bar entries" + }, + "azure-pipelines-vscode-ext.enable-auto-complete": { + "type": "boolean", + "default": false, + "scope": "window", + "description": "Enable currently slow auto completion in this web extension" + }, + "azure-pipelines-vscode-ext.enable-semantic-highlighting": { + "type": "boolean", + "default": false, + "scope": "window", + "description": "Colorize Pipeline Expressions via Semantic Highlighting" } } } @@ -306,6 +318,15 @@ }, "type": "azure-pipelines-vscode-ext" } + ], + "semanticTokenScopes": [ + { + "scopes": { + "constant.defaultLibrary": ["constant.language"], + "constant.numeric": ["constant.numeric"], + "punctuation": ["meta.embedded"] + } + } ] }, "scripts": { @@ -313,7 +334,7 @@ }, "devDependencies": { "@types/vscode": "^1.73.0", - "@vscode/vsce": "^2.31.1", + "@vscode/vsce": "^3.1.0", "webpack-cli": "^5.1.4" }, "license": "MIT",