Compare commits

..

1 Commits

Author SHA1 Message Date
Francesco Renzi de148476d4 Add actions-languageserver executable 2025-12-09 10:55:07 +00:00
192 changed files with 836 additions and 1866 deletions
-299
View File
@@ -1,299 +0,0 @@
# ESM Migration Plan: Add File Extensions to Imports
## Overview
This document outlines the plan to migrate from TypeScript's deprecated `"moduleResolution": "node"` (node10) to `"moduleResolution": "node16"` or `"nodenext"`. This change is necessary because the published ESM packages have extensionless imports that don't work correctly in modern ESM environments.
## Issues Fixed
This migration will resolve the following issues:
- **#154** - Upgrade `moduleResolution` from `node` to `node16` or `nodenext` in tsconfig
- **#110** - Published ESM code has imports without file extensions
- **#64** - expressions: ERR_MODULE_NOT_FOUND attempting to run example demo script
- **#146** - Can not import `@actions/workflow-parser`
## Problem Statement
### Current State
All packages use `"moduleResolution": "node"`:
| Package | moduleResolution | TypeScript |
|---------|------------------|------------|
| expressions | `"node"` | ^4.7.4 |
| workflow-parser | `"node"` | ^4.8.4 |
| languageservice | `"node"` | ^4.8.4 |
| languageserver | `"node"` | ^4.8.4 |
| browser-playground | `"Node16"` ✅ | ^4.9.4 |
This causes TypeScript to emit code like:
```javascript
// Published to npm - INVALID ESM
export { Expr } from "./ast"; // Missing .js extension!
```
### Why This Fails
ESM in Node.js 12+ **requires** explicit file extensions. When users try to import these packages:
```javascript
// User's code
import { Expr } from "@actions/expressions";
```
Node.js fails with:
```
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../node_modules/@actions/expressions/dist/ast'
```
## Migration Strategy
### Option A: TypeScript 5.7+ with `rewriteRelativeImportExtensions` (Recommended)
TypeScript 5.7 introduced a new compiler option that automatically rewrites `.ts` extensions to `.js` in output:
```jsonc
{
"compilerOptions": {
"moduleResolution": "node16", // or "nodenext"
"rewriteRelativeImportExtensions": true
}
}
```
**Source code:**
```typescript
import { Expr } from "./ast.ts";
```
**Compiled output:**
```javascript
export { Expr } from "./ast.js";
```
**Pros:**
- Source uses `.ts` extensions (matches actual files)
- Works with Deno (which requires `.ts` extensions)
- TypeScript automatically transforms to `.js`
- Modern, forward-looking approach
**Cons:**
- Requires TypeScript 5.7+
- Relatively new feature
- **BUG:** See "Known Issues" section below
### Option B: Manual `.js` Extensions
Use `.js` extensions in source TypeScript files:
```typescript
import { Expr } from "./ast.js"; // Points to .ts file, but use .js extension
```
**Pros:**
- Works with TypeScript 4.7+ (with node16 moduleResolution)
- Well-established pattern
- No post-processing needed
- Works with ts-jest without extra configuration
**Cons:**
- Confusing - `.js` files don't exist at write time
- Doesn't work with Deno out of the box
### Recommendation
**Use Option B** (manual `.js` extensions). Option A with `rewriteRelativeImportExtensions` has compatibility issues with ts-jest and requires additional workarounds.
---
## Known Issues and Workarounds (December 2025)
### 1. TypeScript Version Conflicts in Monorepo
**Problem:** The root `node_modules/typescript` was version 4.9.5 (pulled in by `ts-node` and `tsutils` dependencies), while workspace packages specified `^5.8.3`.
**Symptoms:**
- `npx tsc --version` showed 4.9.5
- `require('typescript').version` in ts-jest showed 5.8.3
- Confusing build failures
**Solution:** Add npm overrides in root `package.json`:
```json
{
"overrides": {
"typescript": "5.8.3"
}
}
```
### 2. ts-jest Compatibility with TypeScript 5.9+
**Problem:** ts-jest 29.4.6 uses `typescript.JSDocParsingMode.ParseAll` which doesn't exist in TypeScript's ES module exports.
**Error:**
```
TypeError: Cannot read properties of undefined (reading 'ParseAll')
at Object.<anonymous> (node_modules/ts-jest/dist/compiler/ts-compiler.js:43:123)
```
**Root Cause:** ts-jest accesses `typescript_1.default.JSDocParsingMode.ParseAll` but TypeScript has no default export in ESM.
**Solution:**
- Use ts-jest 29.0.3 (older version that doesn't use this API)
- OR wait for ts-jest fix
- **Stay on TypeScript 5.8.3, not 5.9+**
### 3. TypeScript `rewriteRelativeImportExtensions` Bug with .d.ts Files
**Problem:** TypeScript's `rewriteRelativeImportExtensions: true` correctly rewrites `.ts``.js` in `.js` output files, but **incorrectly keeps `.ts` extensions in `.d.ts` declaration files**.
**Example:**
- Source: `export { Expr } from "./ast.ts";`
- Output `index.js`: `export { Expr } from "./ast.js";` ✅ Correct
- Output `index.d.ts`: `export { Expr } from "./ast.ts";` ❌ Wrong (should be `.js`)
**Upstream Issue:** https://github.com/microsoft/TypeScript/issues/61037 (marked "Help Wanted", in Backlog, NOT FIXED as of Dec 2025)
**Workaround:** Post-process `.d.ts` files with a script. See `script/fix-dts-extensions.cjs`.
**Note:** Since we use Option B (manual `.js` extensions), this bug does not affect our migration.
### 4. yaml Package Internal Types Not Exported
**Problem:** The `yaml` package does not export internal types like `LinePos` and `NodeBase` that are used in `workflow-parser/src/workflows/yaml-object-reader.ts`.
**Error:**
```
error TS2305: Module '"yaml"' has no exported member 'LinePos'.
error TS2305: Module '"yaml"' has no exported member 'NodeBase'.
```
**Solution:** Define local type aliases in the file that uses them:
```typescript
// Local type definitions to replace yaml internal imports
type LinePos = { line: number; col: number };
type NodeBase = { range?: [number, number, number] };
```
### 5. languageserver Blocked by vscode-languageserver Dependency
**Problem:** The `vscode-languageserver` package (v8.0.2) does not have proper ESM exports. When using `moduleResolution: "node16"`, TypeScript requires packages to have an `exports` field in `package.json` for subpath imports to work.
**Error:**
```
src/index.ts(6,8): error TS2307: Cannot find module 'vscode-languageserver/browser' or its corresponding type declarations.
src/connection.ts(1,43): error TS2307: Cannot find module 'vscode-languageserver/node' or its corresponding type declarations.
```
**Root Cause:** The `vscode-languageserver` package.json only has `main` and `browser` fields, but no `exports` field:
```json
{
"main": "./lib/node/main.js",
"browser": {
"./lib/node/main.js": "./lib/browser/main.js"
}
// No "exports" field!
}
```
With `moduleResolution: "node16"`, TypeScript follows Node.js ESM resolution rules which require explicit `exports` for subpath imports like `vscode-languageserver/browser` and `vscode-languageserver/node`.
**Status:** Verified December 2025. Version 9.0.1 is available but ESM export support is not confirmed.
**Current Decision:** The languageserver package is **deferred** from this migration until the upstream `vscode-languageserver` package adds proper ESM exports. It will continue using the old `moduleResolution: "node"` configuration.
**Options to resolve:**
- Wait for vscode-languageserver to add ESM exports
- Try upgrading to vscode-languageserver v9.x to see if exports were added
- Use a bundler to work around the module resolution
- Fork or patch the dependency
---
## Migration Status
| Package | Tests | ESM Status |
|---------|-------|------------|
| expressions | 1068 | ✅ Migrated |
| workflow-parser | 292 | ✅ Migrated |
| languageservice | 452 | ✅ Migrated |
| languageserver | 6 files | ⏸️ Deferred (vscode-languageserver lacks ESM exports) |
---
## Required Configuration Changes
### tsconfig.build.json (each migrated package)
**Note:** We use **Option B** (manual `.js` extensions in source files) rather than `rewriteRelativeImportExtensions` because Option A caused ts-jest compatibility issues (tests would hang indefinitely).
```json
{
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16",
"skipLibCheck": true,
"lib": ["ES2022"],
"target": "ES2022"
}
}
```
The `skipLibCheck: true` is needed to work around @types/node compatibility issues with TypeScript 5.x (TS2386 overload signature errors).
```
### jest.config.js (each migrated package)
```javascript
/** @type {import('ts-jest').JestConfigWithTsJest} */
export default {
preset: "ts-jest/presets/default-esm",
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1",
"^(\\.{1,2}/.*)\\.ts$": "$1",
},
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{
useESM: true,
isolatedModules: true,
},
],
},
moduleFileExtensions: ["ts", "js"],
};
```
### Root package.json
```json
{
"overrides": {
"typescript": "5.8.3"
}
}
```
### Each workspace package.json
```json
{
"devDependencies": {
"typescript": "^5.8.3",
"ts-jest": "^29.0.3"
}
}
```
---
## References
- [TypeScript moduleResolution reference](https://www.typescriptlang.org/docs/handbook/modules/reference.html)
- [TypeScript 5.7 rewriteRelativeImportExtensions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-7.html#path-rewriting-for-relative-paths)
- [TypeScript .d.ts extension bug #61037](https://github.com/microsoft/TypeScript/issues/61037)
- [Node.js ESM mandatory extensions](https://nodejs.org/api/esm.html#mandatory-file-extensions)
- [ts-jest ESM support](https://kulshekhar.github.io/ts-jest/docs/guides/esm-support)
- [Community fork that works](https://github.com/boxbuild-io/actions-languageservices/commit/077fb2b58dfd2cca3d6e3df1fdf9e26e75db24ae)
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/expressions",
"version": "0.3.27",
"version": "0.3.25",
"license": "MIT",
"type": "module",
"source": "./src/index.ts",
@@ -60,6 +60,6 @@
"prettier": "^2.8.3",
"rimraf": "^3.0.2",
"ts-jest": "^29.0.3",
"typescript": "^5.8.3"
"typescript": "^4.7.4"
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import {ExpressionData} from "./data/index.js";
import {Token} from "./lexer.js";
import {ExpressionData} from "./data";
import {Token} from "./lexer";
export interface ExprVisitor<R> {
visitLiteral(literal: Literal): R;
+8 -8
View File
@@ -1,11 +1,11 @@
import {complete, CompletionItem, trimTokenVector} from "./completion.js";
import {DescriptionDictionary} from "./completion/descriptionDictionary.js";
import {BooleanData} from "./data/boolean.js";
import {Dictionary} from "./data/dictionary.js";
import {StringData} from "./data/string.js";
import {wellKnownFunctions} from "./funcs.js";
import {FunctionDefinition, FunctionInfo} from "./funcs/info.js";
import {Lexer, TokenType} from "./lexer.js";
import {complete, CompletionItem, trimTokenVector} from "./completion";
import {DescriptionDictionary} from "./completion/descriptionDictionary";
import {BooleanData} from "./data/boolean";
import {Dictionary} from "./data/dictionary";
import {StringData} from "./data/string";
import {wellKnownFunctions} from "./funcs";
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
import {Lexer, TokenType} from "./lexer";
const testContext = new Dictionary(
{
+8 -8
View File
@@ -1,11 +1,11 @@
import {DescriptionPair} from "./completion/descriptionDictionary.js";
import {Dictionary, isDictionary} from "./data/dictionary.js";
import {ExpressionData} from "./data/expressiondata.js";
import {Evaluator} from "./evaluator.js";
import {wellKnownFunctions} from "./funcs.js";
import {FunctionDefinition, FunctionInfo} from "./funcs/info.js";
import {Lexer, Token, TokenType} from "./lexer.js";
import {Parser} from "./parser.js";
import {DescriptionPair} from "./completion/descriptionDictionary";
import {Dictionary, isDictionary} from "./data/dictionary";
import {ExpressionData} from "./data/expressiondata";
import {Evaluator} from "./evaluator";
import {wellKnownFunctions} from "./funcs";
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
import {Lexer, Token, TokenType} from "./lexer";
import {Parser} from "./parser";
export type CompletionItem = {
label: string;
@@ -1,5 +1,5 @@
import {StringData} from "../data/index.js";
import {DescriptionDictionary} from "./descriptionDictionary.js";
import {StringData} from "../data";
import {DescriptionDictionary} from "./descriptionDictionary";
describe("description dictionary", () => {
it("pairs contains all values", () => {
@@ -1,5 +1,5 @@
import {Dictionary} from "../data/dictionary.js";
import {ExpressionData, Kind, Pair} from "../data/expressiondata.js";
import {Dictionary} from "../data/dictionary";
import {ExpressionData, Kind, Pair} from "../data/expressiondata";
export type DescriptionPair = Pair & {description?: string};
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionData, ExpressionDataInterface, Kind, kindStr} from "./expressiondata.js";
import {ExpressionData, ExpressionDataInterface, Kind, kindStr} from "./expressiondata";
export class Array implements ExpressionDataInterface {
private v: ExpressionData[] = [];
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
import {ExpressionDataInterface, Kind} from "./expressiondata";
export class BooleanData implements ExpressionDataInterface {
constructor(public readonly value: boolean) {}
+2 -2
View File
@@ -1,5 +1,5 @@
import {Dictionary} from "./dictionary.js";
import {StringData} from "./string.js";
import {Dictionary} from "./dictionary";
import {StringData} from "./string";
describe("dictionary", () => {
it("pairs contains all values", () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionData, ExpressionDataInterface, Kind, kindStr, Pair} from "./expressiondata.js";
import {ExpressionData, ExpressionDataInterface, Kind, kindStr, Pair} from "./expressiondata";
export class Dictionary implements ExpressionDataInterface {
private keys: string[] = [];
+6 -6
View File
@@ -1,9 +1,9 @@
import {Dictionary} from "./dictionary.js";
import {Null} from "./null.js";
import {Array} from "./array.js";
import {StringData} from "./string.js";
import {NumberData} from "./number.js";
import {BooleanData} from "./boolean.js";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {Array} from "./array";
import {StringData} from "./string";
import {NumberData} from "./number";
import {BooleanData} from "./boolean";
export enum Kind {
String = 0,
+9 -9
View File
@@ -1,9 +1,9 @@
export {Array} from "./array.js";
export {BooleanData} from "./boolean.js";
export {Dictionary} from "./dictionary.js";
export {ExpressionData, Kind} from "./expressiondata.js";
export {Null} from "./null.js";
export {NumberData} from "./number.js";
export {replacer} from "./replacer.js";
export {reviver} from "./reviver.js";
export {StringData} from "./string.js";
export {Array} from "./array";
export {BooleanData} from "./boolean";
export {Dictionary} from "./dictionary";
export {ExpressionData, Kind} from "./expressiondata";
export {Null} from "./null";
export {NumberData} from "./number";
export {replacer} from "./replacer";
export {reviver} from "./reviver";
export {StringData} from "./string";
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
import {ExpressionDataInterface, Kind} from "./expressiondata";
export class Null implements ExpressionDataInterface {
public readonly kind = Kind.Null;
+1 -1
View File
@@ -1,4 +1,4 @@
import {NumberData} from "./number.js";
import {NumberData} from "./number";
describe("number", () => {
it("coerces to string", () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
import {ExpressionDataInterface, Kind} from "./expressiondata";
export class NumberData implements ExpressionDataInterface {
constructor(public readonly value: number) {}
+6 -6
View File
@@ -1,9 +1,9 @@
import {Array} from "./array.js";
import {Dictionary} from "./dictionary.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {replacer} from "./replacer.js";
import {StringData} from "./string.js";
import {Array} from "./array";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {NumberData} from "./number";
import {replacer} from "./replacer";
import {StringData} from "./string";
describe("replacer", () => {
it("null", () => {
+6 -6
View File
@@ -1,9 +1,9 @@
import {Array} from "./array.js";
import {BooleanData} from "./boolean.js";
import {Dictionary} from "./dictionary.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {StringData} from "./string.js";
import {Array} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {Null} from "./null";
import {NumberData} from "./number";
import {StringData} from "./string";
/**
* Replacer can be passed to JSON.stringify to convert an ExpressionData object into plain JSON
+8 -8
View File
@@ -1,11 +1,11 @@
import {Array} from "./array.js";
import {BooleanData} from "./boolean.js";
import {Dictionary} from "./dictionary.js";
import {ExpressionData} from "./expressiondata.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {reviver} from "./reviver.js";
import {StringData} from "./string.js";
import {Array} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {ExpressionData} from "./expressiondata";
import {Null} from "./null";
import {NumberData} from "./number";
import {reviver} from "./reviver";
import {StringData} from "./string";
describe("reviver", () => {
const tests: {
+7 -7
View File
@@ -1,10 +1,10 @@
import {Array as dArray} from "./array.js";
import {BooleanData} from "./boolean.js";
import {Dictionary} from "./dictionary.js";
import {ExpressionData} from "./expressiondata.js";
import {Null} from "./null.js";
import {NumberData} from "./number.js";
import {StringData} from "./string.js";
import {Array as dArray} from "./array";
import {BooleanData} from "./boolean";
import {Dictionary} from "./dictionary";
import {ExpressionData} from "./expressiondata";
import {Null} from "./null";
import {NumberData} from "./number";
import {StringData} from "./string";
/**
* Reviver can be passed to `JSON.parse` to convert plain JSON into an `ExpressionData` object.
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionDataInterface, Kind} from "./expressiondata.js";
import {ExpressionDataInterface, Kind} from "./expressiondata";
export class StringData implements ExpressionDataInterface {
constructor(public readonly value: string) {}
+1 -1
View File
@@ -1,4 +1,4 @@
import {Pos, Token, tokenString} from "./lexer.js";
import {Pos, Token, tokenString} from "./lexer";
export const MAX_PARSER_DEPTH = 50;
export const MAX_EXPRESSION_LENGTH = 21000;
+5 -5
View File
@@ -1,8 +1,8 @@
import * as data from "./data/index.js";
import {ExpressionEvaluationError} from "./errors.js";
import {Evaluator} from "./evaluator.js";
import {Lexer} from "./lexer.js";
import {Parser} from "./parser.js";
import * as data from "./data";
import {ExpressionEvaluationError} from "./errors";
import {Evaluator} from "./evaluator";
import {Lexer} from "./lexer";
import {Parser} from "./parser";
describe("evaluator", () => {
const lexAndParse = (input: string) => {
+8 -8
View File
@@ -10,14 +10,14 @@ import {
Logical,
Star,
Unary
} from "./ast.js";
import * as data from "./data/index.js";
import {FilteredArray} from "./filtered_array.js";
import {wellKnownFunctions} from "./funcs.js";
import {FunctionDefinition} from "./funcs/info.js";
import {idxHelper} from "./idxHelper.js";
import {TokenType} from "./lexer.js";
import {equals, falsy, greaterThan, lessThan, truthy} from "./result.js";
} from "./ast";
import * as data from "./data";
import {FilteredArray} from "./filtered_array";
import {wellKnownFunctions} from "./funcs";
import {FunctionDefinition} from "./funcs/info";
import {idxHelper} from "./idxHelper";
import {TokenType} from "./lexer";
import {equals, falsy, greaterThan, lessThan, truthy} from "./result";
export class Evaluator implements ExprVisitor<data.ExpressionData> {
/**
+1 -1
View File
@@ -1,3 +1,3 @@
import * as data from "./data/index.js";
import * as data from "./data";
export class FilteredArray extends data.Array {}
+10 -10
View File
@@ -1,13 +1,13 @@
import {ErrorType, ExpressionError} from "./errors.js";
import {contains} from "./funcs/contains.js";
import {endswith} from "./funcs/endswith.js";
import {format} from "./funcs/format.js";
import {fromjson} from "./funcs/fromjson.js";
import {FunctionDefinition, FunctionInfo} from "./funcs/info.js";
import {join} from "./funcs/join.js";
import {startswith} from "./funcs/startswith.js";
import {tojson} from "./funcs/tojson.js";
import {Token} from "./lexer.js";
import {ErrorType, ExpressionError} from "./errors";
import {contains} from "./funcs/contains";
import {endswith} from "./funcs/endswith";
import {format} from "./funcs/format";
import {fromjson} from "./funcs/fromjson";
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
import {join} from "./funcs/join";
import {startswith} from "./funcs/startswith";
import {tojson} from "./funcs/tojson";
import {Token} from "./lexer";
export type ParseContext = {
allowUnknownKeywords: boolean;
+3 -3
View File
@@ -1,6 +1,6 @@
import {BooleanData, ExpressionData, Kind} from "../data/index.js";
import {equals} from "../result.js";
import {FunctionDefinition} from "./info.js";
import {BooleanData, ExpressionData, Kind} from "../data";
import {equals} from "../result";
import {FunctionDefinition} from "./info";
export const contains: FunctionDefinition = {
name: "contains",
+3 -3
View File
@@ -1,6 +1,6 @@
import {BooleanData, ExpressionData} from "../data/index.js";
import {toUpperSpecial} from "../result.js";
import {FunctionDefinition} from "./info.js";
import {BooleanData, ExpressionData} from "../data";
import {toUpperSpecial} from "../result";
import {FunctionDefinition} from "./info";
export const endswith: FunctionDefinition = {
name: "endsWith",
+2 -2
View File
@@ -1,5 +1,5 @@
import {Null, NumberData, StringData} from "../data/index.js";
import {format} from "./format.js";
import {Null, NumberData, StringData} from "../data";
import {format} from "./format";
describe("format", () => {
it("null", () => {
+2 -2
View File
@@ -1,5 +1,5 @@
import {ExpressionData, StringData} from "../data/index.js";
import {FunctionDefinition} from "./info.js";
import {ExpressionData, StringData} from "../data";
import {FunctionDefinition} from "./info";
export const format: FunctionDefinition = {
name: "format",
+4 -4
View File
@@ -1,7 +1,7 @@
import {ExpressionData} from "../data/index.js";
import {reviver} from "../data/reviver.js";
import {ExpressionEvaluationError} from "../errors.js";
import {FunctionDefinition} from "./info.js";
import {ExpressionData} from "../data";
import {reviver} from "../data/reviver";
import {ExpressionEvaluationError} from "../errors";
import {FunctionDefinition} from "./info";
export const fromjson: FunctionDefinition = {
name: "fromJson",
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionData} from "../data/index.js";
import {ExpressionData} from "../data";
export interface FunctionInfo {
name: string;
+2 -2
View File
@@ -1,5 +1,5 @@
import {ExpressionData, Kind, StringData} from "../data/index.js";
import {FunctionDefinition} from "./info.js";
import {ExpressionData, Kind, StringData} from "../data";
import {FunctionDefinition} from "./info";
export const join: FunctionDefinition = {
name: "join",
+3 -3
View File
@@ -1,6 +1,6 @@
import {BooleanData, ExpressionData} from "../data/index.js";
import {toUpperSpecial} from "../result.js";
import {FunctionDefinition} from "./info.js";
import {BooleanData, ExpressionData} from "../data";
import {toUpperSpecial} from "../result";
import {FunctionDefinition} from "./info";
export const startswith: FunctionDefinition = {
name: "startsWith",
+3 -3
View File
@@ -1,6 +1,6 @@
import {ExpressionData, StringData} from "../data/index.js";
import {replacer} from "../data/replacer.js";
import {FunctionDefinition} from "./info.js";
import {ExpressionData, StringData} from "../data";
import {replacer} from "../data/replacer";
import {FunctionDefinition} from "./info";
export const tojson: FunctionDefinition = {
name: "toJson",
+1 -1
View File
@@ -1,4 +1,4 @@
import {ExpressionData} from "./data/index.js";
import {ExpressionData} from "./data";
export class idxHelper {
public readonly str: string | undefined;
+9 -9
View File
@@ -1,9 +1,9 @@
export {Expr} from "./ast.js";
export {complete, CompletionItem} from "./completion.js";
export {DescriptionDictionary, DescriptionPair, isDescriptionDictionary} from "./completion/descriptionDictionary.js";
export * as data from "./data/index.js";
export {ExpressionError, ExpressionEvaluationError} from "./errors.js";
export {Evaluator} from "./evaluator.js";
export {wellKnownFunctions} from "./funcs.js";
export {Lexer, Result} from "./lexer.js";
export {Parser} from "./parser.js";
export {Expr} from "./ast";
export {complete, CompletionItem} from "./completion";
export {DescriptionDictionary, DescriptionPair, isDescriptionDictionary} from "./completion/descriptionDictionary";
export * as data from "./data";
export {ExpressionError, ExpressionEvaluationError} from "./errors";
export {Evaluator} from "./evaluator";
export {wellKnownFunctions} from "./funcs";
export {Lexer, Result} from "./lexer";
export {Parser} from "./parser";
+1 -1
View File
@@ -1,4 +1,4 @@
import {Lexer, Token, TokenType} from "./lexer.js";
import {Lexer, Token, TokenType} from "./lexer";
describe("lexer", () => {
const tests: {
+2 -2
View File
@@ -1,5 +1,5 @@
import {StringData} from "./data/index.js";
import {MAX_EXPRESSION_LENGTH} from "./errors.js";
import {StringData} from "./data";
import {MAX_EXPRESSION_LENGTH} from "./errors";
export enum TokenType {
UNKNOWN,
+6 -17
View File
@@ -1,20 +1,9 @@
import {
Binary,
ContextAccess,
Expr,
FunctionCall,
Grouping,
IndexAccess,
Literal,
Logical,
Star,
Unary
} from "./ast.js";
import * as data from "./data/index.js";
import {ErrorType, ExpressionError, MAX_PARSER_DEPTH} from "./errors.js";
import {ParseContext, validateFunction} from "./funcs.js";
import {FunctionInfo} from "./funcs/info.js";
import {Token, TokenType} from "./lexer.js";
import {Binary, ContextAccess, Expr, FunctionCall, Grouping, IndexAccess, Literal, Logical, Star, Unary} from "./ast";
import * as data from "./data";
import {ErrorType, ExpressionError, MAX_PARSER_DEPTH} from "./errors";
import {ParseContext, validateFunction} from "./funcs";
import {FunctionInfo} from "./funcs/info";
import {Token, TokenType} from "./lexer";
export class Parser {
private extContexts: Map<string, boolean>;
+2 -2
View File
@@ -1,5 +1,5 @@
import {BooleanData, ExpressionData, NumberData, StringData} from "./data/index.js";
import {coerceTypes, toUpperSpecial} from "./result.js";
import {BooleanData, ExpressionData, NumberData, StringData} from "./data";
import {coerceTypes, toUpperSpecial} from "./result";
describe("coerceTypes", () => {
const tests: {
+1 -1
View File
@@ -1,4 +1,4 @@
import * as data from "./data/index.js";
import * as data from "./data";
export function falsy(d: data.ExpressionData): boolean {
switch (d.kind) {
+9 -9
View File
@@ -1,14 +1,14 @@
import * as fs from "fs";
import * as path from "path";
import {Expr} from "./ast.js";
import * as data from "./data/index.js";
import {kindStr} from "./data/expressiondata.js";
import {replacer} from "./data/replacer.js";
import {reviver} from "./data/reviver.js";
import {ExpressionError} from "./errors.js";
import {Evaluator} from "./evaluator.js";
import {Lexer, Result} from "./lexer.js";
import {Parser} from "./parser.js";
import {Expr} from "./ast";
import * as data from "./data";
import {kindStr} from "./data/expressiondata";
import {replacer} from "./data/replacer";
import {reviver} from "./data/reviver";
import {ExpressionError} from "./errors";
import {Evaluator} from "./evaluator";
import {Lexer, Result} from "./lexer";
import {Parser} from "./parser";
interface TestResult {
value: data.ExpressionData;
+1 -4
View File
@@ -2,12 +2,9 @@
"exclude": ["./src/**/*.test.ts"],
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "node16",
"moduleResolution": "node16",
"declaration": true,
"declarationMap": true,
"noEmit": false,
"outDir": "./dist",
"skipLibCheck": true
"outDir": "./dist"
}
}
-173
View File
@@ -10,14 +10,6 @@ The [package](https://www.npmjs.com/package/@actions/languageserver) contains Ty
npm install @actions/languageserver
```
To install the language server as a standalone CLI:
```bash
npm install -g @actions/languageserver
```
This makes the `actions-languageserver` command available globally.
## Usage
### Basic usage using `vscode-languageserver-node`
@@ -100,150 +92,6 @@ const clientOptions: LanguageClientOptions = {
const client = new LanguageClient("actions-language", "GitHub Actions Language Server", serverOptions, clientOptions);
```
### Standalone CLI
After installing globally, you can run the language server directly:
```bash
actions-languageserver --stdio
```
This starts the language server using stdio transport, which is the standard way for editors to communicate with language servers.
### In Neovim
#### 1. Install the language server
```bash
npm install -g @actions/languageserver
```
#### 2. Set up filetype detection
Add this to your `init.lua` to detect GitHub Actions workflow files:
```lua
vim.filetype.add({
pattern = {
[".*/%.github/workflows/.*%.ya?ml"] = "yaml.ghactions",
},
})
```
This sets the filetype to `yaml.ghactions` for YAML files in `.github/workflows/`, allowing you to keep separate YAML LSP configurations if needed.
#### 3. Create the LSP configuration
As of Neovim 0.11+ you can add this configuration in `~/.config/nvim/lsp/actionsls.lua`:
```lua
local function get_github_token()
local handle = io.popen("gh auth token 2>/dev/null")
if not handle then return nil end
local token = handle: read("*a"):gsub("%s+", "")
handle:close()
return token ~= "" and token or nil
end
local function parse_github_remote(url)
if not url or url == "" then return nil end
-- SSH format: git@github.com:owner/repo.git
local owner, repo = url:match("git@github%.com:([^/]+)/([^/%.]+)")
if owner and repo then
return owner, repo: gsub("%.git$", "")
end
-- HTTPS format: https://github.com/owner/repo.git
owner, repo = url:match("github%.com/([^/]+)/([^/%.]+)")
if owner and repo then
return owner, repo:gsub("%.git$", "")
end
return nil
end
local function get_repo_info(owner, repo)
local cmd = string.format(
"gh repo view %s/%s --json id,owner --template '{{.id}}\t{{.owner.type}}' 2>/dev/null",
owner,
repo
)
local handle = io.popen(cmd)
if not handle then return nil end
local result = handle: read("*a"):gsub("%s+$", "")
handle:close()
local id, owner_type = result:match("^(%d+)\t(.+)$")
if id then
return {
id = tonumber(id),
organizationOwned = owner_type == "Organization",
}
end
return nil
end
local function get_repos_config()
local handle = io.popen("git rev-parse --show-toplevel 2>/dev/null")
if not handle then return nil end
local git_root = handle: read("*a"):gsub("%s+", "")
handle:close()
if git_root == "" then return nil end
handle = io.popen("git remote get-url origin 2>/dev/null")
if not handle then return nil end
local remote_url = handle:read("*a"):gsub("%s+", "")
handle:close()
local owner, name = parse_github_remote(remote_url)
if not owner or not name then return nil end
local info = get_repo_info(owner, name)
return {
{
id = info and info.id or 0,
owner = owner,
name = name,
organizationOwned = info and info.organizationOwned or false,
workspaceUri = "file://" .. git_root,
},
}
end
return {
cmd = { "actions-languageserver", "--stdio" },
filetypes = { "yaml.ghactions" },
root_markers = { ".git" },
init_options = {
-- Optional: provide a GitHub token and repo context for added functionality
-- (e.g., repository-specific completions)
sessionToken = get_github_token(),
repos = get_repos_config(),
},
}
```
#### 4. Enable the LSP
Add to your `init.lua`:
```lua
vim.lsp.enable('actionsls')
```
#### 5. Verify it's working
Open any `.github/workflows/*.yml` file and run:
```vim
:checkhealth vim.lsp
```
You should see `actionsls` in the list of attached clients.
## Contributing
See [CONTRIBUTING.md](../CONTRIBUTING.md) at the root of the repository for general guidelines and recommendations.
@@ -262,27 +110,6 @@ or to watch for changes
npm run watch
```
### Running the language server locally
After running
```bash
npm run build:cli
npm link
```
`actions-languageserver` will be available globally. You can start it with:
```bash
actions-languageserver --stdio
```
Once linked you can also watch for changes and rebuild automatically:
```bash
npm run watch:cli
```
### Test
```bash
View File
+6 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageserver",
"version": "0.3.27",
"version": "0.4.0",
"description": "Language server for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -31,7 +31,7 @@
"url": "https://github.com/actions/languageservices"
},
"scripts": {
"build": "tsc --build tsconfig.build.json && npm run build:cli",
"build": "tsc --build tsconfig.build.json",
"build:cli": "esbuild src/index.ts --bundle --platform=node --format=cjs --outfile=dist/cli.bundle.cjs",
"clean": "rimraf dist",
"format": "prettier --write '**/*.ts'",
@@ -41,15 +41,14 @@
"prepublishOnly": "npm run build && npm run test",
"test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest",
"test-watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch",
"watch": "tsc --build tsconfig.build.json --watch",
"watch:cli": "esbuild src/index.ts --bundle --platform=node --format=cjs --outfile=dist/cli.bundle.cjs --watch"
"watch": "tsc --build tsconfig.build.json --watch"
},
"bin": {
"actions-languageserver": "./bin/actions-languageserver"
},
"dependencies": {
"@actions/languageservice": "^0.3.27",
"@actions/workflow-parser": "^0.3.27",
"@actions/languageservice": "^0.3.25",
"@actions/workflow-parser": "^0.3.25",
"@octokit/rest": "^21.1.1",
"@octokit/types": "^9.0.0",
"vscode-languageserver": "^8.0.2",
@@ -60,8 +59,7 @@
"node": ">= 18"
},
"files": [
"dist/**/*",
"bin/**/*"
"dist/**/*"
],
"devDependencies": {
"@types/jest": "^29.0.3",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/languageservice",
"version": "0.3.27",
"version": "0.3.25",
"description": "Language service for GitHub Actions",
"license": "MIT",
"type": "module",
@@ -47,8 +47,8 @@
"watch": "tsc --build tsconfig.build.json --watch"
},
"dependencies": {
"@actions/expressions": "^0.3.27",
"@actions/workflow-parser": "^0.3.27",
"@actions/expressions": "^0.3.25",
"@actions/workflow-parser": "^0.3.25",
"vscode-languageserver-textdocument": "^1.0.7",
"vscode-languageserver-types": "^3.17.2",
"vscode-uri": "^3.0.8",
+1 -1
View File
@@ -1,4 +1,4 @@
import {actionIdentifier, parseActionReference as parse} from "./action.js";
import {actionIdentifier, parseActionReference as parse} from "./action";
describe("parseActionReference", () => {
it("basic action", () => {
@@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {data, DescriptionDictionary} from "@actions/expressions";
import {CompletionItem, CompletionItemKind} from "vscode-languageserver-types";
import {complete, getExpressionInput} from "./complete.js";
import {ContextProviderConfig} from "./context-providers/config.js";
import {registerLogger} from "./log.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {TestLogger} from "./test-utils/logger.js";
import {testFileProvider} from "./test-utils/test-file-provider.js";
import {clearCache} from "./utils/workflow-cache.js";
import {complete, getExpressionInput} from "./complete";
import {ContextProviderConfig} from "./context-providers/config";
import {registerLogger} from "./log";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {TestLogger} from "./test-utils/logger";
import {testFileProvider} from "./test-utils/test-file-provider";
import {clearCache} from "./utils/workflow-cache";
const contextProviderConfig: ContextProviderConfig = {
getContext: (context: string) => {
@@ -1110,7 +1110,7 @@ jobs:
`;
const result = await complete(...getPositionFromCursor(input), {contextProviderConfig});
expect(result.map(x => x.label)).toEqual(["check_run_id", "container", "services", "status"]);
expect(result.map(x => x.label)).toEqual(["container", "services", "status"]);
});
it("job context is suggested within a job output", async () => {
@@ -1,7 +1,7 @@
import {complete} from "./complete.js";
import {complete} from "./complete";
import {TextDocument} from "vscode-languageserver-textdocument";
import {clearCache} from "./utils/workflow-cache.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {clearCache} from "./utils/workflow-cache";
import {getPositionFromCursor} from "./test-utils/cursor-position";
beforeEach(() => {
clearCache();
@@ -1,8 +1,8 @@
import {CompletionItem, MarkupContent} from "vscode-languageserver-types";
import {complete} from "./complete.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {testFileProvider} from "./test-utils/test-file-provider.js";
import {clearCache} from "./utils/workflow-cache.js";
import {complete} from "./complete";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {testFileProvider} from "./test-utils/test-file-provider";
import {clearCache} from "./utils/workflow-cache";
function mapResult(result: CompletionItem[]) {
return result.map(x => {
+15 -108
View File
@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {MarkupContent, TextEdit} from "vscode-languageserver-types";
import {complete} from "./complete.js";
import {registerLogger} from "./log.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {TestLogger} from "./test-utils/logger.js";
import {clearCache} from "./utils/workflow-cache.js";
import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config.js";
import {complete} from "./complete";
import {registerLogger} from "./log";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config";
registerLogger(new TestLogger());
@@ -44,7 +44,7 @@ jobs:
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result.length).toEqual(13);
expect(result.length).toEqual(9);
expect(result[0].label).toEqual("concurrency");
});
@@ -70,7 +70,7 @@ jobs:
|`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result.length).toEqual(30);
expect(result.length).toEqual(21);
});
it("string definition completion in sequence", async () => {
@@ -243,7 +243,7 @@ jobs:
runs-|`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result).toHaveLength(30);
expect(result).toHaveLength(21);
});
it("job key with comment afterwards", async () => {
@@ -254,7 +254,7 @@ jobs:
#`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result).toHaveLength(30);
expect(result).toHaveLength(21);
});
it("job key with other values afterwards", async () => {
@@ -266,7 +266,7 @@ jobs:
concurrency: 'group-name'`;
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result).toHaveLength(29);
expect(result).toHaveLength(20);
});
it("step key without space after colon", async () => {
@@ -335,7 +335,7 @@ jobs:
- uses: actions/checkout@v2
`;
const result = await complete(...getPositionFromCursor(input));
expect(result).toHaveLength(25);
expect(result).toHaveLength(17);
});
it("complete from behind a colon will replace it", async () => {
@@ -348,7 +348,7 @@ jobs:
- uses: actions/checkout@v2
`;
const result = await complete(...getPositionFromCursor(input));
expect(result).toHaveLength(25);
expect(result).toHaveLength(17);
const textEdit = result[0].textEdit as TextEdit;
expect(textEdit.range).toEqual({
start: {line: 5, character: 4},
@@ -510,104 +510,11 @@ jobs:
expect(result.filter(x => x.label === "types").map(x => x.textEdit?.newText)).toEqual(["types: "]);
});
it("adds newline and indentation for one-of in key mode", async () => {
it("does not add : for one-of in key mode", async () => {
const input = "on:\n check_run: ty|";
const result = await complete(...getPositionFromCursor(input));
// When completing a one-of property in key mode (after colon on same line),
// insert newline + indentation + key + colon to create valid YAML structure
expect(result.filter(x => x.label === "types").map(x => x.textEdit?.newText)).toEqual(["\n types: "]);
});
it("handles mixed string and mapping completions for one-of in key mode", async () => {
const input = "on: push\npermissions: |";
const result = await complete(...getPositionFromCursor(input));
// String values (read-all, write-all) should insert directly without newline
expect(result.filter(x => x.label === "read-all").map(x => x.textEdit?.newText)).toEqual(["read-all"]);
expect(result.filter(x => x.label === "write-all").map(x => x.textEdit?.newText)).toEqual(["write-all"]);
// Mapping keys with one-of types should insert with newline and indentation
expect(result.filter(x => x.label === "actions").map(x => x.textEdit?.newText)).toEqual(["\n actions: "]);
expect(result.filter(x => x.label === "contents").map(x => x.textEdit?.newText)).toEqual(["\n contents: "]);
});
it("shows both simple and full syntax for null+mapping one-of", async () => {
// check_run is a one-of: [null, mapping]. Show both:
// - check_run (simple, just the key with colon)
// - check_run (full syntax) (ready to add mapping keys)
const input = "on:\n |";
const result = await complete(...getPositionFromCursor(input));
// Should have both check_run and check_run (full syntax)
expect(result.some(x => x.label === "check_run")).toBe(true);
expect(result.some(x => x.label === "check_run (full syntax)")).toBe(true);
});
it("shows all three variants for scalar+sequence+mapping one-of", async () => {
// runs-on is a one-of: [string, sequence, mapping]
const input = `on: push
jobs:
build:
|`;
const result = await complete(...getPositionFromCursor(input));
// Should have runs-on, runs-on (list), and runs-on (full syntax)
expect(result.some(x => x.label === "runs-on")).toBe(true);
expect(result.some(x => x.label === "runs-on (list)")).toBe(true);
expect(result.some(x => x.label === "runs-on (full syntax)")).toBe(true);
});
it("generates correct insertText for one-of variants in parent mode", async () => {
// runs-on is a one-of: [string, sequence, mapping]
const input = `on: push
jobs:
build:
|`;
const result = await complete(...getPositionFromCursor(input));
// Scalar: just key with colon and space
expect(result.find(x => x.label === "runs-on")?.textEdit?.newText).toEqual("runs-on: ");
// Sequence: key with colon, newline, and list item
expect(result.find(x => x.label === "runs-on (list)")?.textEdit?.newText).toEqual("runs-on:\n - ");
// Mapping: key with colon, newline, and indentation for nested keys
expect(result.find(x => x.label === "runs-on (full syntax)")?.textEdit?.newText).toEqual("runs-on:\n ");
});
it("generates correct insertText for one-of variants in key mode", async () => {
// concurrency is a one-of: [string, mapping] - testing key mode (after colon on same line)
const input = "concurrency: |";
const result = await complete(...getPositionFromCursor(input));
// Scalar in key mode: newline + indented key + colon + space
expect(result.find(x => x.label === "group")?.textEdit?.newText).toEqual("\n group: ");
// Boolean in key mode (cancel-in-progress): newline + indented key + colon + space
expect(result.find(x => x.label === "cancel-in-progress")?.textEdit?.newText).toEqual("\n cancel-in-progress: ");
});
it("uses base key as filterText for qualified one-of variants", async () => {
// runs-on has multiple structural types, so variants get qualifiers
const input = `on: push
jobs:
build:
|`;
const result = await complete(...getPositionFromCursor(input));
// Scalar: no qualifier, so no filterText needed
expect(result.find(x => x.label === "runs-on")?.filterText).toBeUndefined();
// Sequence and mapping: qualified labels should filter on base key
expect(result.find(x => x.label === "runs-on (list)")?.filterText).toEqual("runs-on");
expect(result.find(x => x.label === "runs-on (full syntax)")?.filterText).toEqual("runs-on");
expect(result.filter(x => x.label === "types").map(x => x.textEdit?.newText)).toEqual(["types"]);
});
});
+15 -17
View File
@@ -11,20 +11,20 @@ import {File} from "@actions/workflow-parser/workflows/file";
import {FileProvider} from "@actions/workflow-parser/workflows/file-provider";
import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {CompletionItem, CompletionItemKind, CompletionItemTag, Range, TextEdit} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config.js";
import {getContext, Mode} from "./context-providers/default.js";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context.js";
import {validatorFunctions} from "./expression-validation/functions.js";
import {error} from "./log.js";
import {isPotentiallyExpression} from "./utils/expression-detection.js";
import {findToken} from "./utils/find-token.js";
import {guessIndentation} from "./utils/indentation-guesser.js";
import {mapRange} from "./utils/range.js";
import {isPlaceholder, transform} from "./utils/transform.js";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache.js";
import {Value, ValueProviderConfig} from "./value-providers/config.js";
import {defaultValueProviders} from "./value-providers/default.js";
import {DefinitionValueMode, definitionValues} from "./value-providers/definition.js";
import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {validatorFunctions} from "./expression-validation/functions";
import {error} from "./log";
import {isPotentiallyExpression} from "./utils/expression-detection";
import {findToken} from "./utils/find-token";
import {guessIndentation} from "./utils/indentation-guesser";
import {mapRange} from "./utils/range";
import {isPlaceholder, transform} from "./utils/transform";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache";
import {Value, ValueProviderConfig} from "./value-providers/config";
import {defaultValueProviders} from "./value-providers/default";
import {DefinitionValueMode, definitionValues} from "./value-providers/definition";
export function getExpressionInput(input: string, pos: number): string {
// Find start marker around the cursor position
@@ -129,8 +129,6 @@ export async function complete(
const item: CompletionItem = {
label: value.label,
filterText: value.filterText,
sortText: value.sortText,
documentation: value.description && {
kind: "markdown",
value: value.description
@@ -255,7 +253,7 @@ function getExpressionCompletionItems(
function filterAndSortCompletionOptions(options: Value[], existingValues?: Set<string>) {
options = options.filter(x => !existingValues?.has(x.label));
options.sort((a, b) => (a.sortText ?? a.label).localeCompare(b.sortText ?? b.label));
options.sort((a, b) => a.label.localeCompare(b.label));
return options;
}
@@ -1,6 +1,6 @@
import {DescriptionDictionary} from "@actions/expressions";
import {WorkflowContext} from "../context/workflow-context.js";
import {Mode} from "./default.js";
import {WorkflowContext} from "../context/workflow-context";
import {Mode} from "./default";
export type ContextProviderConfig = {
getContext: (
@@ -1,6 +1,6 @@
import {DescriptionDictionary} from "@actions/expressions";
import {WorkflowContext} from "../context/workflow-context.js";
import {getContext, Mode} from "./default.js";
import {WorkflowContext} from "../context/workflow-context";
import {getContext, Mode} from "./default";
describe("getContext", () => {
const emptyWorkflowContext: WorkflowContext = {
@@ -1,18 +1,18 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {Kind} from "@actions/expressions/data/expressiondata";
import {WorkflowContext} from "../context/workflow-context.js";
import {ContextProviderConfig} from "./config.js";
import {getDescription, RootContext} from "./descriptions.js";
import {getEnvContext} from "./env.js";
import {getGithubContext} from "./github.js";
import {getInputsContext} from "./inputs.js";
import {getJobContext} from "./job.js";
import {getJobsContext} from "./jobs.js";
import {getMatrixContext} from "./matrix.js";
import {getNeedsContext} from "./needs.js";
import {getSecretsContext} from "./secrets.js";
import {getStepsContext} from "./steps.js";
import {getStrategyContext} from "./strategy.js";
import {WorkflowContext} from "../context/workflow-context";
import {ContextProviderConfig} from "./config";
import {getDescription, RootContext} from "./descriptions";
import {getEnvContext} from "./env";
import {getGithubContext} from "./github";
import {getInputsContext} from "./inputs";
import {getJobContext} from "./job";
import {getJobsContext} from "./jobs";
import {getMatrixContext} from "./matrix";
import {getNeedsContext} from "./needs";
import {getSecretsContext} from "./secrets";
import {getStepsContext} from "./steps";
import {getStrategyContext} from "./strategy";
// ContextValue is the type of the value returned by a context provider
// Null indicates that the context provider doesn't have any value to provide
+1 -1
View File
@@ -1,7 +1,7 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {isScalar, isString} from "@actions/workflow-parser";
import {MappingToken} from "@actions/workflow-parser/templates/tokens/mapping-token";
import {WorkflowContext} from "../context/workflow-context.js";
import {WorkflowContext} from "../context/workflow-context";
export function getEnvContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
@@ -1,5 +1,5 @@
import {DescriptionDictionary} from "@actions/expressions";
import {getEventPayload, getSupportedEventTypes} from "./eventPayloads.js";
import {getEventPayload, getSupportedEventTypes} from "./eventPayloads";
describe("eventPayloads", () => {
describe("getSupportedEventTypes", () => {
@@ -1,7 +1,7 @@
import {DescriptionDictionary, isDescriptionDictionary} from "@actions/expressions";
import {testGetWorkflowContext} from "../test-utils/test-workflow-context.js";
import {Mode} from "./default.js";
import {getGithubContext} from "./github.js";
import {DescriptionDictionary, isDescriptionDictionary} from "@actions/expressions/.";
import {testGetWorkflowContext} from "../test-utils/test-workflow-context";
import {Mode} from "./default";
import {getGithubContext} from "./github";
describe("github context", () => {
it("single event", async () => {
@@ -1,11 +1,11 @@
import {data, DescriptionDictionary, isDescriptionDictionary} from "@actions/expressions";
import {ExpressionData} from "@actions/expressions/data/expressiondata";
import {TypesFilterConfig} from "@actions/workflow-parser/model/workflow-template";
import {WorkflowContext} from "../context/workflow-context.js";
import {Mode} from "./default.js";
import {getDescription} from "./descriptions.js";
import {getEventPayload, getSupportedEventTypes} from "./events/eventPayloads.js";
import {getInputsContext} from "./inputs.js";
import {WorkflowContext} from "../context/workflow-context";
import {Mode} from "./default";
import {getDescription} from "./descriptions";
import {getEventPayload, getSupportedEventTypes} from "./events/eventPayloads";
import {getInputsContext} from "./inputs";
export function getGithubContext(workflowContext: WorkflowContext, mode: Mode): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
@@ -1,6 +1,6 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {InputConfig} from "@actions/workflow-parser/model/workflow-template";
import {WorkflowContext} from "../context/workflow-context.js";
import {WorkflowContext} from "../context/workflow-context";
export function getInputsContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
+1 -4
View File
@@ -1,7 +1,7 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {isMapping, isSequence} from "@actions/workflow-parser";
import {MappingToken} from "@actions/workflow-parser/templates/tokens/mapping-token";
import {WorkflowContext} from "../context/workflow-context.js";
import {WorkflowContext} from "../context/workflow-context";
export function getJobContext(workflowContext: WorkflowContext): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#job-context
@@ -35,9 +35,6 @@ export function getJobContext(workflowContext: WorkflowContext): DescriptionDict
// Status
jobContext.add("status", new data.Null());
// Check run ID
jobContext.add("check_run_id", new data.Null());
return jobContext;
}
@@ -1,8 +1,8 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {StringData} from "@actions/expressions/data/string";
import {MappingToken} from "@actions/workflow-parser/templates/tokens/mapping-token";
import {WorkflowContext} from "../context/workflow-context.js";
import {getDescription} from "./descriptions.js";
import {WorkflowContext} from "../context/workflow-context";
import {getDescription} from "./descriptions";
export function getJobsContext(workflowContext: WorkflowContext): DescriptionDictionary {
// https://docs.github.com/en/actions/learn-github-actions/contexts#jobs-context
@@ -6,9 +6,9 @@ import {MappingToken} from "@actions/workflow-parser/templates/tokens/mapping-to
import {SequenceToken} from "@actions/workflow-parser/templates/tokens/sequence-token";
import {StringToken} from "@actions/workflow-parser/templates/tokens/string-token";
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {WorkflowContext} from "../context/workflow-context.js";
import {Mode} from "./default.js";
import {getMatrixContext} from "./matrix.js";
import {WorkflowContext} from "../context/workflow-context";
import {Mode} from "./default";
import {getMatrixContext} from "./matrix";
type MatrixMap = {
[key: string]: Array<string> | Array<{[key: string]: string}>;
@@ -3,8 +3,8 @@ import {isBasicExpression, isMapping, isSequence, isString} from "@actions/workf
import {KeyValuePair} from "@actions/workflow-parser/templates/tokens/key-value-pair";
import {MappingToken} from "@actions/workflow-parser/templates/tokens/mapping-token";
import {SequenceToken} from "@actions/workflow-parser/templates/tokens/sequence-token";
import {WorkflowContext} from "../context/workflow-context.js";
import {ContextValue, Mode} from "./default.js";
import {WorkflowContext} from "../context/workflow-context";
import {ContextValue, Mode} from "./default";
export function getMatrixContext(workflowContext: WorkflowContext, mode: Mode): ContextValue {
// https://docs.github.com/en/actions/learn-github-actions/contexts#matrix-context
@@ -1,8 +1,8 @@
import {DescriptionDictionary} from "@actions/expressions";
import {StringData} from "@actions/expressions/data/string";
import {WorkflowContext} from "../context/workflow-context.js";
import {testGetWorkflowContext} from "../test-utils/test-workflow-context.js";
import {getNeedsContext} from "./needs.js";
import {WorkflowContext} from "../context/workflow-context";
import {testGetWorkflowContext} from "../test-utils/test-workflow-context";
import {getNeedsContext} from "./needs";
describe("needs context", () => {
describe("invalid workflow context", () => {
@@ -3,7 +3,7 @@ import {isMapping, isScalar, isString} from "@actions/workflow-parser";
import {isJob} from "@actions/workflow-parser/model/type-guards";
import {WorkflowJob} from "@actions/workflow-parser/model/workflow-template";
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {WorkflowContext} from "../context/workflow-context.js";
import {WorkflowContext} from "../context/workflow-context";
export function getNeedsContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
@@ -1,8 +1,8 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {StringData} from "@actions/expressions/data/string";
import {WorkflowContext} from "../context/workflow-context.js";
import {Mode} from "./default.js";
import {getDescription} from "./descriptions.js";
import {WorkflowContext} from "../context/workflow-context";
import {Mode} from "./default";
import {getDescription} from "./descriptions";
export function getSecretsContext(workflowContext: WorkflowContext, mode: Mode): DescriptionDictionary {
const d = new DescriptionDictionary({
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {DescriptionDictionary, isDescriptionDictionary} from "@actions/expressions";
import {WorkflowContext} from "../context/workflow-context.js";
import {getStepsContext} from "./steps.js";
import {WorkflowContext} from "../context/workflow-context";
import {getStepsContext} from "./steps";
function createWorkflowContext(stepIds: string[], currentStepId?: string): WorkflowContext {
return {
@@ -1,7 +1,7 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {Step} from "@actions/workflow-parser/model/workflow-template";
import {WorkflowContext} from "../context/workflow-context.js";
import {getDescription} from "./descriptions.js";
import {WorkflowContext} from "../context/workflow-context";
import {getDescription} from "./descriptions";
export function getStepsContext(workflowContext: WorkflowContext): DescriptionDictionary {
const d = new DescriptionDictionary();
@@ -5,8 +5,8 @@ import {MappingToken} from "@actions/workflow-parser/templates/tokens/mapping-to
import {NumberToken} from "@actions/workflow-parser/templates/tokens/number-token";
import {StringToken} from "@actions/workflow-parser/templates/tokens/string-token";
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {WorkflowContext} from "../context/workflow-context.js";
import {getStrategyContext} from "./strategy.js";
import {WorkflowContext} from "../context/workflow-context";
import {getStrategyContext} from "./strategy";
function stringToToken(value: string) {
return new StringToken(undefined, undefined, value, undefined);
@@ -1,7 +1,7 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {isMapping, isScalar, isString} from "@actions/workflow-parser";
import {WorkflowContext} from "../context/workflow-context.js";
import {scalarToData} from "../utils/scalar-to-data.js";
import {WorkflowContext} from "../context/workflow-context";
import {scalarToData} from "../utils/scalar-to-data";
// Default strategy values when no strategy block is defined
const DEFAULT_STRATEGY = {
@@ -1,5 +1,5 @@
import {ActionStep, RunStep} from "@actions/workflow-parser/model/workflow-template";
import {testGetWorkflowContext} from "../test-utils/test-workflow-context.js";
import {testGetWorkflowContext} from "../test-utils/test-workflow-context";
describe("getWorkflowContext", () => {
it("context for workflow", async () => {
@@ -1,7 +1,7 @@
import {isMapping, isString} from "@actions/workflow-parser";
import {DESCRIPTION} from "@actions/workflow-parser/templates/template-constants";
import {WorkflowContext} from "../context/workflow-context.js";
import {TokenResult} from "../utils/find-token.js";
import {WorkflowContext} from "../context/workflow-context";
import {TokenResult} from "../utils/find-token";
export function isReusableWorkflowJobInput(tokenResult: TokenResult): boolean {
return (
+3 -3
View File
@@ -1,6 +1,6 @@
import {documentLinks} from "./document-links.js";
import {createDocument} from "./test-utils/document.js";
import {clearCache} from "./utils/workflow-cache.js";
import {documentLinks} from "./document-links";
import {createDocument} from "./test-utils/document";
import {clearCache} from "./utils/workflow-cache";
beforeEach(() => {
clearCache();
+3 -3
View File
@@ -5,9 +5,9 @@ import {parseFileReference} from "@actions/workflow-parser/workflows/file-refere
import {TextDocument} from "vscode-languageserver-textdocument";
import {DocumentLink} from "vscode-languageserver-types";
import * as vscodeURI from "vscode-uri";
import {actionUrl, parseActionReference} from "./action.js";
import {mapRange} from "./utils/range.js";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache.js";
import {actionUrl, parseActionReference} from "./action";
import {mapRange} from "./utils/range";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache";
export async function documentLinks(document: TextDocument, workspace: string | undefined): Promise<DocumentLink[]> {
const file: File = {
+7 -11
View File
@@ -1,9 +1,9 @@
import {complete} from "./complete.js";
import {hover} from "./hover.js";
import {registerLogger} from "./log.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {TestLogger} from "./test-utils/logger.js";
import {clearCache} from "./utils/workflow-cache.js";
import {complete} from "./complete";
import {hover} from "./hover";
import {registerLogger} from "./log";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
registerLogger(new TestLogger());
@@ -21,21 +21,17 @@ describe("end-to-end", () => {
const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined();
expect(result.length).toEqual(13);
expect(result.length).toEqual(9);
const labels = result.map(x => x.label);
expect(labels).toEqual([
"concurrency",
"concurrency (full syntax)",
"defaults",
"description",
"env",
"jobs",
"name",
"on",
"on (list)",
"on (full syntax)",
"permissions",
"permissions (full syntax)",
"run-name"
]);
});
@@ -1,9 +1,9 @@
import {parseWorkflow} from "@actions/workflow-parser";
import {File} from "@actions/workflow-parser/workflows/file";
import {nullTrace} from "../nulltrace.js";
import {getPositionFromCursor} from "../test-utils/cursor-position.js";
import {findToken} from "../utils/find-token.js";
import {ExpressionPos, mapToExpressionPos} from "./expression-pos.js";
import {nullTrace} from "../nulltrace";
import {getPositionFromCursor} from "../test-utils/cursor-position";
import {findToken} from "../utils/find-token";
import {ExpressionPos, mapToExpressionPos} from "./expression-pos";
describe("mapToExpressionPos", () => {
it("simple expression", () => {
@@ -3,8 +3,8 @@ import {ensureStatusFunction} from "@actions/workflow-parser/model/converter/if-
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {isBasicExpression, isString} from "@actions/workflow-parser/templates/tokens/type-guards";
import {Position, Range as LSPRange} from "vscode-languageserver-textdocument";
import {mapRange} from "../utils/range.js";
import {posWithinRange} from "./pos-range.js";
import {mapRange} from "../utils/range";
import {posWithinRange} from "./pos-range";
export type ExpressionPos = {
/** The expression that includes the position */
@@ -2,13 +2,13 @@ import {data, DescriptionDictionary, Lexer, Parser} from "@actions/expressions";
import {convertWorkflowTemplate, parseWorkflow} from "@actions/workflow-parser";
import {ErrorPolicy} from "@actions/workflow-parser/model/convert";
import {File} from "@actions/workflow-parser/workflows/file";
import {ContextProviderConfig} from "../context-providers/config.js";
import {getContext, Mode} from "../context-providers/default.js";
import {getWorkflowContext} from "../context/workflow-context.js";
import {validatorFunctions} from "../expression-validation/functions.js";
import {nullTrace} from "../nulltrace.js";
import {getPositionFromCursor} from "../test-utils/cursor-position.js";
import {HoverVisitor} from "./visitor.js";
import {ContextProviderConfig} from "../context-providers/config";
import {getContext, Mode} from "../context-providers/default";
import {getWorkflowContext} from "../context/workflow-context";
import {validatorFunctions} from "../expression-validation/functions";
import {nullTrace} from "../nulltrace";
import {getPositionFromCursor} from "../test-utils/cursor-position";
import {HoverVisitor} from "./visitor";
const contextProviderConfig: ContextProviderConfig = {
getContext: (context: string) => {
@@ -13,7 +13,7 @@ import {
} from "@actions/expressions/ast";
import {FunctionDefinition} from "@actions/expressions/funcs/info";
import {Pos, Range} from "@actions/expressions/lexer";
import {posWithinRange} from "./pos-range.js";
import {posWithinRange} from "./pos-range";
export type HoverResult =
| undefined
@@ -3,7 +3,7 @@ import {Expr, Logical} from "@actions/expressions/ast";
import {ExpressionData} from "@actions/expressions/data/expressiondata";
import {TokenType} from "@actions/expressions/lexer";
import {falsy, truthy} from "@actions/expressions/result";
import {AccessError} from "./error-dictionary.js";
import {AccessError} from "./error-dictionary";
export type ValidationError = {
message: string;
@@ -1,12 +1,12 @@
import {data, DescriptionDictionary} from "@actions/expressions";
import {format} from "@actions/expressions/funcs/format";
import {Hover} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config.js";
import {hover} from "./hover.js";
import {registerLogger} from "./log.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {TestLogger} from "./test-utils/logger.js";
import {clearCache} from "./utils/workflow-cache.js";
import {ContextProviderConfig} from "./context-providers/config";
import {hover} from "./hover";
import {registerLogger} from "./log";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
const contextProviderConfig: ContextProviderConfig = {
getContext: (context: string) => {
@@ -1,7 +1,7 @@
import {hover} from "./hover.js";
import {testHoverConfig} from "./hover.test.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {clearCache} from "./utils/workflow-cache.js";
import {hover} from "./hover";
import {testHoverConfig} from "./hover.test";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {clearCache} from "./utils/workflow-cache";
beforeEach(() => {
clearCache();
+4 -4
View File
@@ -1,8 +1,8 @@
import {isString} from "@actions/workflow-parser";
import {DescriptionProvider, hover, HoverConfig} from "./hover.js";
import {getPositionFromCursor} from "./test-utils/cursor-position.js";
import {testFileProvider} from "./test-utils/test-file-provider.js";
import {clearCache} from "./utils/workflow-cache.js";
import {DescriptionProvider, hover, HoverConfig} from "./hover";
import {getPositionFromCursor} from "./test-utils/cursor-position";
import {testFileProvider} from "./test-utils/test-file-provider";
import {clearCache} from "./utils/workflow-cache";
export function testHoverConfig(tokenValue: string, tokenKey: string, description?: string) {
return {
+12 -12
View File
@@ -9,21 +9,21 @@ import {File} from "@actions/workflow-parser/workflows/file";
import {FileProvider} from "@actions/workflow-parser/workflows/file-provider";
import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {Hover} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config.js";
import {getContext, Mode} from "./context-providers/default.js";
import {getFunctionDescription} from "./context-providers/descriptions.js";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context.js";
import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default";
import {getFunctionDescription} from "./context-providers/descriptions";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {
getReusableWorkflowInputDescription,
isReusableWorkflowJobInput
} from "./description-providers/reusable-job-inputs.js";
import {ExpressionPos, mapToExpressionPos} from "./expression-hover/expression-pos.js";
import {HoverVisitor} from "./expression-hover/visitor.js";
import {info} from "./log.js";
import {isPotentiallyExpression} from "./utils/expression-detection.js";
import {findToken} from "./utils/find-token.js";
import {mapRange} from "./utils/range.js";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache.js";
} from "./description-providers/reusable-job-inputs";
import {ExpressionPos, mapToExpressionPos} from "./expression-hover/expression-pos";
import {HoverVisitor} from "./expression-hover/visitor";
import {info} from "./log";
import {isPotentiallyExpression} from "./utils/expression-detection";
import {findToken} from "./utils/find-token";
import {mapRange} from "./utils/range";
import {fetchOrConvertWorkflowTemplate, fetchOrParseWorkflow} from "./utils/workflow-cache";
export type HoverConfig = {
descriptionProvider?: DescriptionProvider;
+7 -7
View File
@@ -1,7 +1,7 @@
export {complete} from "./complete.js";
export {ContextProviderConfig} from "./context-providers/config.js";
export {documentLinks} from "./document-links.js";
export {hover} from "./hover.js";
export {Logger, LogLevel, registerLogger, setLogLevel} from "./log.js";
export {validate, ValidationConfig, ActionsMetadataProvider} from "./validate.js";
export {ValueProviderConfig, ValueProviderKind} from "./value-providers/config.js";
export {complete} from "./complete";
export {ContextProviderConfig} from "./context-providers/config";
export {documentLinks} from "./document-links";
export {hover} from "./hover";
export {Logger, LogLevel, registerLogger, setLogLevel} from "./log";
export {validate, ValidationConfig, ActionsMetadataProvider} from "./validate";
export {ValueProviderConfig, ValueProviderKind} from "./value-providers/config";
@@ -1,5 +1,5 @@
import {clearCache} from "../utils/workflow-cache.js";
import {getPositionFromCursor} from "./cursor-position.js";
import {clearCache} from "../utils/workflow-cache";
import {getPositionFromCursor} from "./cursor-position";
beforeEach(() => {
clearCache();
@@ -1,5 +1,5 @@
import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {createDocument} from "./document.js";
import {createDocument} from "./document";
/**
* Calculates the position of the cursor and the document without that cursor
+1 -1
View File
@@ -1,4 +1,4 @@
import {Logger} from "../log.js";
import {Logger} from "../log";
export class TestLogger implements Logger {
error(message: string): void {
@@ -1,9 +1,9 @@
import {convertWorkflowTemplate, parseWorkflow, WorkflowTemplate} from "@actions/workflow-parser";
import {getWorkflowContext, WorkflowContext} from "../context/workflow-context.js";
import {nullTrace} from "../nulltrace.js";
import {findToken} from "../utils/find-token.js";
import {getPositionFromCursor} from "./cursor-position.js";
import {testFileProvider} from "./test-file-provider.js";
import {getWorkflowContext, WorkflowContext} from "../context/workflow-context";
import {nullTrace} from "../nulltrace";
import {findToken} from "../utils/find-token";
import {getPositionFromCursor} from "./cursor-position";
import {testFileProvider} from "./test-file-provider";
export async function testGetWorkflowContext(input: string): Promise<WorkflowContext> {
const [textDocument, pos] = getPositionFromCursor(input);
+3 -3
View File
@@ -1,9 +1,9 @@
import {isScalar, parseWorkflow} from "@actions/workflow-parser";
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {TokenType} from "@actions/workflow-parser/templates/tokens/types";
import {nullTrace} from "../nulltrace.js";
import {getPositionFromCursor} from "../test-utils/cursor-position.js";
import {findToken} from "./find-token.js";
import {nullTrace} from "../nulltrace";
import {getPositionFromCursor} from "../test-utils/cursor-position";
import {findToken} from "./find-token";
type testTokenInfo = [definitionKey: string | null, tokenType: TokenType, literalValue?: string];
+2 -2
View File
@@ -1,5 +1,5 @@
import {getPositionFromCursor} from "../test-utils/cursor-position.js";
import {transform} from "./transform.js";
import {getPositionFromCursor} from "../test-utils/cursor-position";
import {transform} from "./transform";
describe("transform", () => {
it("adds : at end of line", () => {
+2 -2
View File
@@ -4,8 +4,8 @@ import {TemplateContext} from "@actions/workflow-parser/templates/template-conte
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {File} from "@actions/workflow-parser/workflows/file";
import {CompletionConfig} from "../complete.js";
import {nullTrace} from "../nulltrace.js";
import {CompletionConfig} from "../complete";
import {nullTrace} from "../nulltrace";
const parsedWorkflowCache = new Map<string, ParseWorkflowResult>();
const workflowTemplateCache = new Map<string, WorkflowTemplate>();
+3 -3
View File
@@ -4,9 +4,9 @@ import {Step} from "@actions/workflow-parser/model/workflow-template";
import {ScalarToken} from "@actions/workflow-parser/templates/tokens/scalar-token";
import {TemplateToken} from "@actions/workflow-parser/templates/tokens/template-token";
import {Diagnostic, DiagnosticSeverity} from "vscode-languageserver-types";
import {parseActionReference} from "./action.js";
import {mapRange} from "./utils/range.js";
import {ValidationConfig} from "./validate.js";
import {parseActionReference} from "./action";
import {mapRange} from "./utils/range";
import {ValidationConfig} from "./validate";
export async function validateAction(
diagnostics: Diagnostic[],
+7 -7
View File
@@ -1,11 +1,11 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {ActionMetadata, ActionReference} from "./action.js";
import {registerLogger} from "./log.js";
import {createDocument} from "./test-utils/document.js";
import {TestLogger} from "./test-utils/logger.js";
import {validate, ValidationConfig} from "./validate.js";
import {ValueProviderKind} from "./value-providers/config.js";
import {clearCache} from "./utils/workflow-cache.js";
import {ActionMetadata, ActionReference} from "./action";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {validate, ValidationConfig} from "./validate";
import {ValueProviderKind} from "./value-providers/config";
import {clearCache} from "./utils/workflow-cache";
registerLogger(new TestLogger());
@@ -1,7 +1,7 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {validate} from "./validate.js";
import {createDocument} from "./test-utils/document.js";
import {clearCache} from "./utils/workflow-cache.js";
import {validate} from "./validate";
import {createDocument} from "./test-utils/document";
import {clearCache} from "./utils/workflow-cache";
beforeEach(() => {
clearCache();
@@ -1,9 +1,9 @@
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {registerLogger} from "./log.js";
import {createDocument} from "./test-utils/document.js";
import {TestLogger} from "./test-utils/logger.js";
import {clearCache} from "./utils/workflow-cache.js";
import {validate} from "./validate.js";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate} from "./validate";
registerLogger(new TestLogger());
@@ -1,11 +1,11 @@
import {DescriptionDictionary} from "@actions/expressions";
import {DescriptionDictionary} from "@actions/expressions/.";
import {DiagnosticSeverity} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config.js";
import {registerLogger} from "./log.js";
import {createDocument} from "./test-utils/document.js";
import {TestLogger} from "./test-utils/logger.js";
import {clearCache} from "./utils/workflow-cache.js";
import {validate, ValidationConfig} from "./validate.js";
import {ContextProviderConfig} from "./context-providers/config";
import {registerLogger} from "./log";
import {createDocument} from "./test-utils/document";
import {TestLogger} from "./test-utils/logger";
import {clearCache} from "./utils/workflow-cache";
import {validate, ValidationConfig} from "./validate";
registerLogger(new TestLogger());
@@ -417,21 +417,6 @@ jobs:
expect(result).toEqual([]);
});
it("job.check_run_id", async () => {
const input = `
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo \${{ job.check_run_id }}
`;
const result = await validate(createDocument("wf.yaml", input));
expect(result).toEqual([]);
});
it("job.services.<service_id>", async () => {
const input = `
on: push

Some files were not shown because too many files have changed in this diff Show More