diff --git a/.github/workflows/buildtest.yml b/.github/workflows/buildtest.yml index f6b5e87..3021880 100644 --- a/.github/workflows/buildtest.yml +++ b/.github/workflows/buildtest.yml @@ -12,14 +12,15 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Use Node.js 16.x + - name: Use Node.js 16.15 uses: actions/setup-node@v3 with: - node-version: 16.x + node-version: 16.15 cache: 'npm' registry-url: 'https://npm.pkg.github.com' - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: npm run format-check -ws - run: npm run build -ws - run: npm test -ws diff --git a/.prettierignore b/.prettierignore index ce98533..323e10d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ dist *.md *.js *.json +*.d.ts \ No newline at end of file diff --git a/actions-expressions/package.json b/actions-expressions/package.json index 39212c9..c3f5240 100755 --- a/actions-expressions/package.json +++ b/actions-expressions/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-expressions", - "version": "0.1.100", + "version": "0.1.109", "license": "MIT", "type": "module", "source": "./src/index.ts", @@ -37,7 +37,7 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" }, "files": [ "dist/**/*" @@ -45,7 +45,7 @@ "devDependencies": { "@types/jest": "^29.0.3", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.7.4" diff --git a/actions-languageserver/package.json b/actions-languageserver/package.json index 00db1b3..9ff0edd 100644 --- a/actions-languageserver/package.json +++ b/actions-languageserver/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageserver", - "version": "0.1.100", + "version": "0.1.109", "description": "Language server for GitHub Actions", "license": "MIT", "type": "module", @@ -38,15 +38,15 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-languageservice": "^0.1.100", - "@github/actions-workflow-parser": "^0.1.100", - "@octokit/rest": "^19.0.5", + "@github/actions-languageservice": "^0.1.109", + "@github/actions-workflow-parser": "^0.1.109", + "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "yaml": "^2.1.3" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" }, "files": [ "dist/**/*" @@ -55,7 +55,7 @@ "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4" diff --git a/actions-languageserver/src/client.ts b/actions-languageserver/src/client.ts new file mode 100644 index 0000000..de54554 --- /dev/null +++ b/actions-languageserver/src/client.ts @@ -0,0 +1,8 @@ +import {Octokit} from "@octokit/rest"; + +export function getClient(token: string, userAgent?: string): Octokit { + return new Octokit({ + auth: token, + userAgent: userAgent || `GitHub Actions Language Server` + }); +} diff --git a/actions-languageserver/src/connection.ts b/actions-languageserver/src/connection.ts index d1650bc..daddd1c 100644 --- a/actions-languageserver/src/connection.ts +++ b/actions-languageserver/src/connection.ts @@ -16,19 +16,20 @@ import { TextDocumentSyncKind } from "vscode-languageserver"; import {TextDocument} from "vscode-languageserver-textdocument"; +import {getClient} from "./client"; +import {Commands} from "./commands"; import {contextProviders} from "./context-providers"; +import {descriptionProvider} from "./description-provider"; import {InitializationOptions, RepositoryContext} from "./initializationOptions"; import {onCompletion} from "./on-completion"; import {TTLCache} from "./utils/cache"; import {valueProviders} from "./value-providers"; import {getActionInputs} from "./value-providers/action-inputs"; -import {Commands} from "./commands"; -import {descriptionProvider} from "./description-provider"; export function initConnection(connection: Connection) { const documents: TextDocuments = new TextDocuments(TextDocument); - let sessionToken: string | undefined; + let client: Octokit | undefined; let repos: RepositoryContext[] = []; const cache = new TTLCache(); @@ -49,7 +50,11 @@ export function initConnection(connection: Connection) { ); const options: InitializationOptions = params.initializationOptions; - sessionToken = options.sessionToken; + + if (options.sessionToken) { + client = getClient(options.sessionToken, options.userAgent); + } + if (options.repos) { repos = options.repos; } @@ -93,15 +98,13 @@ export function initConnection(connection: Connection) { const repoContext = repos.find(repo => textDocument.uri.startsWith(repo.workspaceUri)); const config: ValidationConfig = { - valueProviderConfig: valueProviders(sessionToken, repoContext, cache), - contextProviderConfig: contextProviders(sessionToken, repoContext, cache), + valueProviderConfig: valueProviders(client, repoContext, cache), + contextProviderConfig: contextProviders(client, repoContext, cache), getActionInputs: async action => { - if (sessionToken) { - const octokit = new Octokit({ - auth: sessionToken - }); - return await getActionInputs(octokit, cache, action); + if (client) { + return await getActionInputs(client, cache, action); } + return undefined; } }; @@ -114,7 +117,7 @@ export function initConnection(connection: Connection) { return await onCompletion( position, documents.get(textDocument.uri)!, - sessionToken, + client, repos.find(repo => textDocument.uri.startsWith(repo.workspaceUri)), cache ); @@ -122,7 +125,7 @@ export function initConnection(connection: Connection) { connection.onHover(async ({position, textDocument}: HoverParams): Promise => { return hover(documents.get(textDocument.uri)!, position, { - descriptionProvider: descriptionProvider(sessionToken, cache) + descriptionProvider: descriptionProvider(client, cache) }); }); diff --git a/actions-languageserver/src/context-providers.ts b/actions-languageserver/src/context-providers.ts index 5d1f151..965475c 100644 --- a/actions-languageserver/src/context-providers.ts +++ b/actions-languageserver/src/context-providers.ts @@ -9,18 +9,14 @@ import {RepositoryContext} from "./initializationOptions"; import {TTLCache} from "./utils/cache"; export function contextProviders( - sessionToken: string | undefined, + client: Octokit | undefined, repo: RepositoryContext | undefined, cache: TTLCache ): ContextProviderConfig { - if (!repo || !sessionToken) { + if (!repo || !client) { return {getContext: (_: string) => Promise.resolve(undefined)}; } - const octokit = new Octokit({ - auth: sessionToken - }); - const getContext = async ( name: string, defaultContext: DescriptionDictionary | undefined, @@ -28,11 +24,11 @@ export function contextProviders( ) => { switch (name) { case "secrets": - return await getSecrets(workflowContext, octokit, cache, repo, defaultContext); + return await getSecrets(workflowContext, client, cache, repo, defaultContext); case "vars": - return await getVariables(workflowContext, octokit, cache, repo, defaultContext); + return await getVariables(workflowContext, client, cache, repo, defaultContext); case "steps": - return await getStepsContext(octokit, cache, defaultContext, workflowContext); + return await getStepsContext(client, cache, defaultContext, workflowContext); } }; diff --git a/actions-languageserver/src/context-providers/secrets.ts b/actions-languageserver/src/context-providers/secrets.ts index c8c262a..61c6ca0 100644 --- a/actions-languageserver/src/context-providers/secrets.ts +++ b/actions-languageserver/src/context-providers/secrets.ts @@ -41,6 +41,15 @@ export async function getSecrets( } >(); + secrets.orgSecrets.forEach(secret => + secretsMap.set(secret.value.toLowerCase(), { + key: secret.value, + value: new data.StringData("***"), + description: "Organization secret" + }) + ); + + // Override org secrets with repo secrets secrets.repoSecrets.forEach(secret => secretsMap.set(secret.value.toLowerCase(), { key: secret.value, @@ -76,6 +85,7 @@ async function getRemoteSecrets( ): Promise<{ repoSecrets: StringData[]; environmentSecrets: StringData[]; + orgSecrets: StringData[]; }> { return { repoSecrets: await cache.get(`${repo.owner}/${repo.name}/secrets`, undefined, () => @@ -86,18 +96,22 @@ async function getRemoteSecrets( (await cache.get(`${repo.owner}/${repo.name}/secrets/environment/${environmentName}`, undefined, () => fetchEnvironmentSecrets(octokit, repo.id, environmentName) ))) || - [] + [], + orgSecrets: await cache.get(`${repo.owner}/secrets`, undefined, () => fetchOrganizationSecrets(octokit, repo)) }; } async function fetchSecrets(octokit: Octokit, owner: string, name: string): Promise { try { - const response = await octokit.actions.listRepoSecrets({ - owner, - repo: name - }); - - return response.data.secrets.map(secret => new StringData(secret.name)); + return await octokit.paginate( + octokit.actions.listRepoSecrets, + { + owner, + repo: name, + per_page: 100 + }, + response => response.data.map(secret => new StringData(secret.name)) + ); } catch (e) { console.log("Failure to retrieve secrets: ", e); } @@ -111,15 +125,37 @@ async function fetchEnvironmentSecrets( environmentName: string ): Promise { try { - const response = await octokit.actions.listEnvironmentSecrets({ - repository_id: repositoryId, - environment_name: environmentName - }); - - return response.data.secrets.map(secret => new StringData(secret.name)); + return await octokit.paginate( + octokit.actions.listEnvironmentSecrets, + { + repository_id: repositoryId, + environment_name: environmentName, + per_page: 100 + }, + response => response.data.map(secret => new StringData(secret.name)) + ); } catch (e) { console.log("Failure to retrieve environment secrets: ", e); } return []; } + +async function fetchOrganizationSecrets(octokit: Octokit, repo: RepositoryContext): Promise { + if (!repo.organizationOwned) { + return []; + } + + try { + const secrets: {name: string}[] = await octokit.paginate("GET /repos/{owner}/{repo}/actions/organization-secrets", { + owner: repo.owner, + repo: repo.name, + per_page: 100 + }); + return secrets.map(secret => new StringData(secret.name)); + } catch (e) { + console.log("Failure to retrieve organization secrets: ", e); + } + + return []; +} diff --git a/actions-languageserver/src/context-providers/variables.ts b/actions-languageserver/src/context-providers/variables.ts index 5481bc4..52babbd 100644 --- a/actions-languageserver/src/context-providers/variables.ts +++ b/actions-languageserver/src/context-providers/variables.ts @@ -42,6 +42,15 @@ export async function getVariables( } >(); + variables.organizationVariables.forEach(variable => + variablesMap.set(variable.key.toLowerCase(), { + key: variable.key, + value: new data.StringData(variable.value.coerceString()), + description: `${variable.value.coerceString()} - Organization variable` + }) + ); + + // Override org variables with repo variables variables.repoVariables.forEach(variable => variablesMap.set(variable.key.toLowerCase(), { key: variable.key, @@ -77,6 +86,7 @@ export async function getRemoteVariables( ): Promise<{ repoVariables: Pair[]; environmentVariables: Pair[]; + organizationVariables: Pair[]; }> { // Repo variables return { @@ -88,25 +98,27 @@ export async function getRemoteVariables( (await cache.get(`${repo.owner}/${repo.name}/vars/environment/${environmentName}`, undefined, () => fetchEnvironmentVariables(octokit, repo.id, environmentName) ))) || - [] + [], + organizationVariables: await cache.get(`${repo.owner}/vars`, undefined, () => + fetchOrganizationVariables(octokit, repo) + ) }; } async function fetchVariables(octokit: Octokit, owner: string, name: string): Promise { try { - const response = (await octokit.paginate("GET /repos/{owner}/{repo}/actions/variables{?per_page}", { - owner: owner, - repo: name - })) as { - name: string; - value: string; - created_at: string; - updated_at: string; - }[]; - - return response.map(variable => { - return {key: variable.name, value: new StringData(variable.value)}; - }); + return await octokit.paginate( + octokit.actions.listRepoVariables, + { + owner: owner, + repo: name, + per_page: 100 + }, + response => + response.data.map(variable => { + return {key: variable.name, value: new StringData(variable.value)}; + }) + ); } catch (e) { console.log("Failure to retrieve variables: ", e); } @@ -120,25 +132,45 @@ async function fetchEnvironmentVariables( environmentName: string ): Promise { try { - const response = (await octokit.paginate( - "GET /repositories/{repository_id}/environments/{environment_name}/variables{?per_page}", + return await octokit.paginate( + octokit.actions.listEnvironmentVariables, { repository_id: repositoryId, - environment_name: environmentName - } - )) as { - name: string; - value: string; - created_at: string; - updated_at: string; - }[]; - - return response.map(variable => { - return {key: variable.name, value: new StringData(variable.value)}; - }); + environment_name: environmentName, + per_page: 100 + }, + response => + response.data.map(variable => { + return {key: variable.name, value: new StringData(variable.value)}; + }) + ); } catch (e) { console.log("Failure to retrieve environment variables: ", e); } return []; } + +async function fetchOrganizationVariables(octokit: Octokit, repo: RepositoryContext): Promise { + if (!repo.organizationOwned) { + return []; + } + + try { + const variables: {name: string; value: string}[] = await octokit.paginate( + "GET /repos/{owner}/{repo}/actions/organization-variables", + { + owner: repo.owner, + repo: repo.name, + per_page: 100 + } + ); + return variables.map(variable => { + return {key: variable.name, value: new StringData(variable.value)}; + }); + } catch (e) { + console.log("Failure to retrieve organization variables: ", e); + } + + return []; +} diff --git a/actions-languageserver/src/description-provider.ts b/actions-languageserver/src/description-provider.ts index bd724ed..128a1ee 100644 --- a/actions-languageserver/src/description-provider.ts +++ b/actions-languageserver/src/description-provider.ts @@ -1,22 +1,17 @@ import {DescriptionProvider} from "@github/actions-languageservice/hover"; import {Octokit} from "@octokit/rest"; -import {TTLCache} from "./utils/cache"; import {getActionInputDescription} from "./description-providers/action-input"; +import {TTLCache} from "./utils/cache"; -export function descriptionProvider(sessionToken: string | undefined, cache: TTLCache): DescriptionProvider { - const octokit = - sessionToken && - new Octokit({ - auth: sessionToken - }); - +export function descriptionProvider(client: Octokit | undefined, cache: TTLCache): DescriptionProvider { const getDescription: DescriptionProvider["getDescription"] = async (context, token, path) => { - if (!octokit) { + if (!client) { return undefined; } + const parent = path[path.length - 1]; if (context.step && parent.definition?.key === "step-with") { - return await getActionInputDescription(octokit, cache, context.step, token); + return await getActionInputDescription(client, cache, context.step, token); } }; diff --git a/actions-languageserver/src/initializationOptions.ts b/actions-languageserver/src/initializationOptions.ts index fba9578..0052dda 100644 --- a/actions-languageserver/src/initializationOptions.ts +++ b/actions-languageserver/src/initializationOptions.ts @@ -9,6 +9,11 @@ export interface InitializationOptions { */ sessionToken?: string; + /** + * Optional user agent to use when making calls to github.com + */ + userAgent?: string; + /** * List of repositories that the language server should be aware of */ @@ -31,6 +36,11 @@ export interface RepositoryContext { */ owner: string; + /** + * Indicates if the repository is owned by an organization + */ + organizationOwned: boolean; + /** * Repository name */ diff --git a/actions-languageserver/src/on-completion.ts b/actions-languageserver/src/on-completion.ts index 43bd70f..8be0d3b 100644 --- a/actions-languageserver/src/on-completion.ts +++ b/actions-languageserver/src/on-completion.ts @@ -1,4 +1,5 @@ import {complete} from "@github/actions-languageservice/complete"; +import {Octokit} from "@octokit/rest"; import {CompletionItem, Position} from "vscode-languageserver"; import {TextDocument} from "vscode-languageserver-textdocument"; import {contextProviders} from "./context-providers"; @@ -9,14 +10,14 @@ import {valueProviders} from "./value-providers"; export async function onCompletion( position: Position, document: TextDocument, - sessionToken: string | undefined, + client: Octokit | undefined, repoContext: RepositoryContext | undefined, cache: TTLCache ): Promise { return await complete( document, position, - repoContext && valueProviders(sessionToken, repoContext, cache), - repoContext && contextProviders(sessionToken, repoContext, cache) + repoContext && valueProviders(client, repoContext, cache), + repoContext && contextProviders(client, repoContext, cache) ); } diff --git a/actions-languageserver/src/value-providers.ts b/actions-languageserver/src/value-providers.ts index d5e0eba..3180431 100644 --- a/actions-languageserver/src/value-providers.ts +++ b/actions-languageserver/src/value-providers.ts @@ -9,34 +9,30 @@ import {getEnvironments} from "./value-providers/job-environment"; import {getRunnerLabels} from "./value-providers/runs-on"; export function valueProviders( - sessionToken: string | undefined, + client: Octokit | undefined, repo: RepositoryContext | undefined, cache: TTLCache ): ValueProviderConfig { - if (!repo || !sessionToken) { + if (!repo || !client) { return {}; } - const octokit = new Octokit({ - auth: sessionToken - }); - return { "job-environment": { kind: ValueProviderKind.AllowedValues, - get: (_: WorkflowContext) => getEnvironments(octokit, cache, repo.owner, repo.name) + get: (_: WorkflowContext) => getEnvironments(client, cache, repo.owner, repo.name) }, "job-environment-name": { kind: ValueProviderKind.AllowedValues, - get: (_: WorkflowContext) => getEnvironments(octokit, cache, repo.owner, repo.name) + get: (_: WorkflowContext) => getEnvironments(client, cache, repo.owner, repo.name) }, "runs-on": { kind: ValueProviderKind.SuggestedValues, - get: (_: WorkflowContext) => getRunnerLabels(octokit, cache, repo.owner, repo.name) + get: (_: WorkflowContext) => getRunnerLabels(client, cache, repo.owner, repo.name) }, "step-with": { kind: ValueProviderKind.AllowedValues, - get: (context: WorkflowContext) => getActionInputValues(octokit, cache, context) + get: (context: WorkflowContext) => getActionInputValues(client, cache, context) } }; } diff --git a/actions-languageserver/src/value-providers/runs-on.ts b/actions-languageserver/src/value-providers/runs-on.ts index 0de79f4..732e464 100644 --- a/actions-languageserver/src/value-providers/runs-on.ts +++ b/actions-languageserver/src/value-providers/runs-on.ts @@ -2,6 +2,8 @@ import {Value} from "@github/actions-languageservice/value-providers/config"; import {Octokit} from "@octokit/rest"; import {TTLCache} from "../utils/cache"; +// Limitation: getRunnerLabels returns default hosted labels and labels for repository self-hosted runners. +// It doesn't return labels for organization runners visible to the repository. export async function getRunnerLabels(client: Octokit, cache: TTLCache, owner: string, name: string): Promise { const defaultLabels = [ "ubuntu-latest", @@ -33,14 +35,17 @@ export async function getRunnerLabels(client: Octokit, cache: TTLCache, owner: s async function fetchRunnerLabels(client: Octokit, owner: string, name: string): Promise> { const labels = new Set(); try { - const response = await client.actions.listSelfHostedRunnersForRepo({ + const itor = client.paginate.iterator(client.actions.listSelfHostedRunnersForRepo, { owner, - repo: name + repo: name, + per_page: 100 }); - for (const runner of response.data.runners) { - for (const label of runner.labels) { - labels.add(label.name); + for await (const response of itor) { + for (const runner of response.data) { + for (const label of runner.labels) { + labels.add(label.name); + } } } } catch (e) { diff --git a/actions-languageserver/tsconfig.json b/actions-languageserver/tsconfig.json index d2faef8..85f9f1c 100644 --- a/actions-languageserver/tsconfig.json +++ b/actions-languageserver/tsconfig.json @@ -8,7 +8,8 @@ "sourceMap": true, "strict": true, "moduleResolution": "node", - "lib": ["es6", "webworker"] + "lib": ["es6", "webworker"], + "resolveJsonModule": true }, "watchOptions": { "watchFile": "useFsEvents", diff --git a/actions-languageservice/package.json b/actions-languageservice/package.json index 1ffc4a8..25b3dd7 100644 --- a/actions-languageservice/package.json +++ b/actions-languageservice/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageservice", - "version": "0.1.100", + "version": "0.1.109", "description": "Language service for GitHub Actions", "license": "MIT", "type": "module", @@ -38,14 +38,14 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.100", - "@github/actions-workflow-parser": "^0.1.100", + "@github/actions-expressions": "^0.1.109", + "@github/actions-workflow-parser": "^0.1.109", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" }, "files": [ "dist/**/*" @@ -54,7 +54,7 @@ "devDependencies": { "@types/jest": "^29.0.3", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4" diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index 5b4b576..070ea21 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -255,6 +255,13 @@ jobs: expect(result.map(x => x.label)).toEqual(["event"]); }); + it("auto-complete partial", async () => { + const input = "run-name: ${{ github.ev| }}"; + const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); + + expect(result.map(x => x.label)).toEqual(["event"]); + }); + it("using default context provider", async () => { const input = "on: push\njobs:\n build:\n runs-on: ubuntu-latest\n environment:\n url: ${{ runner.| }}\n steps:\n - run: echo"; diff --git a/actions-languageservice/src/complete.ts b/actions-languageservice/src/complete.ts index cd88b64..54942fb 100644 --- a/actions-languageservice/src/complete.ts +++ b/actions-languageservice/src/complete.ts @@ -1,6 +1,12 @@ -import {complete as completeExpression} from "@github/actions-expressions"; +import {DescriptionDictionary, complete as completeExpression} from "@github/actions-expressions"; import {CompletionItem as ExpressionCompletionItem} from "@github/actions-expressions/completion"; -import {convertWorkflowTemplate, isSequence, isString, parseWorkflow} from "@github/actions-workflow-parser"; +import { + convertWorkflowTemplate, + isBasicExpression, + isSequence, + isString, + parseWorkflow +} from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {DefinitionType} from "@github/actions-workflow-parser/templates/schema/definition-type"; import {StringDefinition} from "@github/actions-workflow-parser/templates/schema/string-definition"; @@ -24,6 +30,7 @@ import {transform} from "./utils/transform"; import {Value, ValueProviderConfig} from "./value-providers/config"; import {defaultValueProviders} from "./value-providers/default"; import {definitionValues} from "./value-providers/definition"; +import {TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range"; export function getExpressionInput(input: string, pos: number): string { // Find start marker around the cursor position @@ -71,37 +78,14 @@ export async function complete( // If we are inside an expression, take a different code-path. The workflow parser does not correctly create // expression nodes for invalid expressions and during editing expressions are invalid most of the time. if (token) { - const isExpression = - token.definition?.definitionType === DefinitionType.String && (token.definition as StringDefinition).isExpression; - const containsExpression = isString(token) && token.value.indexOf(OPEN_EXPRESSION) >= 0; - if (isString(token) && (isExpression || containsExpression)) { - const currentInput = token.source || token.value; - - // Transform the overall position into a node relative position - let relCharPos: number = 0; - const range = mapRange(token.range!); - if (range.start.line !== range.end.line) { - const lines = currentInput.split("\n"); - const lineDiff = newPos.line - range.start.line - 1; - const linesBeforeCusor = lines.slice(0, lineDiff); - relCharPos = linesBeforeCusor.join("\n").length + newPos.character + 1; - } else { - relCharPos = newPos.character - range.start.character; - } - - const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim(); + const isStringExpressionToken = isStringExpression(token); + const isBasicExpressionToken = isBasicExpression(token) && token.isExpression; + if (isStringExpressionToken || isBasicExpressionToken) { const allowedContext = token.definitionInfo?.allowedContext || []; const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion); - try { - return completeExpression(expressionInput, context, [], validatorFunctions).map(item => - mapExpressionCompletionItem(item, currentInput[relCharPos]) - ); - } catch (e: any) { - error(`Error while completing expression: '${e?.message || ""}'`); - return []; - } + return getExpressionCompletionItems(token, context, newPos); } } @@ -216,6 +200,35 @@ export function getExistingValues(token: TemplateToken | null, parent: TemplateT } } +function getExpressionCompletionItems( + token: TemplateToken, + context: DescriptionDictionary, + pos: Position +): CompletionItem[] { + let expressionInput = ""; + let currentInput = ""; + let relCharPos: number = 0; + + if (isBasicExpression(token)) { + expressionInput = currentInput = token.expression; + relCharPos = getRelCharPos(token.range!, expressionInput, pos); + } else { + const stringToken = token.assertString("Expected string token for expression completion"); + currentInput = stringToken.source || stringToken.value; + relCharPos = getRelCharPos(stringToken.range!, currentInput, pos); + expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim(); + } + + try { + return completeExpression(expressionInput, context, [], validatorFunctions).map(item => + mapExpressionCompletionItem(item, currentInput[relCharPos]) + ); + } catch (e: any) { + error(`Error while completing expression: '${e?.message || ""}'`); + return []; + } +} + function filterAndSortCompletionOptions(options: Value[], existingValues?: Set) { options = options.filter(x => !existingValues?.has(x.label)); options.sort((a, b) => a.label.localeCompare(b.label)); @@ -239,3 +252,23 @@ function mapExpressionCompletionItem(item: ExpressionCompletionItem, charAfterPo kind: item.function ? CompletionItemKind.Function : CompletionItemKind.Variable }; } + +function getRelCharPos(tokenRange: TokenRange, currentInput: string, pos: Position): number { + // Transform the overall position into a node relative position + const range = mapRange(tokenRange); + if (range.start.line !== range.end.line) { + const lines = currentInput.split("\n"); + const lineDiff = pos.line - range.start.line - 1; + const linesBeforeCusor = lines.slice(0, lineDiff); + return linesBeforeCusor.join("\n").length + pos.character + 1; + } else { + return pos.character - range.start.character; + } +} + +function isStringExpression(token: TemplateToken): boolean { + const isExpression = + token.definition?.definitionType === DefinitionType.String && (token.definition as StringDefinition).isExpression; + const containsExpression = isString(token) && token.value.indexOf(OPEN_EXPRESSION) >= 0; + return isExpression || containsExpression; +} diff --git a/actions-languageservice/src/context-providers/descriptions.ts b/actions-languageservice/src/context-providers/descriptions.ts index 766a442..73ef57d 100644 --- a/actions-languageservice/src/context-providers/descriptions.ts +++ b/actions-languageservice/src/context-providers/descriptions.ts @@ -1,4 +1,4 @@ -import descriptions from "./descriptions.json"; +import descriptions from "./descriptions.json" assert {type: "json"}; export const RootContext = "root"; diff --git a/actions-languageservice/src/context-providers/events/eventPayloads.ts b/actions-languageservice/src/context-providers/events/eventPayloads.ts index a5286a8..8bdb46d 100644 --- a/actions-languageservice/src/context-providers/events/eventPayloads.ts +++ b/actions-languageservice/src/context-providers/events/eventPayloads.ts @@ -1,55 +1,55 @@ -import check_run from "./check_run.json"; -import check_suite from "./check_suite.json"; -import commit_comment from "./commit_comment.json"; -import content_reference from "./content_reference.json"; -import create from "./create.json"; -import deletePayload from "./delete.json"; -import deploy_key from "./deploy_key.json"; -import deployment from "./deployment.json"; -import deployment_status from "./deployment_status.json"; -import fork from "./fork.json"; -import github_app_authorization from "./github_app_authorization.json"; -import gollum from "./gollum.json"; -import installation from "./installation.json"; -import installation_repositories from "./installation_repositories.json"; -import issue_comment from "./issue_comment.json"; -import issues from "./issues.json"; -import label from "./label.json"; -import marketplace_purchase from "./marketplace_purchase.json"; -import member from "./member.json"; -import membership from "./membership.json"; -import merge_group from "./merge_group.json"; -import meta from "./meta.json"; -import milestone from "./milestone.json"; -import org_block from "./org_block.json"; -import organization from "./organization.json"; -import packagePayload from "./package_payload.json"; -import page_build from "./page_build.json"; -import ping from "./ping.json"; -import project from "./project.json"; -import project_card from "./project_card.json"; -import project_column from "./project_column.json"; -import publicPayload from "./public.json"; -import pull_request from "./pull_request.json"; -import pull_request_review from "./pull_request_review.json"; -import pull_request_review_comment from "./pull_request_review_comment.json"; -import push from "./push.json"; -import release from "./release.json"; -import repository from "./repository.json"; -import repository_dispatch from "./repository_dispatch.json"; -import repository_import from "./repository_import.json"; -import repository_vulnerability_alert from "./repository_vulnerability_alert.json"; -import schedule from "./schedule.json"; -import security_advisory from "./security_advisory.json"; -import sponsorship from "./sponsorship.json"; -import star from "./star.json"; -import status from "./status.json"; -import team from "./team.json"; -import team_add from "./team_add.json"; -import watch from "./watch.json"; -import workflow_call from "./workflow_call.json"; -import workflow_dispatch from "./workflow_dispatch.json"; -import workflow_run from "./workflow_run.json"; +import check_run from "./check_run.json" assert {type: "json"}; +import check_suite from "./check_suite.json" assert {type: "json"}; +import commit_comment from "./commit_comment.json" assert {type: "json"}; +import content_reference from "./content_reference.json" assert {type: "json"}; +import create from "./create.json" assert {type: "json"}; +import deletePayload from "./delete.json" assert {type: "json"}; +import deploy_key from "./deploy_key.json" assert {type: "json"}; +import deployment from "./deployment.json" assert {type: "json"}; +import deployment_status from "./deployment_status.json" assert {type: "json"}; +import fork from "./fork.json" assert {type: "json"}; +import github_app_authorization from "./github_app_authorization.json" assert {type: "json"}; +import gollum from "./gollum.json" assert {type: "json"}; +import installation from "./installation.json" assert {type: "json"}; +import installation_repositories from "./installation_repositories.json" assert {type: "json"}; +import issue_comment from "./issue_comment.json" assert {type: "json"}; +import issues from "./issues.json" assert {type: "json"}; +import label from "./label.json" assert {type: "json"}; +import marketplace_purchase from "./marketplace_purchase.json" assert {type: "json"}; +import member from "./member.json" assert {type: "json"}; +import membership from "./membership.json" assert {type: "json"}; +import merge_group from "./merge_group.json" assert {type: "json"}; +import meta from "./meta.json" assert {type: "json"}; +import milestone from "./milestone.json" assert {type: "json"}; +import org_block from "./org_block.json" assert {type: "json"}; +import organization from "./organization.json" assert {type: "json"}; +import packagePayload from "./package_payload.json" assert {type: "json"}; +import page_build from "./page_build.json" assert {type: "json"}; +import ping from "./ping.json" assert {type: "json"}; +import project from "./project.json" assert {type: "json"}; +import project_card from "./project_card.json" assert {type: "json"}; +import project_column from "./project_column.json" assert {type: "json"}; +import publicPayload from "./public.json" assert {type: "json"}; +import pull_request from "./pull_request.json" assert {type: "json"}; +import pull_request_review from "./pull_request_review.json" assert {type: "json"}; +import pull_request_review_comment from "./pull_request_review_comment.json" assert {type: "json"}; +import push from "./push.json" assert {type: "json"}; +import release from "./release.json" assert {type: "json"}; +import repository from "./repository.json" assert {type: "json"}; +import repository_dispatch from "./repository_dispatch.json" assert {type: "json"}; +import repository_import from "./repository_import.json" assert {type: "json"}; +import repository_vulnerability_alert from "./repository_vulnerability_alert.json" assert {type: "json"}; +import schedule from "./schedule.json" assert {type: "json"}; +import security_advisory from "./security_advisory.json" assert {type: "json"}; +import sponsorship from "./sponsorship.json" assert {type: "json"}; +import star from "./star.json" assert {type: "json"}; +import status from "./status.json" assert {type: "json"}; +import team from "./team.json" assert {type: "json"}; +import team_add from "./team_add.json" assert {type: "json"}; +import watch from "./watch.json" assert {type: "json"}; +import workflow_call from "./workflow_call.json" assert {type: "json"}; +import workflow_dispatch from "./workflow_dispatch.json" assert {type: "json"}; +import workflow_run from "./workflow_run.json" assert {type: "json"}; export const eventPayloads: {[key: string]: Object} = { check_run, diff --git a/actions-languageservice/src/hover.test.ts b/actions-languageservice/src/hover.test.ts index ac25ff8..fe017dc 100644 --- a/actions-languageservice/src/hover.test.ts +++ b/actions-languageservice/src/hover.test.ts @@ -17,7 +17,7 @@ function testHoverConfig(tokenValue: string, tokenKey: string, description?: str return description; } } satisfies DescriptionProvider - } satisfies HoverConfig + } satisfies HoverConfig; } describe("hover", () => { @@ -146,7 +146,8 @@ jobs: expect(result).not.toBeUndefined(); // The `ref` is a `string` definition and inherits the context from `step-with` - const expected = "**Context:** github, inputs, vars, needs, strategy, matrix, secrets, steps, job, runner, env, hashFiles(1,255)" + const expected = + "**Context:** github, inputs, vars, needs, strategy, matrix, secrets, steps, job, runner, env, hashFiles(1,255)"; expect(result?.contents).toEqual(expected); }); }); @@ -164,11 +165,15 @@ jobs: ref|: main `; - const result = await hover(...getPositionFromCursor(input), testHoverConfig("ref", "string", "The branch, tag or SHA to checkout.")); + const result = await hover( + ...getPositionFromCursor(input), + testHoverConfig("ref", "string", "The branch, tag or SHA to checkout.") + ); expect(result).not.toBeUndefined(); - const expected = "The branch, tag or SHA to checkout.\n\n" + - "**Context:** github, inputs, vars, needs, strategy, matrix, secrets, steps, job, runner, env, hashFiles(1,255)" + const expected = + "The branch, tag or SHA to checkout.\n\n" + + "**Context:** github, inputs, vars, needs, strategy, matrix, secrets, steps, job, runner, env, hashFiles(1,255)"; expect(result?.contents).toEqual(expected); }); @@ -184,6 +189,8 @@ jobs: const result = await hover(...getPositionFromCursor(input), testHoverConfig("uses", "non-empty-string", undefined)); expect(result).not.toBeUndefined(); - expect(result?.contents).toEqual("Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image."); + expect(result?.contents).toEqual( + "Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image." + ); }); }); diff --git a/actions-languageservice/src/hover.ts b/actions-languageservice/src/hover.ts index b24536a..eab154e 100644 --- a/actions-languageservice/src/hover.ts +++ b/actions-languageservice/src/hover.ts @@ -39,7 +39,7 @@ export async function hover(document: TextDocument, position: Position, config?: info(`Calculating hover for token with definition ${token.definition.key}`); if (tokenResult.parent && isCronMappingValue(tokenResult)) { - const tokenValue = (token as StringToken).value + const tokenValue = (token as StringToken).value; let description = getCronDescription(tokenValue); if (description) { return { @@ -82,7 +82,9 @@ async function getDescription( } function isCronMappingValue(tokenResult: TokenResult): boolean { - return tokenResult.parent?.definition?.key === "cron-mapping" && + return ( + tokenResult.parent?.definition?.key === "cron-mapping" && isString(tokenResult.token!) && tokenResult.token.value !== "cron" + ); } diff --git a/actions-languageservice/src/utils/find-token.ts b/actions-languageservice/src/utils/find-token.ts index b133001..bf2b744 100644 --- a/actions-languageservice/src/utils/find-token.ts +++ b/actions-languageservice/src/utils/find-token.ts @@ -141,13 +141,16 @@ function posInToken(pos: Position, token: TemplateToken): boolean { const tokenChar = pos.character + 1; // Check lines - if (r.start[0] > tokenLine || tokenLine > r.end[0]) { + if (r.start.line > tokenLine || tokenLine > r.end.line) { return false; } // Position is within the token lines. Check character/column if pos line matches // start or end - if ((r.start[0] === tokenLine && tokenChar < r.start[1]) || (r.end[0] === tokenLine && tokenChar > r.end[1])) { + if ( + (r.start.line === tokenLine && tokenChar < r.start.column) || + (r.end.line === tokenLine && tokenChar > r.end.column) + ) { return false; } @@ -163,14 +166,14 @@ function onSameLine(pos: Position, key: TemplateToken, value: TemplateToken): bo return false; } - if (value.range.start[0] !== value.range.end[0]) { + if (value.range.start.line !== value.range.end.line) { // Token occupies multiple lines, can't be an empty node return false; } // TokenRange is one-based, Position is zero-based const posLine = pos.line + 1; - if (posLine != value.range.start[0]) { + if (posLine != value.range.start.line) { return false; } diff --git a/actions-languageservice/src/utils/range.ts b/actions-languageservice/src/utils/range.ts index 13e6c5a..bba22cc 100644 --- a/actions-languageservice/src/utils/range.ts +++ b/actions-languageservice/src/utils/range.ts @@ -23,7 +23,7 @@ export function mapRange(range: TokenRange | undefined): Range { export function mapPosition(position: TokenPosition): Position { return { - line: position[0] - 1, - character: position[1] - 1 + line: position.line - 1, + character: position.column - 1 }; } diff --git a/actions-languageservice/src/validate.test.ts b/actions-languageservice/src/validate.test.ts index ea0e993..05b8514 100644 --- a/actions-languageservice/src/validate.test.ts +++ b/actions-languageservice/src/validate.test.ts @@ -73,21 +73,7 @@ jobs: {valueProviderConfig: defaultValueProviders} ); - expect(result.length).toBe(1); - expect(result[0]).toEqual({ - message: "Value 'does-not-exist' might not be valid", - severity: DiagnosticSeverity.Warning, - range: { - end: { - character: 27, - line: 3 - }, - start: { - character: 13, - line: 3 - } - } - } as Diagnostic); + expect(result.length).toBe(0); }); it("value in sequence not returned by value provider", async () => { @@ -106,21 +92,7 @@ jobs: {valueProviderConfig: defaultValueProviders} ); - expect(result.length).toBe(1); - expect(result[0]).toEqual({ - message: "Value 'does-not-exist' might not be valid", - severity: DiagnosticSeverity.Warning, - range: { - end: { - character: 20, - line: 5 - }, - start: { - character: 6, - line: 5 - } - } - } as Diagnostic); + expect(result.length).toBe(0); }); it("single value not returned by allowed value provider", async () => { diff --git a/actions-languageservice/src/validate.ts b/actions-languageservice/src/validate.ts index 0f82985..3477abb 100644 --- a/actions-languageservice/src/validate.ts +++ b/actions-languageservice/src/validate.ts @@ -150,13 +150,7 @@ function invalidValue(diagnostics: Diagnostic[], token: StringToken, kind: Value }); break; - case ValueProviderKind.SuggestedValues: - diagnostics.push({ - message: `Value '${token.value}' might not be valid`, - severity: DiagnosticSeverity.Warning, - range: mapRange(token.range) - }); - break; + // no messages for SuggestedValues } } @@ -168,8 +162,8 @@ function getProviderContext( ): WorkflowContext { const {parent, path} = findToken( { - line: token.range!.start[0] - 1, - character: token.range!.start[1] - 1 + line: token.range!.start.line - 1, + character: token.range!.start.column - 1 }, root ); diff --git a/actions-workflow-parser/package.json b/actions-workflow-parser/package.json index fc3952b..bba2791 100644 --- a/actions-workflow-parser/package.json +++ b/actions-workflow-parser/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-workflow-parser", - "version": "0.1.100", + "version": "0.1.109", "license": "MIT", "type": "module", "source": "./src/index.ts", @@ -40,12 +40,12 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.100", + "@github/actions-expressions": "^0.1.109", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" }, "files": [ "dist/**/*" @@ -56,7 +56,7 @@ "@typescript-eslint/parser": "^5.40.0", "eslint": "7.32.0", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4" diff --git a/actions-workflow-parser/src/expressions.test.ts b/actions-workflow-parser/src/expressions.test.ts index f45eaea..8bdc916 100644 --- a/actions-workflow-parser/src/expressions.test.ts +++ b/actions-workflow-parser/src/expressions.test.ts @@ -85,15 +85,15 @@ jobs: [ "${{ github.event_name }}", { - start: [7, 16], - end: [7, 40] + start: {line: 7, column: 16}, + end: {line: 7, column: 40} } ], [ "${{ github.ref }}", { - start: [8, 24], - end: [8, 41] + start: {line: 8, column: 24}, + end: {line: 8, column: 41} } ] ]); @@ -160,16 +160,16 @@ jobs: { prefix: "test.yaml (Line: 6, Col: 14)", range: { - start: [7, 16], - end: [7, 40] + start: {line: 7, column: 16}, + end: {line: 7, column: 40} }, rawMessage: "Unrecognized function: 'fromJSON2'" }, { prefix: "test.yaml (Line: 6, Col: 14)", range: { - start: [8, 24], - end: [8, 51] + start: {line: 8, column: 24}, + end: {line: 8, column: 51} }, rawMessage: "Unrecognized function: 'toJSON2'" } diff --git a/actions-workflow-parser/src/index.test.ts b/actions-workflow-parser/src/index.test.ts index de071cb..b316089 100644 --- a/actions-workflow-parser/src/index.test.ts +++ b/actions-workflow-parser/src/index.test.ts @@ -32,8 +32,8 @@ describe("parseWorkflow", () => { expect(result.context.errors.getErrors()).toEqual([ new TemplateValidationError("Required property is missing: runs-on", "test.yaml (Line: 4, Col: 5)", undefined, { - start: [4, 5], - end: [5, 24] + start: {line: 4, column: 5}, + end: {line: 5, column: 24} }) ]); }); @@ -62,8 +62,8 @@ jobs: "test.yaml (Line: 6, Col: 13)", undefined, { - start: [6, 13], - end: [6, 37] + start: {line: 6, column: 13}, + end: {line: 6, column: 37} } ) ]); diff --git a/actions-workflow-parser/src/templates/template-reader.ts b/actions-workflow-parser/src/templates/template-reader.ts index f7290a2..d00ea75 100644 --- a/actions-workflow-parser/src/templates/template-reader.ts +++ b/actions-workflow-parser/src/templates/template-reader.ts @@ -455,7 +455,7 @@ class TemplateReader { // Doesn't contain "${{" // Check if value should still be evaluated as an expression if (definitionInfo.definition instanceof StringDefinition && definitionInfo.definition.isExpression) { - const expression = this.parseIntoExpressionToken(token.range!, raw, allowedContext, token); + const expression = this.parseIntoExpressionToken(token.range!, raw, allowedContext, token, definitionInfo); if (expression) { return expression; } @@ -500,11 +500,11 @@ class TemplateReader { ); let tr = token.range!; - if (tr.start[0] === tr.end[0]) { + if (tr.start.line === tr.end.line) { // If it's a single line expression, adjust the range to only cover the sub-expression tr = { - start: [tr.start[0], tr.start[1] + startExpression], - end: [tr.end[0], tr.start[1] + endExpression + 1] + start: {line: tr.start.line, column: tr.start.column + startExpression}, + end: {line: tr.end.line, column: tr.start.column + endExpression + 1} }; } else { // Adjust the range to only cover the expression for multi-line strings @@ -515,12 +515,12 @@ class TemplateReader { const adjustedEnd = endExpression - beginningOfLine + 1; tr = { - start: [tr.start[0] + adjustedStartLine, adjustedStart], - end: [tr.start[0] + adjustedStartLine, adjustedEnd] + start: {line: tr.start.line + adjustedStartLine, column: adjustedStart}, + end: {line: tr.start.line + adjustedStartLine, column: adjustedEnd} }; } - const expression = this.parseIntoExpressionToken(tr, rawExpression, allowedContext, token); + const expression = this.parseIntoExpressionToken(tr, rawExpression, allowedContext, token, definitionInfo); if (!expression) { // Record that we've hit an error but continue to validate any other expressions @@ -615,9 +615,10 @@ class TemplateReader { tr: TokenRange, rawExpression: string, allowedContext: string[], - token: TemplateToken + token: TemplateToken, + definitionInfo: DefinitionInfo | undefined ): ExpressionToken | undefined { - const parseExpressionResult = this.parseExpression(tr, rawExpression, allowedContext, token.definitionInfo); + const parseExpressionResult = this.parseExpression(tr, rawExpression, allowedContext, definitionInfo); // Check for error if (parseExpressionResult.error) { diff --git a/actions-workflow-parser/src/templates/tokens/template-token.ts b/actions-workflow-parser/src/templates/tokens/template-token.ts index 5257b3c..9aab233 100644 --- a/actions-workflow-parser/src/templates/tokens/template-token.ts +++ b/actions-workflow-parser/src/templates/tokens/template-token.ts @@ -42,11 +42,11 @@ export abstract class TemplateToken { } public get line(): number | undefined { - return this.range?.start[0]; + return this.range?.start.line; } public get col(): number | undefined { - return this.range?.start[1]; + return this.range?.start.column; } public get definition(): Definition | undefined { diff --git a/actions-workflow-parser/src/templates/tokens/token-range.ts b/actions-workflow-parser/src/templates/tokens/token-range.ts index 34b1f90..03d5f8e 100644 --- a/actions-workflow-parser/src/templates/tokens/token-range.ts +++ b/actions-workflow-parser/src/templates/tokens/token-range.ts @@ -1,4 +1,10 @@ -export type Position = [line: number, character: number]; +/** Represents the position within a template object */ +export type Position = { + /** The one-based line value */ + line: number; + /** The one-based column value */ + column: number; +}; export type TokenRange = { start: Position; diff --git a/actions-workflow-parser/src/workflows/workflow-schema.ts b/actions-workflow-parser/src/workflows/workflow-schema.ts index d2b7dc2..df45e88 100644 --- a/actions-workflow-parser/src/workflows/workflow-schema.ts +++ b/actions-workflow-parser/src/workflows/workflow-schema.ts @@ -1,6 +1,6 @@ import {JSONObjectReader} from "../templates/json-object-reader"; import {TemplateSchema} from "../templates/schema"; -import WorkflowSchema from "../workflow-v1.0.json"; +import WorkflowSchema from "../workflow-v1.0.json" assert {type: "json"}; let schema: TemplateSchema; diff --git a/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts b/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts index 9813dff..4ff3b13 100644 --- a/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts +++ b/actions-workflow-parser/src/workflows/yaml-object-reader.test.ts @@ -74,8 +74,8 @@ it("YAML errors include range information", () => { const error = context.errors.getErrors()[0]; expect(error.range).toEqual({ - start: [7, 38], - end: [7, 63] + start: {line: 7, column: 38}, + end: {line: 7, column: 63} }); }); diff --git a/actions-workflow-parser/src/workflows/yaml-object-reader.ts b/actions-workflow-parser/src/workflows/yaml-object-reader.ts index e4bc3d9..d64c4c5 100644 --- a/actions-workflow-parser/src/workflows/yaml-object-reader.ts +++ b/actions-workflow-parser/src/workflows/yaml-object-reader.ts @@ -89,8 +89,8 @@ export class YamlObjectReader implements ObjectReader { const elp = this.lineCounter.linePos(endPos); return { - start: [slp.line, slp.col], - end: [elp.line, elp.col] + start: {line: slp.line, column: slp.col}, + end: {line: elp.line, column: elp.col} }; } @@ -216,7 +216,7 @@ function rangeFromLinePos(linePos: [LinePos] | [LinePos, LinePos] | undefined): return; } // TokenRange and linePos are both 1-based - const start: Position = [linePos[0].line, linePos[0].col]; - const end: Position = linePos.length == 2 ? [linePos[1].line, linePos[1].col] : start; + const start: Position = {line: linePos[0].line, column: linePos[0].col}; + const end: Position = linePos.length == 2 ? {line: linePos[1].line, column: linePos[1].col} : start; return {start, end}; } diff --git a/browser-playground/package.json b/browser-playground/package.json index 90dd297..1c9b95c 100644 --- a/browser-playground/package.json +++ b/browser-playground/package.json @@ -1,12 +1,12 @@ { "name": "browser-playground", - "version": "0.1.100", + "version": "0.1.109", "description": "", "private": true, "main": "index.js", "type": "module", "dependencies": { - "@github/actions-languageserver": "^0.1.100", + "@github/actions-languageserver": "^0.1.109", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", diff --git a/lerna.json b/lerna.json index 23bb32a..a87db82 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, - "version": "0.1.100" + "version": "0.1.109" } diff --git a/package-lock.json b/package-lock.json index ad876ad..2859a94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,28 +18,28 @@ }, "actions-expressions": { "name": "@github/actions-expressions", - "version": "0.1.100", + "version": "0.1.109", "license": "MIT", "devDependencies": { "@types/jest": "^29.0.3", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.7.4" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" } }, "actions-languageserver": { "name": "@github/actions-languageserver", - "version": "0.1.100", + "version": "0.1.109", "license": "MIT", "dependencies": { - "@github/actions-languageservice": "^0.1.100", - "@github/actions-workflow-parser": "^0.1.100", - "@octokit/rest": "^19.0.5", + "@github/actions-languageservice": "^0.1.109", + "@github/actions-workflow-parser": "^0.1.109", + "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", "yaml": "^2.1.3" @@ -48,22 +48,22 @@ "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" } }, "actions-languageservice": { "name": "@github/actions-languageservice", - "version": "0.1.100", + "version": "0.1.109", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.100", - "@github/actions-workflow-parser": "^0.1.100", + "@github/actions-expressions": "^0.1.109", + "@github/actions-workflow-parser": "^0.1.109", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" @@ -71,21 +71,21 @@ "devDependencies": { "@types/jest": "^29.0.3", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" } }, "actions-workflow-parser": { "name": "@github/actions-workflow-parser", - "version": "0.1.100", + "version": "0.1.109", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.100", + "@github/actions-expressions": "^0.1.109", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, @@ -95,20 +95,20 @@ "@typescript-eslint/parser": "^5.40.0", "eslint": "7.32.0", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4" }, "engines": { - "node": ">= 16" + "node": ">= 16.15" } }, "browser-playground": { - "version": "0.1.100", + "version": "0.1.109", "license": "MIT", "dependencies": { - "@github/actions-languageserver": "^0.1.100", + "@github/actions-languageserver": "^0.1.109", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", @@ -2904,11 +2904,11 @@ "dev": true }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz", - "integrity": "sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz", + "integrity": "sha512-Sq5VU1PfT6/JyuXPyt04KZNVsFOSBaYOAq2QRZUwzVlI10KFvcbUo8lR258AAQL1Et60b0WuVik+zOWKLuDZxw==", "dependencies": { - "@octokit/types": "^8.0.0" + "@octokit/types": "^9.0.0" }, "engines": { "node": ">= 14" @@ -2917,6 +2917,19 @@ "@octokit/core": ">=4" } }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", + "dependencies": { + "@octokit/openapi-types": "^16.0.0" + } + }, "node_modules/@octokit/plugin-request-log": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", @@ -2926,11 +2939,11 @@ } }, "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz", - "integrity": "sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.0.1.tgz", + "integrity": "sha512-pnCaLwZBudK5xCdrR823xHGNgqOzRnJ/mpC/76YPpNP7DybdsJtP7mdOwh+wYZxK5jqeQuhu59ogMI4NRlBUvA==", "dependencies": { - "@octokit/types": "^8.0.0", + "@octokit/types": "^9.0.0", "deprecation": "^2.3.1" }, "engines": { @@ -2940,6 +2953,19 @@ "@octokit/core": ">=3" } }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", + "dependencies": { + "@octokit/openapi-types": "^16.0.0" + } + }, "node_modules/@octokit/request": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz", @@ -2970,14 +2996,14 @@ } }, "node_modules/@octokit/rest": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.5.tgz", - "integrity": "sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow==", + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.7.tgz", + "integrity": "sha512-HRtSfjrWmWVNp2uAkEpQnuGMJsu/+dBr47dRc5QVgsCbnIc1+GFEaoKBWkYG+zjrsHpSqcAElMio+n10c0b5JA==", "dependencies": { "@octokit/core": "^4.1.0", - "@octokit/plugin-paginate-rest": "^5.0.0", + "@octokit/plugin-paginate-rest": "^6.0.0", "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^6.7.0" + "@octokit/plugin-rest-endpoint-methods": "^7.0.0" }, "engines": { "node": ">= 14" @@ -10998,9 +11024,9 @@ } }, "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -14535,7 +14561,7 @@ "requires": { "@types/jest": "^29.0.3", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.7.4" @@ -14544,13 +14570,13 @@ "@github/actions-languageserver": { "version": "file:actions-languageserver", "requires": { - "@github/actions-languageservice": "^0.1.100", - "@github/actions-workflow-parser": "^0.1.100", - "@octokit/rest": "^19.0.5", + "@github/actions-languageservice": "^0.1.109", + "@github/actions-workflow-parser": "^0.1.109", + "@octokit/rest": "^19.0.7", "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4", @@ -14562,11 +14588,11 @@ "@github/actions-languageservice": { "version": "file:actions-languageservice", "requires": { - "@github/actions-expressions": "^0.1.100", - "@github/actions-workflow-parser": "^0.1.100", + "@github/actions-expressions": "^0.1.109", + "@github/actions-workflow-parser": "^0.1.109", "@types/jest": "^29.0.3", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4", @@ -14578,14 +14604,14 @@ "@github/actions-workflow-parser": { "version": "file:actions-workflow-parser", "requires": { - "@github/actions-expressions": "^0.1.100", + "@github/actions-expressions": "^0.1.109", "@types/jest": "^29.0.3", "@typescript-eslint/eslint-plugin": "^5.40.0", "@typescript-eslint/parser": "^5.40.0", "cronstrue": "^2.21.0", "eslint": "7.32.0", "jest": "^29.0.3", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "rimraf": "^3.0.2", "ts-jest": "^29.0.3", "typescript": "^4.8.4", @@ -16280,11 +16306,26 @@ "dev": true }, "@octokit/plugin-paginate-rest": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz", - "integrity": "sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz", + "integrity": "sha512-Sq5VU1PfT6/JyuXPyt04KZNVsFOSBaYOAq2QRZUwzVlI10KFvcbUo8lR258AAQL1Et60b0WuVik+zOWKLuDZxw==", "requires": { - "@octokit/types": "^8.0.0" + "@octokit/types": "^9.0.0" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==" + }, + "@octokit/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", + "requires": { + "@octokit/openapi-types": "^16.0.0" + } + } } }, "@octokit/plugin-request-log": { @@ -16294,12 +16335,27 @@ "requires": {} }, "@octokit/plugin-rest-endpoint-methods": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz", - "integrity": "sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.0.1.tgz", + "integrity": "sha512-pnCaLwZBudK5xCdrR823xHGNgqOzRnJ/mpC/76YPpNP7DybdsJtP7mdOwh+wYZxK5jqeQuhu59ogMI4NRlBUvA==", "requires": { - "@octokit/types": "^8.0.0", + "@octokit/types": "^9.0.0", "deprecation": "^2.3.1" + }, + "dependencies": { + "@octokit/openapi-types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-16.0.0.tgz", + "integrity": "sha512-JbFWOqTJVLHZSUUoF4FzAZKYtqdxWu9Z5m2QQnOyEa04fOFljvyh7D3GYKbfuaSWisqehImiVIMG4eyJeP5VEA==" + }, + "@octokit/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.0.0.tgz", + "integrity": "sha512-LUewfj94xCMH2rbD5YJ+6AQ4AVjFYTgpp6rboWM5T7N3IsIF65SBEOVcYMGAEzO/kKNiNaW4LoWtoThOhH06gw==", + "requires": { + "@octokit/openapi-types": "^16.0.0" + } + } } }, "@octokit/request": { @@ -16326,14 +16382,14 @@ } }, "@octokit/rest": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.5.tgz", - "integrity": "sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow==", + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.7.tgz", + "integrity": "sha512-HRtSfjrWmWVNp2uAkEpQnuGMJsu/+dBr47dRc5QVgsCbnIc1+GFEaoKBWkYG+zjrsHpSqcAElMio+n10c0b5JA==", "requires": { "@octokit/core": "^4.1.0", - "@octokit/plugin-paginate-rest": "^5.0.0", + "@octokit/plugin-paginate-rest": "^6.0.0", "@octokit/plugin-request-log": "^1.0.4", - "@octokit/plugin-rest-endpoint-methods": "^6.7.0" + "@octokit/plugin-rest-endpoint-methods": "^7.0.0" } }, "@octokit/types": { @@ -17506,7 +17562,7 @@ "browser-playground": { "version": "file:browser-playground", "requires": { - "@github/actions-languageserver": "^0.1.100", + "@github/actions-languageserver": "^0.1.109", "css-loader": "^6.7.2", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", @@ -22508,9 +22564,9 @@ "dev": true }, "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz", + "integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==", "dev": true }, "pretty-format": {