From 90e7932e97db17e1c0a5f1e3fbc7f61d3bba2bdf Mon Sep 17 00:00:00 2001 From: eric sciple Date: Tue, 30 Dec 2025 10:30:19 -0600 Subject: [PATCH] Add runs-on label completions for mapping syntax (#273) Provides runner label completions (ubuntu-latest, macos-latest, etc.) when using the runs-on mapping syntax with the labels property: jobs: build: runs-on: labels: | jobs: build: runs-on: labels: - | Previously, completions only worked for the simple runs-on syntax: jobs: build: runs-on: | The fix registers the same value provider for both 'runs-on' and 'runs-on-labels' definition keys in the schema. --- languageservice/src/complete.test.ts | 47 +++++++++++++++++++ .../src/value-providers/default.ts | 11 +++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/languageservice/src/complete.test.ts b/languageservice/src/complete.test.ts index e9fa920..9ffa945 100644 --- a/languageservice/src/complete.test.ts +++ b/languageservice/src/complete.test.ts @@ -825,4 +825,51 @@ jobs: expect(textEdit.newText).toEqual("runs-on:\n - "); }); }); + + describe("runs-on mapping syntax", () => { + it("provides label completions for labels as scalar", async () => { + const input = `on: push +jobs: + build: + runs-on: + labels: |`; + const result = await complete(...getPositionFromCursor(input)); + + // Should show runner labels + expect(result.some(x => x.label === "ubuntu-latest")).toBe(true); + expect(result.some(x => x.label === "macos-latest")).toBe(true); + expect(result.some(x => x.label === "self-hosted")).toBe(true); + }); + + it("provides label completions for labels as sequence item", async () => { + const input = `on: push +jobs: + build: + runs-on: + labels: + - |`; + const result = await complete(...getPositionFromCursor(input)); + + // Should show runner labels + expect(result.some(x => x.label === "ubuntu-latest")).toBe(true); + expect(result.some(x => x.label === "macos-latest")).toBe(true); + expect(result.some(x => x.label === "self-hosted")).toBe(true); + }); + + it("excludes already used labels in sequence", async () => { + const input = `on: push +jobs: + build: + runs-on: + labels: + - ubuntu-latest + - |`; + const result = await complete(...getPositionFromCursor(input)); + + // Should NOT show ubuntu-latest since it's already in the list + expect(result.some(x => x.label === "ubuntu-latest")).toBe(false); + // But should show other labels + expect(result.some(x => x.label === "macos-latest")).toBe(true); + }); + }); }); diff --git a/languageservice/src/value-providers/default.ts b/languageservice/src/value-providers/default.ts index f31c7f9..2ece56f 100644 --- a/languageservice/src/value-providers/default.ts +++ b/languageservice/src/value-providers/default.ts @@ -19,6 +19,11 @@ export const DEFAULT_RUNNER_LABELS = [ "self-hosted" ]; +const runsOnValueProvider = { + kind: ValueProviderKind.SuggestedValues, + get: () => Promise.resolve(stringsToValues(DEFAULT_RUNNER_LABELS)) +}; + export const defaultValueProviders: ValueProviderConfig = { needs: { kind: ValueProviderKind.AllowedValues, @@ -32,8 +37,6 @@ export const defaultValueProviders: ValueProviderConfig = { kind: ValueProviderKind.SuggestedValues, get: (context, existingValues) => Promise.resolve(reusableJobSecrets(context, existingValues)) }, - "runs-on": { - kind: ValueProviderKind.SuggestedValues, - get: () => Promise.resolve(stringsToValues(DEFAULT_RUNNER_LABELS)) - } + "runs-on": runsOnValueProvider, + "runs-on-labels": runsOnValueProvider };